0%

Java多线程

进程

线程

继承Thread类创建线程类

1、定义Thread子类,重写线程执行体也就是run方法。
2、创建Thread子类的实例,也就是线程对象。
3、调用线程对象的start方法来启动该线程,也就是run方法中的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StackThread extends Thread {
private int mNum = 0;

@Override
public void run() {
super.run();//有无均可,此时Thread中target肯定是空的。
mNum++;
System.out.println(mNum);
}

public static void main(String[] args) {
StackThread stackThread = new StackThread();
stackThread.start();
StackThread stackThread1 = new StackThread();
stackThread1.start();
}
}

运行结果:

1
2
1
1

使用继承Thread类的方法创建线程类时,多个线程之间无法共享线程类的实例变量。

实现Runnable接口创建线程类

1、定义Runnable接口的实现类,重写该接口的run方法。
2、创建Runnable实现类的实例,将实例作为Thread的target属性来创建线程对象。
3、调用线程对象的start方法来启动线程。

1
2
3
4
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class StackRunnable implements Runnable{
private int mNum=0;
@Override
public void run() {
mNum++;
System.out.println(mNum);
}

public static void main(String[] args) {
StackRunnable stackRunnable = new StackRunnable();
Thread thread = new Thread(stackRunnable);//此处的stackRunnable就是Thread中的target
thread.start();
Thread thread1 = new Thread(stackRunnable);
thread1.start();
}
}

运行结果:

1
2
1
2

使用Runnable接口的方式,创建多个使用共同target的线程时,多个线程共享Runnable实现类的变量。

Thread与Runnable的关系

1
2
3
4
5
6
7
8
9
10
public class Thread implements Runnable {

@Override
public void run() {
if (target != null) {
target.run();
}
}

}

Thread类实现了Runnable接口,并且拥有Runnable类型的属性变量target。
所以若是以Thread子类的方式实现,则执行的是Thread子类的run方法;
若是以实现Runnable接口的方式实现,则执行的是Thread类中run方法,继而执行的是Runnable实现类中的run方法。
不存在既有Thread子类实现、又有Runnable接口实现的方式,Thread子类不能传入Runnable实现类实例作为构造参数来创建线程。

1
2
3
4
5
6
7
public class StackThreadRunnable {
public static void main(String[] args) {
StackRunnable stackRunnable = new StackRunnable();
StackThread thread = new StackThread(stackRunnable);//无法编译成功
thread.start();
}
}

使用Callable和Future创建线程

Callable接口提供了一个call方法作为线程执行体。
特性:call方法有返回值,且可以抛出异常。
因为call方法有返回值,所以Callable接口是一个泛型接口,实现Callable接口的时候需要传入泛型类型。

1
2
3
4
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}

Future接口,是异步计算结果的容器接口。
提供了在等待异步计算完成时检查计算是否完成的状态,并在异步计算完成后获取计算结果而且只能通过 get 方法获取结果,如果异步计算没有完成则阻塞。
可以在异步计算完成前通过 cancel 方法取消,如果异步计算被取消则标记一个取消的状态。
如果希望异步计算可以被取消而且不提供可用的计算结果,则可以声明 Future<?> 形式类型,并返回 null 作为底层任务的结果。

1
2
3
4
5
6
7
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);//试图取消Future也就是FutureTask里关联的Callable任务。
boolean isCancelled();//如果在Callable任务正常完成前被取消,返回true。
boolean isDone();//任务已经结束,在任务完成、任务取消、任务异常的情况下都返回true。
V get() throws InterruptedException, ExecutionException;//返回Callable任务里call方法的返回值,此方法会导致程序阻塞,必须等到子线程结束后才会得到返回值。
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;////如果在指定单位时间内没有返回值,将会抛出异常。
}

RunnableFuture接口,继承了Future接口和Runnable接口。

1
2
3
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}

FutureTask类,实现了RunnableFuture接口。

Callable构造函数:

1
2
3
4
5
6
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}

Runnable和返回值构造函数:

1
2
3
4
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}

通过Executors的callable方法将Runnable和结果值生成Callable对象:

1
2
3
4
5
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}

Runnable适配器实现了Callable接口,并包含Runnable属性,实现的call方法中调用Runnable的run方法:

1
2
3
4
5
6
7
8
9
10
11
12
private static final class RunnableAdapter<T> implements Callable<T> {
private final Runnable task;
private final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}

状态:

1
2
3
4
5
6
7
private static final int NEW          = 0;//新建
private static final int COMPLETING = 1;//计算中
private static final int NORMAL = 2;//正常
private static final int EXCEPTIONAL = 3;//异常
private static final int CANCELLED = 4;//已取消
private static final int INTERRUPTING = 5;//中断中
private static final int INTERRUPTED = 6;//已中断

属性:

1
2
3
4
private Callable<V> callable;//执行任务
private Object outcome;//执行结果,或者异常
private volatile Thread runner;//执行线程
private volatile WaitNode waiters;//等待的线程栈

FutureTask实现了Runnable接口,所以可以作为Thread的target,所以Thread执行的是FutureTask的run方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void run() {
if (state != NEW ||
!U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}

而FutureTask的run方法中执行的是Callable实例的call方法。
FutureTask的方法是阻塞方法,直到获取到到结果才返回值,结束阻塞:

1
2
3
4
5
6
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}

使用:
FutureTask与Callable:
1、创建Callable接口的实现类,并实现call方法作为线程执行体,并返回值,再创建Callable实现类的实例。
2、使用FutureTask类来包装Callable对象,FutureTask对象封装了Callable对象中call方法的返回值。
3、使用FutureTask对象作为Thread对象的target,创建并启动新线程。
4、调用FutureTask对象的get方法,来获取子线程执行结束后的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static Integer callable() {
Integer result=0;
FutureTask futureTask = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 100;
}
});
Thread thread = new Thread(futureTask);
thread.start();
try {
result = (Integer) futureTask.get();
System.out.println(result);
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
return result;
}

结果:

1
100

FutureTask与Runnable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    FutureTask futureTask = new FutureTask<Integer>(new Runnable() {
@Override
public void run() {
System.out.println();
}
}, 0);
Thread thread = new Thread(futureTask);
thread.start();
try {
Integer result = (Integer) futureTask.get();
System.out.println(result);
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
}

结果:

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
2
3
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方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。