Java并发编程原理与实战十一:锁重入&自旋锁&死锁
一、锁重入
package com.roocon.thread.t6;
public class Demo {
/*
当第一个线程A拿到当前实例锁后,进入a方法,那么,线程A还能拿到被当前实例所加锁的另一个
同步方法b吗?是不是只有当线程A释放了a方法的同步锁后,才可以去获取b方法的同步锁呢?
*/
public synchronized void a(){
System.out.println("a");
b();
}
public synchronized void b(){
System.out.println("b");
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
Demo demo = new Demo();
demo.a();
}
}).start();
}
}
运行结果:
a
b
以上结果说明,线程A在释放方法a的同步锁之前,是可以重新获得b方法的同步锁的。同一个线程拿到同一个对象的锁,它是可以进入另一个同步方法的,这就是锁的重入。以上代码仅仅是同一个线程在一个同步方法中去成功调用另一个同步方法,并且,锁的是同一个实例。那么,不同的线程拿同一把对象去加锁,会怎样进行呢?
package com.roocon.thread.t6;
public class Demo {
/*
当第一个线程A拿到当前实例锁后,进入a方法,那么,线程A还能拿到被当前实例所加锁的另一个
同步方法b吗?是不是只有当线程A释放了a方法的同步锁后,才可以去获取b方法的同步锁呢?
*/
public synchronized void a(){
System.out.println("a");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void b(){
System.out.println("b");
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Demo demo = new Demo();
//Demo demo1 = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
demo.b();
}
}).start();
}
}
运行结果:
a
b
虽然以上运行结果还是a b,但是,由于锁的是同一个实例,所以,在输出a之后,要等待5s才会输出b。若将以上代码修改为如下,锁的不是同一个实例:
package com.roocon.thread.t6;
public class Demo {
/*
当第一个线程A拿到当前实例锁后,进入a方法,那么,线程A还能拿到被当前实例所加锁的另一个
同步方法b吗?是不是只有当线程A释放了a方法的同步锁后,才可以去获取b方法的同步锁呢?
*/
public synchronized void a(){
System.out.println("a");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void b(){
System.out.println("b");
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Demo demo = new Demo();
Demo demo1 = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
demo1.b();
}
}).start();
}
}
运行结果:
a
b
a b几乎是同时输出的。
以上两个代码说明,如果多个线程同时去执行同步方法,如果锁的是同一个实例,那么必须等当前这个同步方法释放锁后,才可以去获取另一个同步锁方法。
而如果锁的不是同一个实例,那么,两个同步方法几乎是可以同时执行。有了以上基础,那么再来理解以下代码,就很简单了。

package com.roocon.thread.t6;
public class Demo {
public synchronized void a(){
System.out.println("a");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("print b()");
b();
}
public synchronized void b(){
System.out.println("b");
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(new Runnable() {
@Override
public void run() {
demo.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
demo.b();
}
}).start();
}
}

运行结果:
a
print b()
b
b
以上结果,先输出a,过了5s后再输出print b() b,再过了8s输出b,也就是,由于锁的是同一个实例,所以,只有线程1当a方法调用完毕后,线程2才可以获取该实例锁进入b方法。
二、自旋锁
自旋锁,自己在不停的旋转,旋的是CPU的时间片,也就是空转CPU。当另外一个线程没有执行结束时,它一直在自旋等待。它会一直等待另外的线程执行完毕。

package com.roocon.thread.t6;
public class Demo2 {
//多个线程执行完毕后,输出,全部执行完毕
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕了");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕了");
}
}).start();
System.out.println("全部执行完毕");
}
}

运行结果:
全部执行完毕
Thread-0开始执行...
Thread-1开始执行...
Thread-1执行完毕了
Thread-0执行完毕了
以上结果明显,主线程执行结束后,其他线程还在继续执行。那么,怎么解决这个问题呢?
加入条件判断,如果最后只剩下主线程了,则打印。

package com.roocon.thread.t6;
public class Demo2 {
//多个线程执行完毕后,输出,全部执行完毕
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕了");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕了");
}
}).start();
if (Thread.activeCount()==1) {
System.out.println("全部执行完毕");
}
}
}

运行结果:
Thread-0开始执行...
Thread-1开始执行...
Thread-0执行完毕了
Thread-1执行完毕了
为什么不输出“全部执行完毕"呢?因为,以上代码是并行执行的,在执行if语句时,Thread.activeCount()根本就不等于1。所以呢,我们让它在不等于1的时候,也就是除了主线程还有别的线程时,让它自旋等待。自旋完毕后,再去执行输出”全部执行完毕“,达到想要的效果。

package com.roocon.thread.t6;
import java.util.Random;
public class Demo2 {
//多个线程执行完毕后,输出,全部执行完毕
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行...");
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕了");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行...");
try {
Thread.sleep(new Random().nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行完毕了");
}
}).start();
while (Thread.activeCount() != 1) {//其实在实际应用中,不能这样去判断线程的个数。全部执行完毕不一定会被正确输出。
//自旋等待
}
System.out.println("全部执行完毕");
}
}

运行结果:
Thread-0开始执行...
Thread-1开始执行...
Thread-1执行完毕了
Thread-0执行完毕了
全部执行完毕
以上代码只能说是模拟自旋等待过程。
三、模拟死锁

package com.roocon.thread.t6;
public class Demo3 {
private Object obj1 = new Object();
private Object obj2 = new Object();
public void a(){
synchronized (obj1){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
System.out.println("a");
}
}
}
public void b(){
synchronized (obj2){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
System.out.println("b");
}
}
}
public static void main(String[] args) {
Demo3 demo3 = new Demo3();
new Thread(new Runnable() {
@Override
public void run() {
demo3.a();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
demo3.b();
}
}).start();
}
}

