一个线程就是一个"执行流",一个进程中至少有一个线程,每个线程运行自己的代码块,多个线程会同时运行多段代码。
如果把进程比作银行的服务,张三(即用户)来接受服务时必然要经过不同程序,例如身份查询、业务解释、业务办理等等,一个线程就相当于一个工作人员,而多线程就相当于多个工作人员为张三处理事务,每个工作人员同时分工合作,这样就大大提高了工作效率,这也就是所谓的并发编程。
并发编程在狭义上指的是一个CPU核心运行多个线程,与之对应的还有并行——指n个CPU核心运行n个线程,但是我们常说的并发编程广义上包括了并发和并行。
并发编程充分利用了多核CPU的性能,从而更能发挥CPU的运算效率。
同时,线程相比于进程更轻量化,创建和销毁一个线程的速度远大于创建和销毁一个进程的速度。对于一个进程来说,它的创建和销毁有如下步骤:
创建
销毁
其中给进程分配资源和销毁资源极大占用了操作系统的资源,因此效率较低。
以之前的银行服务为例,如果一个银行同时遇到多个客户就必须开辟多个进程然后销毁,这就大大浪费了时间。
而一个进程上的多个线程是共享同一份资源的,直到最后一个线程被销毁,这个进程的资源才被释放,所以极大节约了系统资源。
操作系统中是通过一组PCB来描述一个进程的,其中每个PCB对应一个线程。
因此一个进程至少有一个线程,线程是被包含在进程中的。
由于线程共享资源,一组PCB的内存指针和文件描述符表其实是同一份东西,而状态、上下文、优先级、记账信息则是每个PCB独有的。
总的来说:
进程是资源分配的单位
线程是调度执行的单位
java实现了操作多线程的API,想要实现多线程只需要调用API即可,而Thread类就是java提供的API。
创建Thread类的方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用 Runnable 对象创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 对象创建线程对象,并命名 |
获取Thread类的属性方法
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
ID 是线程的唯一标识,不同线程不会重复
名称是各种调试工具用到
状态表示线程当前所处的一个情况
优先级高的线程理论上来说更容易被调度到
关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
是否存活,即简单的理解,为 run 方法是否运行结束了
线程的中断问题,下面我们进一步说明
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(1);
}
}
}
MyThread t = new MyThread();
使用Runnable接口可以减少代码的耦合性,即要修改Thread的功能只需要修改对应的Runnable接口或者放入新的Runnable接口
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(1);
}
}
}
Thread t = new Thread(new MyRunnable());
//继承Thread类
Thread t = new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(1);
}
}
};
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(1);
}
}
线程让程序更有效率的同时也给程序猿带来了很多问题,这些问题往往伴随着它们的优势。
我们知道多个线程公用同一份资源,同时运行,这就不可避免导致了多个线程同时操作同一份资源的情况,产生一系列如脏读、幻读、不可重复读等问题,为了解决这些问题就必须以效率为代价对线程的权限加以限制,即为线程“上锁“,具体方法会在下篇博客中阐述。
线程产生的问题难以修复。线程的调度是由操作系统决定的,因此线程执行的顺序是不可预料的,这就给多线程程序的执行带来了太多的变数。所以如果代码产生了问题,由于顺序的不确定性,可能每次运行会产生不同的结果,甚至有可能有的时候运行结果是对的,有的时候会错,这就为调试增加了难度。如果要编写多线程代码,程序猿就必须考虑所有可能的调度情况,保证每种情况都不出现问题。