目录
1.synchronized的基本使用
2.监视器锁monitor lock
2.1synchronized 的特性
2.2死锁
3.Java 标准库中的线程安全类
4. volatile 关键字
5.wait 和 notify
5.1wait()方法
5.2notify()方法
synchronized 的本质操作,就是修改了Object 对象中的 对象头 里的一个标记
当两个线程同时针对一个对象加锁,才会产生竞争
如果两个线程针对不同对象加锁,就不会竞争。
举个例子:同学A 进一个ATM取款, 同学B 也想要进这个ATM取款,就得等A出来;
如果 同学B 想要进另一个ATM取款,就不需要等
(1)把synchronized 加到普通方法上
直接修饰普通方法(实例方法),也就是相当于把锁对象指定为 this
synchnorized public void increase() {
count++;
}
(2)把synchronized加到代码块上
如果要是针对某个代码块加锁,就需要手动指定。
public void increase() {
synchnorized (this) {
count++;
}
}
(3)synchronized 加到代码块上
把synchronized 加到静态方法上,严谨的说是“类方法”。
public void increase() {
synchnorized (Counter.class) {
}
}
(1)互斥
(2)刷新内存
synchronized的工作过程
1. 获得互斥锁2. 从主内存拷贝变量的最新副本到工作的内存3. 执行代码4. 将更改后的共享变量的值刷新到主内存5. 释放互斥锁
synchnorized public void increase() {
synchnorized (this) {
count++;
}
}
对于可重入锁来说,上述连续加锁操作,不会导致死锁。
可重入锁内部,会记录当前的锁被哪个线程占用的,同时也会记录一个“加锁次数"
线程a针对锁第一次加锁的时候,显然能够加锁成功,锁内部就记录了当前的占用着是a,同时加锁次数为1.
后续再a对锁进行加锁,此时就不是真加锁,而是单纯的把计数给自增,加锁次数为2~
后续再解锁的时候,先把计数进行-1.当锁的计数减到0的时候,就真的解锁。
可重入锁的意义就是降低了程序猿的负担. (使用成本,提高了开发效率)
但是也带来了代价,程序中需要有更高的开销(维护锁属于哪个线程,并且加减计数.降低了运行效率)
1.一个线程一把锁
2.两个线程两把锁
3.N个线程,M把锁
死锁的四个必要条件
1.互斥使用~一个锁被一一个线程占用了之后,其他线程占用不了(锁的本质,保证原子性)
2.不可抢占- 一个锁被一个线程占用了之后,其他的线程不能把这个锁给抢走. (挖墙脚是不行的)
3.请求和保持当-个线程占据了多把锁之后,除非显式的释放锁,否则这些锁始终都是被该线程持有的~
4.环路等待等待关系, 成环了~~A等B,B等C,C又等A
Java 标准库中很多都是线程不安全的. 这些类可能会涉及到多线程修改共享数据, 又没有任何加锁措施.
ArrayListLinkedListHashMapTreeMapHashSetTreeSetStringBuilder
但是还有一些是线程安全的. 使用了一些锁机制来控制
Vector ( 不推荐使用 )HashTable ( 不推荐使用 )ConcurrentHashMapStringBuffer
volatile只是保证可见性,不保证原子性
volatile 不会引起线程阻塞
由于线程之间是抢占式执行的 , 因此线程之间执行的先后顺序难以预知 .但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序
wait() / wait(long timeout): 让当前线程进入等待状态.
public class Tes16 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized(object) {
System.out.println("wait 前");
object.wait();//等待
System.out.println("wait 后");
方法 notify() 也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify ,并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。 ( 并没有 " 先来后到 ")在notify() 方法后,当前线程不会马上释放该对象锁,要等到执行 notify() 方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
如下通过线程 t2 唤醒 线程 t1 的wait
public class Test17 {
private static Object locker = new Object();
public static void main(String[] args) throws InterruptedException {
//创建线程 t1
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);//main线程阻塞3秒
//创建线程 t2
Thread t2 = new Thread(() -> {
synchronized(locker) {
System.out.println("notify 之前");
locker.notify();
}
});
t2.start();
System.out.println("notify 之后");
}
}