运行结果:
控制台一直在运行,但是无任何输出。
通过命令检测是否真的发生了死锁:

点击线程,检测死锁:

参考资料:
《java并发编程与实战》龙果学院
Java并发编程原理与实战十一:锁重入&自旋锁&死锁的更多相关文章
- Java并发编程原理与实战五:创建线程的多种方式
一.继承Thread类 public class Demo1 extends Thread { public Demo1(String name) { super(name); } @Override ...
- Java并发编程原理与实战四十二:锁与volatile的内存语义
锁与volatile的内存语义 1.锁的内存语义 2.volatile内存语义 3.synchronized内存语义 4.Lock与synchronized的区别 5.ReentrantLock源码实 ...
- Java并发编程原理与实战三十一:Future&FutureTask 浅析
一.Futrue模式有什么用?------>正所谓技术来源与生活,这里举个栗子.在家里,我们都有煮菜的经验.(如果没有的话,你们还怎样来泡女朋友呢?你懂得).现在女票要你煮四菜一汤,这汤是鸡汤, ...
- Java并发编程原理与实战二十一:线程通信wait¬ify&join
wait和notify wait和notify可以实现线程之间的通信,当一个线程执行不满足条件时可以调用wait方法将线程置为等待状态,当另一个线程执行到等待线程可以执行的条件时,调用notify可以 ...
- Java并发编程原理与实战十五:手动实现一个可重入锁
package com.roocon.thread.ta1; public class Sequence { private MyLock lock = new MyLock(); private ...
- Java并发编程原理与实战十:单例问题与线程安全性深入解析
单例模式我想这个设计模式大家都很熟悉,如果不熟悉的可以看我写的设计模式系列然后再来看本文.单例模式通常可以分为:饿汉式和懒汉式,那么分别和线程安全是否有关呢? 一.饿汉式 先看代码: package ...
- Java并发编程原理与实战九:synchronized的原理与使用
一.理论层面 内置锁与互斥锁 修饰普通方法.修饰静态方法.修饰代码块 package com.roocon.thread.t3; public class Sequence { private sta ...
- Java并发编程原理与实战三十三:同步容器与并发容器
1.什么叫容器? ----->数组,对象,集合等等都是容器. 2.什么叫同步容器? ----->Vector,ArrayList,HashMap等等. 3.在多线程环境下,为什么不 ...
- Java并发编程原理与实战二十五:ThreadLocal线程局部变量的使用和原理
1.什么是ThreadLocal ThreadLocal顾名思义是线程局部变量.这种变量和普通的变量不同,这种变量在每个线程中通过get和set方法访问, 每个线程有自己独立的变量副本.线程局部变量不 ...
随机推荐
- Software Defined Networking(Week 3, part 1)
Control and Data Plane Seperation 课程地址 Overview 今天正式地学习一个控制与数据平面分离.学习完本节,应该要掌握何为控制和数据平面,了解它们的功能以及好处, ...
- poi excel导入 数字自动加小数点
问题:导入excel表,若表格中为整数数字,不管单元格设置成数字格式还是文本格式,导入时都会出现小数点和0. 我遇到的问题是:一个名称,做测试数据的时候做了纯整形数字,发现了这个问题. 解决办法:在代 ...
- display:flex 布局教程,弹性布局!
display:flex 布局教程 布局的传统解决方案,基于盒状模型,依赖 display属性 + position属性 + float属性.它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现. ...
- 项目上线,php的错误信息必须不让其在页面中显示给客户,
对于PHP开发者来 说,一旦某个产品投入使用,应该立即将 display_errors选项关闭,以免因为这些错误所透露的路径.数据库连接.数据表等信息而遭到黑客攻击.但是,任何一个产品在投入使用后,都 ...
- Thinkphp面试问题
1.如何理解TP中的单一入口文件? 答:ThinkPHP采用单一入口模式进行项目部署和访问,无论完成什么功能,一个项目都有一个统一(但不一定是唯一)的入口.应该说,所有项目都是从入口文件开始的,并且所 ...
- 对final和static的理解
一.final (一).final的使用 final关键字可以用来修饰类.方法和变量(包括成员变量和局部变量) 1. 当用final修饰一个类时,表明这个类不能被继承.2. 当用final修饰一个方法 ...
- Qt托盘程序
使用QSystemTrayIcon类可以实现托盘程序.在这里使用QMainWindow做实例: mainwindow.h头文件 #ifndef MAINWINDOW_H #define MAINWIN ...
- P4867 Gty的二逼妹子序列
题目描述 Autumn和Bakser又在研究Gty的妹子序列了!但他们遇到了一个难题. 对于一段妹子们,他们想让你帮忙求出这之内美丽度∈[a,b]的妹子的美丽度的种类数. 为了方便,我们规定妹子们的美 ...
- Day24-图片验证码----待续
一,上课所讲---------没听懂!!! 1,首先,图片验证码不能放到客户浏览器的cookie上,否则,客户可以直接从它的浏览器上拿到,然后自动提交,这样的话,验证码就没有任何用处了.所以图片验证码 ...
- Error:Artifact 'xx.war exploded' has invalid extension
环境信息: IDEA 13 , MAVEN, JBOSS 7. 配置信息: 常规配置. 出错信息: Error:Artifact 'xx.war exploded' has invalid ext ...