标题制作网站,酒泉网站建设与制作,网站模板 介绍,网店设计方案范文目录
一、synchronized 关键字简介
二、synchronized 的特点 -- 互斥
三、synchronized 的特点 -- 可重入
四、synchronized 的使用示例
4.1 修饰代码块 - 锁任意实例
4.2 修饰代码块 - 锁当前实例
4.3 修饰普通方法 - 锁方法所在实例
4.4 修饰代码块 - 锁指定类对象
…目录
一、synchronized 关键字简介
二、synchronized 的特点 -- 互斥
三、synchronized 的特点 -- 可重入
四、synchronized 的使用示例
4.1 修饰代码块 - 锁任意实例
4.2 修饰代码块 - 锁当前实例
4.3 修饰普通方法 - 锁方法所在实例
4.4 修饰代码块 - 锁指定类对象
4.5 修饰静态方法 - 锁方法所在类对象
五、锁竞争和死锁
5.1 出现死锁的三种典型场景
5.1.1 “重复锁”
5.1.2 “互相锁”
5.1.3 “复杂锁”
5.2 死锁产生的必要条件
5.3 解决死锁的方案 一、synchronized 关键字简介
概述Java中加锁的方式有很多种其中使用 synchronized 关键字进行加锁是最常用的。synchronized 是一种监视器锁monitor lock。加锁的目的是为了将多个操作“打包”为一个有“原子性”的操作。加锁的核心规则进行加锁的时候必须先准备好“锁对象”锁对象可以是任何类型的实例。synchronized 的底层实现synchronized 的底层是使用操作系统的 mutex lock 实现的本质上依然是调用系统的 API 依靠 CPU 的特定指令完成加锁功能的。 二、synchronized 的特点 -- 互斥
1什么是互斥某个对象使用了 synchronized 进行修饰当一个线程访问这个对象时就会加锁其他线程想要访问这个对象就会先阻塞等待直到这个对象解锁。这就是使用 synchronized 关键字时产生的互斥效果。
2什么是加锁、解锁 当程序进入由 synchronized 修饰的代码块、对象或方法时即相当于加锁。 当程序退出由 synchronized 修饰的代码块、对象或方法时即相当于加锁。
3由互斥到冲突什么是锁冲突/锁竞争 由于 synchronized 具有互斥的特点因此当多个线程同时竞争同一个锁时线程间的冲突就不可避免。当有一个线程获得了锁那么此时其他还想获得该锁的线程就只能阻塞等待直到锁被释放后才能再次竞争这个锁。这就是锁冲突或者说锁竞争。 三、synchronized 的特点 -- 可重入
1什么是不可重入锁 同一个线程在还没释放锁的情况下访问同一个锁。 从 synchronized 的互斥特点可以了解到当锁未被释放访问该锁的线程会阻塞等待。 由于锁还没有释放第二次加锁时线程进入阻塞等待。 线程进入阻塞等待则第一次的锁无法释放。 这样程序就进入了僵持状态。 这种状态被称为“死锁”。 而这样的锁被称为“不可重入锁”。
2什么是可重入锁 可重入锁与不可重入锁不同不会出现自己把自己锁死的情况。synchronized 就是可重入锁。
3可重入锁是怎么实现可重入的 可重入锁锁内部会有两个属性分别是“线程持有者”和“计数器”。 线程持有者 记录了当前锁是被哪一个线程持有的。 当发生重复加锁时会判断是否是同一线程加锁。 如果是则跳过加锁步骤只是在另一个属性“计数器”上自增1。 如果不是则阻塞等待。 计数器 用于记录当前锁的加锁次数。 每次加锁“计数器”计数会自增1比如重复加锁10次那么计数器的值就会等于10。 每次解锁“计数器”计数会自减1当计数器的值归零时才是真正的释放锁此时该锁才能被其他线程获取。 四、synchronized 的使用示例
4.1 修饰代码块 - 锁任意实例
public class Test{//创建任意类型实例作为锁对象Object locker new Object();public void lockTest(){//使用synchronized指定locker作为锁对象在需要加锁的代码块上加锁synchronized (locker) {//需要加锁的代码}}
}
4.2 修饰代码块 - 锁当前实例
public class Test{public void lockTest(){//使用synchronized指定this(当前实例)作为锁对象在需要加锁的代码块上加锁synchronized (this) {//需要加锁的代码}}
}
4.3 修饰普通方法 - 锁方法所在实例
public class Test{//在普通方法上使用synchronized指定当前实例作为锁对象将方法加锁public synchronized void lockTest(){//需要加锁的代码}
}
4.4 修饰代码块 - 锁指定类对象
//任意类
public class Locker{}public class Test{public void lockTest(){//使用synchronized指定class(类对象)作为锁对象在需要加锁的代码块上加锁synchronized (Locker.class) {//需要加锁的代码}}
}
4.5 修饰静态方法 - 锁方法所在类对象
public class Test{//在静态方法上使用synchronized指定当前类对象作为锁对象将方法加锁public synchronized static void lockTest(){//需要加锁的代码}
} 五、锁竞争和死锁
1由锁竞争到死锁什么是死锁 上文在“synchronized 的特点 -- 互斥”中介绍了什么是锁竞争。 人可以卷但卷到一定程度就可以卷死自己或卷死别人。 那么锁也是可以卷的比如锁竞争。 加锁可以解决线程安全问题但是如果加锁方式不当就可能产生死锁。
2死锁对程序来说意味着什么 死锁是程序中最严重的一类BUG。 程序可能因此停摆、崩溃。 当然人也可能因此“停摆、崩溃”。
5.1 出现死锁的三种典型场景
死锁有以下三种典型场景。1 “重复锁”如一个线程一把锁自己把自己拷上了。 2“互相锁”如两个线程两把锁互相把对方拷上了。3“复杂锁”如上述两种锁重复或复合发生的情况多个线程多把锁超级加倍。以上三个锁的名字是笔者归纳总结后为方便记忆而概括出的锁名不是公认的专业名词。
5.1.1 “重复锁”
“重复锁”是指什么情况 锁在被释放前同一个线程再次要求获得同一个锁。 锁没被释放线程无法获得锁进入阻塞。 但线程阻塞代码就不会继续执行锁也就一直得不到释放。 由此实现了自己卡死自己的“壮举”。
代码演示死锁 public static void main(String[] args) {//创建任意类型实例作为锁对象Object locker new Object();Thread t new Thread(()-{//指定locker作为锁对象synchronized (locker) {//再次指定locker作为锁对象synchronized (locker){//需要加锁的代码}}});}
synchronized 是“可重入锁”。 “可重入锁”和“不可重入锁”的定义和区别在上文“synchronized 的特点 -- 可重入”中说明了。 Java 提供的 synchronized 关键字实现的是一个“可重入锁”。所以不用担心会发生这种死锁。
5.1.2 “互相锁”
1“互相锁”是指什么情况 两个线程都获取了一个不同的锁。 但是在各自的锁释放前又分别去获取了对方的锁。 但此时两把锁都还没有被释放那么两个线程都进入阻塞等待的状态都在等对方把锁释放。
代码演示死锁 public static void main(String[] args) {//创建两个任意类型实例作为锁对象Object locker1 new Object();Object locker2 new Object();Thread t1 new Thread(()-{//指定locker1作为锁对象synchronized (locker1) {System.out.println(t1获取locker1);//休眠1秒保证线程t2可以获取到锁locker2。try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//指定locker2作为锁对象synchronized (locker2) {//需要加锁的代码System.out.println(t1获取locker2);}}});Thread t2 new Thread(()-{//指定locker2作为锁对象synchronized (locker2) {System.out.println(t2获取locker2);//休眠1秒保证线程t1可以获取到锁locker1。try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//指定locker1作为锁对象synchronized (locker1) {//需要加锁的代码System.out.println(t2获取locker1);}}});t1.start();t2.start();}//运行结果
t2获取locker2
t1获取locker1
...程序没有正常执行完毕因为出现了死锁。
5.1.3 “复杂锁”
“复杂锁”是指什么情况 “复杂锁”指前两种情况重复发生或复合发生时锁与锁之间相互叠加、“犬牙交错”的局面。
图示演示死锁 5.2 死锁产生的必要条件
产生死锁有以下四个必要条件1互斥获取锁的过程需要是互斥的当锁被一个线程获取另一个线程想获取这把锁就必须阻塞等待。这是锁的基本特性之一。2不可劫取。锁被一个线程获取后另一个线程不能强行把锁抢走除非锁被持有线程释放。这也是锁的基本特性之一。3请求保持。当一个线程申请锁而进入阻塞等待时对自己已经持有的锁保持持有状态。这个条件与代码结构相关。4循环等待/环路等待。线程申请锁而锁在等待线程释放形成环路。这个条件与代码结构相关。
5.3 解决死锁的方案
解决死锁的方案有以下几种方法1超时放弃。线程进入阻塞等待当等待时间超过预设时间则获取锁失败将持有的锁释放。2依序加锁。指定加锁的顺序规则所有线程都需要按照规则规定的加锁顺序进行加锁。
图示演示依序加锁