深入学习 Java 的线程
线程间的通信和协调、协作
synchronized内置锁
关键字synchronized可以修饰方法或者以同步块的形式来进行使用,用来处理不可预料的结果。
效果:并发的执行 → 串行执行
-
对象锁
- 作用于对象实例
- 作用于对象实例方法
-
类锁
- 作用于类的静态方法
- 作用于类的class对象
通过以下实验分析得出结论:如果是相同的锁,它的累加次数应该是遍历次数的倍数,反之不是。
public class SynTest {
public static void main(String[] args) {
var increment = new Increment();
var c1 = new Count(increment);
var c2 = new Count(increment);
c1.start();
c2.start();
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(increment.getNum());
}
private static class Count extends Thread {
private Increment increment;
public Count(Increment increment) {
this.increment = increment;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
// increment.run(); //无锁
// increment.run1(); //实例锁
// increment.run2(); //成员方法锁
// increment.run3(); //Integer 成员变量
increment.run4(); //类锁
}
}
}
static class Increment {
private Integer num = 0;
public Increment() {
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public void run() {
num++;
}
public void run1() {
synchronized (this) {
num++;
}
}
public synchronized void run2() {
num++;
}
public void run3() {
//Synchronization on a non-final field 'num'
synchronized (num) {
num++;
}
}
public void run4() {
//Synchronization on a non-final field 'num'
synchronized (Increment.class) {
num++;
}
}
}
}
volatile,最轻量的通信/同步机制
public class VolatileTest {
private volatile static boolean ready;
private static int number;
public static void main(String[] args) {
System.out.println("main start, number = " + number + " ready = " + ready);
PrintThread printThread = new PrintThread();
printThread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ready = true;
number += 100;
System.out.println("main modify value");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main end, number = " + number + " ready = " + ready);
}
private static class PrintThread extends Thread{
@Override
public void run() {
System.out.println("PrintThread is running.......");
while(!ready){
//System.out.println("lll");
};//无限循环
number ++;
System.out.println("PrintThread number = "+ number);
}
}
}
不加volatile时,子线程无法感知主线程修改了ready的值,从而不会退出循环,而加了volatile后,子线程可以感知主线程修改了ready的值,迅速退出循环。
volatile最适用的场景:一个线程写,多个线程读。
等待/通知机制
对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
- notify()
- 通知一个在对象上等待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入WAITING状态。
- notifyAll()
- 通知所有等待在该对象上的线程
- wait()
- 调用该方法的线程进入 WAITING状态,只有等待另外线程的通知或被中断才会返回.需要注意,调用wait()方法后,会释放对象的锁
- wait(long)
- 超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回
- wait (long,int)
- 对于超时时间更细粒度的控制,可以达到纳秒
等待和通知的范式
等待方:
synchronized(对象){
while(条件不满足){
对象.wait();
}
对应的处理逻辑
}
通知放:
synchronized(对象){
改变条件
对象.notifyAll();
}
面试题
调用yield() 、sleep()、wait()、notify()等方法对锁有何影响?
yield() 、sleep()被调用后,都不会释放当前线程所持有的锁。
调用wait()方法后,会释放当前线程持有的锁,而且当前被唤醒后,会重新去竞争锁,锁竞争到后才会执行wait方法后面的代码。
调用notify()系列方法后,对锁无影响,线程只有在syn同步代码执行完后才会自然而然的释放锁,所以notify()系列方法一般都是syn同步代码的最后一行。
为什么wait和notify方法要在同步块中调用?
主要是因为Java API强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。
如果消费者和生产者不竞争一把锁,可能出现lost wake up问题。
为什么你应该在循环中检查等待条件?
处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。因此,当一个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使用wait()方法效果更好的原因。
CompletableFuture
JDK1.8才新加入的一个实现类CompletableFuture,实现了Future, CompletionStage两个接口。实现了Future接口,意味着可以像以前一样通过阻塞或者轮询的方式获得结果。
ExecutorService executor = Executors.newCachedThreadPool();
// 创建异步任务
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
return 50;
}, executor);
// 在任务完成时执行操作
future.thenAccept(result -> {
System.out.println("Result: " + result);
});
future.thenRun(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("run ....");
});
// 主线程执行其他操作
System.out.println("Main end");