内存可见性
在“多线程5”末尾提到线程安全我呢提,原因在于每个线程都有一个自己的内存,同时他们又共享一个主内存,当线程要循环读取主内存的变量的时候,线程就会把主内存的变量拷贝到自己的线程内存里面读取,所以后续对主内存进行修改,线程仍然在自己的线程内存里面读取之前拷贝过来的变量,感知不到主内存的变化
wait和notify
可以让后执行的逻辑等待先执行的逻辑先跑完,再开始运行。
join和wait的区别
join是要让先运行的线程全部运行完毕,再开始执行下一个。
wait是让前一个线程执行完一段代码(无需全部运行完毕),就可以开始执行。
wait可以控制逻辑运行的顺序,可以很好的协调线程的执行顺序,拿到锁的线程运行到一半,如果来到了没到执行该逻辑的时机,就用wait阻塞等待。
wait可以释放锁,但是要释放锁的前提是加锁,所有在用wait的对象一定要处于加锁状态。
public class xxx{
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
System.out.println("whit前");
synchronized(object){
object.wait();
}
System.out.println("wait后");
}
}
其他线程完成工作后,调用notify唤醒这个wait线程,wait就会解除阻塞,重新获取到锁继续执行。
public class Demo24 {
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(()->{
try{
System.out.println("whit前");
synchronized (locker) {
locker.wait();
}
System.out.println("whit后");
}catch (InterruptedException e){
throw new RuntimeException();
}
});
Thread t2 = new Thread(()->{
Scanner scanner = new Scanner(System.in);
System.out.println("输入任意内容唤醒t1");
scanner.next();
synchronized(locker){
locker.notify();
}
});
}
}
notify也强制要求和synchronized使用,并且涉及到锁的对象必须是相同的,并且必须要wait线程先运行notify再运行。
notify一次只能唤醒有个wait线程,是随机的,不确定每次唤醒的线程是哪个。
public class xxx {
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(()->{
try{
System.out.println("t1whit前");
synchronized (locker) {
locker.wait();
}
System.out.println("t1whit后");
}catch (InterruptedException e){
throw new RuntimeException();
}
});
Thread t2 = new Thread(()->{
try{
System.out.println("t2whit前");
synchronized (locker) {
locker.wait();
}
System.out.println("t2whit后");
}catch (InterruptedException e){
throw new RuntimeException();
}
});
Thread t3 = new Thread(()->{
Scanner scanner = new Scanner(System.in);
System.out.println("输入任意内容唤醒");
scanner.next();
synchronized(locker){
locker.notify();
}
});
t1.start();
t2.start();
t3.start();
}
}
上面代码在唤醒线程的时候是随机唤醒t1或者t2,如果想要两个同时唤醒则要在t3线程里再加入一个notify线程。但这种方法有些麻烦,在这我们还有一个可以一键唤醒的方法notifyAll。
虽然notifyAll可以一键唤醒所有的wait,但是要注意的是wait是停止阻塞回到重新加锁执行状态,实际上他们还是一个一个的执行。
wait和sleep的区别
wait和sleep很像,但最主要的区别在于z针对锁的操作,wait需要加锁才能使用,而sleep不需要。
如果再synchronized内使用,sleep不会释放掉锁,而wait会释放出锁。