进程
线程
继承Thread类创建线程类
1、定义Thread子类,重写线程执行体也就是run方法。
2、创建Thread子类的实例,也就是线程对象。
3、调用线程对象的start方法来启动该线程,也就是run方法中的代码。
1 | public class StackThread extends Thread { |
运行结果:
1 | 1 |
使用继承Thread类的方法创建线程类时,多个线程之间无法共享线程类的实例变量。
实现Runnable接口创建线程类
1、定义Runnable接口的实现类,重写该接口的run方法。
2、创建Runnable实现类的实例,将实例作为Thread的target属性来创建线程对象。
3、调用线程对象的start方法来启动线程。
1 | @FunctionalInterface |
1 | public class StackRunnable implements Runnable{ |
运行结果:
1 | 1 |
使用Runnable接口的方式,创建多个使用共同target的线程时,多个线程共享Runnable实现类的变量。
Thread与Runnable的关系
1 | public class Thread implements Runnable { |
Thread类实现了Runnable接口,并且拥有Runnable类型的属性变量target。
所以若是以Thread子类的方式实现,则执行的是Thread子类的run方法;
若是以实现Runnable接口的方式实现,则执行的是Thread类中run方法,继而执行的是Runnable实现类中的run方法。
不存在既有Thread子类实现、又有Runnable接口实现的方式,Thread子类不能传入Runnable实现类实例作为构造参数来创建线程。
1 | public class StackThreadRunnable { |
使用Callable和Future创建线程
Callable接口提供了一个call方法作为线程执行体。
特性:call方法有返回值,且可以抛出异常。
因为call方法有返回值,所以Callable接口是一个泛型接口,实现Callable接口的时候需要传入泛型类型。
1 | @FunctionalInterface |
Future接口,是异步计算结果的容器接口。
提供了在等待异步计算完成时检查计算是否完成的状态,并在异步计算完成后获取计算结果而且只能通过 get 方法获取结果,如果异步计算没有完成则阻塞。
可以在异步计算完成前通过 cancel 方法取消,如果异步计算被取消则标记一个取消的状态。
如果希望异步计算可以被取消而且不提供可用的计算结果,则可以声明 Future<?> 形式类型,并返回 null 作为底层任务的结果。
1 | public interface Future<V> { |
RunnableFuture接口,继承了Future接口和Runnable接口。
1 | public interface RunnableFuture<V> extends Runnable, Future<V> { |
FutureTask类,实现了RunnableFuture接口。
Callable构造函数:
1 | public FutureTask(Callable<V> callable) { |
Runnable和返回值构造函数:
1 | public FutureTask(Runnable runnable, V result) { |
通过Executors的callable方法将Runnable和结果值生成Callable对象:
1 | public static <T> Callable<T> callable(Runnable task, T result) { |
Runnable适配器实现了Callable接口,并包含Runnable属性,实现的call方法中调用Runnable的run方法:
1 | private static final class RunnableAdapter<T> implements Callable<T> { |
状态:
1 | private static final int NEW = 0;//新建 |
属性:
1 | private Callable<V> callable;//执行任务 |
FutureTask实现了Runnable接口,所以可以作为Thread的target,所以Thread执行的是FutureTask的run方法:
1 | public void run() { |
而FutureTask的run方法中执行的是Callable实例的call方法。
FutureTask的方法是阻塞方法,直到获取到到结果才返回值,结束阻塞:
1 | public V get() throws InterruptedException, ExecutionException { |
使用:
FutureTask与Callable:
1、创建Callable接口的实现类,并实现call方法作为线程执行体,并返回值,再创建Callable实现类的实例。
2、使用FutureTask类来包装Callable对象,FutureTask对象封装了Callable对象中call方法的返回值。
3、使用FutureTask对象作为Thread对象的target,创建并启动新线程。
4、调用FutureTask对象的get方法,来获取子线程执行结束后的返回值。
1 | public static Integer callable() { |
结果:
1 | 100 |
FutureTask与Runnable:
1 | FutureTask futureTask = new FutureTask<Integer>(new Runnable() { |
结果:
1 | 0 |
线程的生命周期
1、新建状态。
新建一个线程,Java虚拟机为其分配内存,并初始化其成员变量的值。
2、就绪状态。
当线程对象调用了start方法,线程处于就绪状态,Java虚拟机会为该线程创建方法调用栈和程序计数器,表示可以运行,什么时候运行取决于Java虚拟机里线程调度器的调度。
只能对处于新建状态的线程调用start方法,否则会抛出IllegalThreadStateException异常。
3、运行状态。
如果处于就绪状态的线程获得了CPU的时间,开始执行run方法的线程执行体,则该线程处于运行状态,多处理器的机器上将会有多个线程并行parallel执行。
4、阻塞状态。
除非线程的执行体够短,瞬间被执行结束,否则线程不会一直处于运行状态,线程在运行过程中会被中断,剥夺该线程所占用的资源,目的是使其他线程获得执行的机会。
抢占式调度。系统给每个线程一个时间片段执行,用完后执行其他线程,且会考虑线程的优先级。
协作式调度。线程主动调用了sleep或yeild方法后才会放弃所占用的资源。
线程将会进入阻塞状态:
线程调用sleep方法主动放弃所占用的处理器资源。
线程调用了一个阻塞式IO方法,在方法返回之前线程会被阻塞。
线程视图获取同步监视器,但该同步监视器正在被其他线程所持有。
线程在等待某个通知notify。
线程被调用了suspend方法被挂起。但suspend方法容易发生死锁,应该尽量避免使用。
当前正在执行的线程被阻塞后,其他就绪的线程将会获得执行的机会,之后在合适的时候被阻塞的线程将会重新进入就绪状态,等待系统调度器再次调度该线程。
解除阻塞,进入就绪状态:
调用sleep方法的线程进过了指定时间。
线程调用的阻塞式IO方法已经返回。
线程成功地获取了试图获取的同步监视器。
线程正在等待某个通知时,其他线程发送了一个通知。
处于挂起状态的线程被调用了resume恢复方法。
线程从阻塞状态只能进入就绪状态,无法直接进入运行状态。
就绪状态线程获取处理器资源后进入运行状态,运行状态线程失去处理器资源后进入就绪状态,由系统线程调度决定。
调用yield方法可以让运行状态线程进入就绪状态。
5、死亡状态。
run方法或call方法执行完成,线程正常结束。
线程抛出一个未捕获的异常exception或错误error。
直接调用线程的stop方法结束线程,容易导致死锁,不建议使用。
线程的isAlive方法,判断线程是否存活,线程处于就绪、运行、阻塞状态返回true,线程处于新建、死亡状态返回false。
控制线程
join线程:
在当前线程调用某线程的join方法,当前线程将被阻塞,直到某线程执行完成为止。
后台/守护/精灵/Daemon线程:
Java垃圾回收线程就是典型的后台线程。
如果所有的前台线程都死亡,那么后台线程会自动死亡。
调用线程的setDaemon(true)方法可以将线程设置为后台线程,但必须在start方法之前设置,否则会抛出IllegalThreadStateException异常。
线程的isDaemon方法判断方法是否是后台线程。
线程睡眠sleep:
调用线程的sleep方法,让正在执行的线程暂停一段时间,并进入阻塞状态,在其睡眠时间内,该线程不会获得执行的机会。
线程的yield方法只是让线程暂停一下,但不会让线程进入阻塞状态,而是让线程进入就绪状态,并让系统的线程调度重新调度,有可能又重新将该线程调度出来执行,具体哪个线程被调度到,需要看线程的优先级。
sleep方法声明了InterruptedException异常,yield方法没有,不建议使用yield方法控制并发线程的执行。
线程优先级
线程默认优先级与创建它的父线程优先级相同。
线程通过setPriority(int newPriority)、getPriority设置和获取线程的优先级。
MAX_PRIORITY 10
MIN__PRIORITY 1
NORMAL_PRIORITY 5
线程同步
同步代码块。
线程的run方法不具备同步安全性,为了解决这个问题,Java的多线程引入了同步监视器,使用同步监视器的通用方法就是同步代码块。
1 | synchronized(obj){ |
obj就是同步监视器,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。
任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码执行完成后,该线程会释放对同步监视器的锁定。
同步监视器的目的:
阻止两个线程对同一个共享资源的并发访问,推荐使用可能被并发访问的共享资源充当同步监视器。
同步方法。
synchronized修饰的非静态实例方法,同步监视器是this,也就是调用该方法的对象。
线程安全的类:
该类的对象可以被多个线程安全地访问。
每个线程调用该对象的任意方法之后都将得到正确的结果。
每个线程调用该对象的任意方法之后,该对象的状态依然保持合理状态。
可变类的线程安全是以降低程序的运行效率作为代价的,所以只对会改变竞争资源的方法同步,且提供单线程和多线程两种版本,例如单线程使用StringBuilder保证性能,多线程使用StringBuffer保证多线程安全。
线程释放对同步监视器的锁定:
线程的同步方法、同步代码快执行结束,释放同步监视器。
线程的同步代码块、同步方法中遇到未处理的error和exception结束,释放同步监视器。
线程的同步代码块、同步方法中遇到break、return终止,释放同步监视器。
线程的同步代码块、同步方法执行的同时,程序执行了同步监视器对象的wait方法,当前线程暂停,释放同步监视器。而调用线程sleep方法和yield方法,其他线程调用该线程的suspend方法都不会释放同步监视器。
同步锁
通过显式定义同步锁Lock对象来实现同步,同步锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象。
读写锁:ReadWriteLock。
可重入锁:ReentrantLock,显式加锁、释放锁。
可重入性:线程可以对已被加锁的ReentrantLock锁对象再次加锁,ReentrantLock对象会维持一个计数器追踪lock方法的嵌套调用,线程每次调用lock加锁后,必须显式地调用unlock来释放锁,所以一段被锁保护的代码可以调用另一个被锁保护的代码。
可重入读写锁:ReentrantReadWriteLock。
StampedLock:是为了优化可重入读写锁性能的一个锁实现工具。
Lock显式地使用Lock对象作为同步锁,同步方法/块是系统隐式地使用当前对象作为同步监视器。
使用finally块,确保在必要时释放锁。
死锁
线程互相等待对方释放同步监视器,就会发生死锁,所有线程处于阻塞状态,无法继续。
Thread类的suspend方法容易产生死锁,不建议使用。
线程通信
Object类的wait、notify、notifyAll方法,由同步监视器对象来调用进行线程通信。
为什么wait,notify,notifyAll这些跟线程有关的方法是封装在Object中而不线程的Thread类呢?
1 | 因为可以把任意的对象作为锁资源进行竞争,所以任意对象都可以调用wait()和notify(),而Object是所有类的父类,所以wait和notify属于Object。 |
notify:唤醒在此对象监视器上等待的单个线程。
notifyAll:唤醒在此对象监视器上等待的所有线程。
wait:导致当前的线程等待,直到其他线程调用此对象的notify方法或notifyAll方法。
wait(long timeout):导致当前的线程等待,直到其他线程调用此对象的notify方法或notifyAll方法,或者超过指定的时间量。
wait(long timeout, int nanos):导致当前的线程等待,直到其他线程调用此对象的notify方法或notifyAll方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。