synchronized关键字(monitor lock)
synchronized使用示例
修饰普通方法修饰代码块修饰静态方法 synchronized的特性
互斥刷新内存可重入 Java 标准库中的线程安全类 死锁
死锁的其他场景
一个线程一把锁两个线程两把锁N个线程M把锁 死锁的必要条件
互斥使用不可抢占请求和保持环路等待 Volatilewait 和 notify
synchronized关键字(monitor lock) synchronized使用示例 修饰普通方法 修饰代码块 修饰静态方法 synchronized的特性 互斥 刷新内存 可重入直观来讲,同一个线程针对同一个锁,连续加锁两次,如果出现了死锁,就是不可重入,如果不是死锁,就是可重入的。
这种代码在实际开发中,很容易被写出来,如果代码真的死锁了,程序的bug就太多了。
实现JVM的大佬们把synchronized实现成了可重入锁,对于可重入锁,上述连续加锁的操作,不会导致死锁。
原因:
可重入锁的内部,会记录当前的锁被哪个线程占用的,同时也会记录一个“加锁次数”。线程A针对锁,第一次加锁的时候,显然能够加锁成功,锁内部就记录了当前占用着的是A,同时加锁次数为1,后续在A对锁进行加锁时,此时就不是真的加锁,而是单纯的吧计数给自增,加锁次数为2,后续在解锁的时候,先把计数进行 -1,当锁的计数减到0的时候,就真的解锁。
外层先加了一次锁,里层对同一个对象在加一次锁。
外层锁:进入方法,则开始加锁,这次能够加锁成功,当前锁是没有人占用的。
里层锁:进入代码块,开始加锁,这次加锁不能加锁成功,得等到外层锁释放了之后,里层锁才能加锁成功。
这就死锁了。
可重入锁的意义:降低了程序员的负担,提高了开发效率。但同时也带来了代价,程序中需要更高的开销(维护锁属于哪个线程,并且加减计数,降低了运行效率)
Java 标准库中的线程安全类1.Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, 又没有任何加锁措施.
ArrayList、linkedList、HashMap、TreeMap、HashSet、TreeSet、StringBuilder
2.但是还有一些是线程安全的. 使用了一些锁机制来控制。
Vector (不推荐使用)、HashTable (不推荐使用)、ConcurrentHashMap、StringBuffer
3.还有的虽然没有加锁, 但是不涉及 “修改”, 仍然是线程安全的。String
连续锁两次
两个线程两把锁 N个线程M把锁举例:哲学家就餐问题
一个锁被一个线程占用了之后,其他线程就占用不了(锁的本质,保证原子性)
不可抢占一个锁被一个线程占用了之后,其他的线程就不能把这个锁给抢走。
请求和保持当一个线程占用了多把锁之后,除非显示的释放锁,否则这些锁始终都是被该线程持有的。
环路等待等待关系成环了。A等B,B等C,C等A
如何避免出现环路等待?
只要约定好,针对多把锁的时候,有固定的顺序即可。所有的线程都遵循同样的规则顺序,就不会出现环路等待。
前3条是锁本身的特点,实际开发中要想避免死锁,需从第4 条为切入点。
Volatile实际开发中,很少出现这种一个线程需要锁里面在套锁的情况。如果使用场景,不得不进行嵌套,一定要先约定好加锁的顺序。
禁止编译器优化,保证内存可见性
volatile只是保证可见性,不保证原子性。volatile只是处理一个线程读,一个线程写的情况,synchronized都能处理。
JMM (java Memory Modle)
JMM就是把上述的硬件结构,在Java中用专门的术语重新抽象封装了一遍。
由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知.但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。
wait 和 notify 都是Object 对象的方法
调用wait 方法的线程,就会进入阻塞
阻塞到其他线程通过notify来通知
public class Demo17 {
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
System.out.println("wait 前");
object.wait();
System.out.println("wait 后");
}
}
执行效果
原因:
wait内部会做三件事
1.先释放锁
2.等待其他线程通知
3.收到通知之后,重新获取锁,并继续往下执行
因此要想使用wait/notify就要搭配synchronized
public class Demo17 {
public static void main(String[] args) throws InterruptedException {
Object object=new Object();
synchronized (object){
System.out.println("wait 前");
object.wait();
System.out.println("wait 后");
}
}
}
执行效果
public class Demo18 {
private static Object locker =new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(() ->{
//进行wait
synchronized (locker){
System.out.println("wait之前");
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait之后");
}
});
t1.start();
Thread.sleep(3000);
Thread t2=new Thread(() ->{
//进行notify
synchronized (locker){
System.out.println("notify 之前");
locker.notify();
System.out.println("notify之后");
}
});
t2.start();
}
}
执行效果
wait notify 都是针对同一个对象来操作的。
例如现在有一个对象o,有10个线程,都调用了o.wait,此时10个线程都是阻塞状态
如果调用了o.notify,就会把10个其中的一个给唤醒(唤醒哪一个,不确定)
针对notifyAll,就会把所有的10个线程都给唤醒
wait 唤醒之后,会重新尝试获取到锁(这个过程就会发生竞争)



