005-五、网络编程之线程同步&死锁详解
引言
我们再谈 synchronied
同步,现在我们都知道了 synchronied
是同步代码了,它默认是的对象锁是 this
,往往效率都不高,因为每次执行到 synchronized
代码块时,其它的 synchronized
代码块都会被阻塞,那么怎么能提高效率呢?当然能实现同步就可以实现异步了。
synchronized
实现异步处理
前面一直使用的 synchronied(this)
锁,但是这样会有让效率很低,因为当线程运行到这个对象锁,就会阻塞这个对象下的其它所有的锁了,所以这样将会让运行效率大大降低,可以弄一个异步处理一段代码块,处理方法就是不要使用对象监视器(this
)了,可以在同步代码块的前面定义一个临时对象Object obj = new Object();
然后使用synchronied(obj)
,那么这样就是锁的对象监视器不同了,也就达到异步的效果了。这样效率也大大提高了。
看个例子。
public class TestThread6 {
public static void main(String[] args) throws InterruptedException {
ThreadEntity threadEntity = new ThreadEntity();
Runnable myRunnable1 = new MyRunnable(threadEntity,"小明","男");
Thread thread1 = new Thread(myRunnable1);
thread1.setName("A");
Runnable myRunnable2 = new MyRunnable(threadEntity,"小花","女");
Thread thread2 = new Thread(myRunnable2);
thread2.setName("B");
thread1.start();
thread2.start();
}
}
class MyRunnable implements Runnable {
private ThreadEntity threadEntity;
private String name;
private String sex;
public MyRunnable(ThreadEntity threadEntity,String name,String sex) {
this.threadEntity = threadEntity;
this.name = name;
this.sex = sex;
}
@Override
public void run() {
this.threadEntity.show(this.name,this.sex);
}
}
class ThreadEntity {
/**
* 大家都知道局部变量是不会出现脏读的
* @param name
* @param sex
*/
public void show(String name,String sex) {
try {
/**
* 实现异步处理就需要同步代码块使用各自的对象锁就可以了
*/
Object obj = new Object();
synchronized (obj) {
System.out.println(Thread.currentThread().getName()+",开始时间"+System.currentTimeMillis() +
",name=" + name + ",sex=" + sex);
//为了演示效果这里休眠2秒
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+",结束时间"+System.currentTimeMillis() +
",name=" + name + ",sex=" + sex);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//结果:
A,开始时间1524296126538,name=小明,sex=男
B,开始时间1524296126549,name=小花,sex=女
A,结束时间1524296128715,name=小明,sex=男
B,结束时间1524296128725,name=小花,sex=女
静态方法的synchronied使用
synchronied
加到非静态方法上使用和静态方法上的使用方式类似,效果也是一样的,但是本质上是不同的,因为 synchronied
加到 static
方法上是给 Class
类上加锁了,而非静态方法上加锁是给对象加锁,
下面用一个小例子验证一下非静态方法和静态方法使用锁,并非是同一个锁 。为了看出效果,我们需要使用2个静态方法和一个非静态方法,使用3个线程来执行。
public class TestThread601 {
public static void main(String[] args) throws InterruptedException {
//共享类
ThreadEntity601 threadEntity601 = new ThreadEntity601();
//执行第一个静态方法的线程
Runnable myRunnable1 = new MyRunnable601(threadEntity601,"printA");
Thread thread1 = new Thread(myRunnable1);
thread1.setName("A...");
//执行第二个静态方法的线程
Runnable myRunnable2 = new MyRunnable601(threadEntity601,"printB");
Thread thread2 = new Thread(myRunnable2);
thread2.setName("B...");
//执行第三个非静态方法的线程
Runnable myRunnable3 = new MyRunnable601(threadEntity601,"printC");
Thread thread3 = new Thread(myRunnable3);
thread3.setName("C...");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyRunnable601 implements Runnable {
private String flag;
private ThreadEntity601 threadEntity601;
public MyRunnable601(ThreadEntity601 threadEntity601,String flag) {
this.flag = flag;
this.threadEntity601 = threadEntity601;
}
@Override
public void run() {
if (this.flag == "printA") {
this.threadEntity601.printA();
} else if (this.flag == "printB") {
this.threadEntity601.printB();
} else {
this.threadEntity601.printC();
}
}
}
class ThreadEntity601 {
//第一个静态方法synchronized
public synchronized static void printA() {
try {
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在 " + System.currentTimeMillis() + "进入printAA");
//为了演示效果在此处加一个休眠3秒钟
Thread.sleep(3000);
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ " 在 " + System.currentTimeMillis() + "离开printA");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//第二个静态方法synchronized
public synchronized static void printB() {
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
+ System.currentTimeMillis() + "进入printB");
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
+ System.currentTimeMillis() + "离开printB");
}
//第三个非静态方法synchronized
public synchronized void printC() {
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
+ System.currentTimeMillis() + "进入printC");
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
+ System.currentTimeMillis() + "离开printC");
}
}
//结果显示效果和异步很像
线程名称为:C...在1524303493953进入printC
线程名称为:A...在 1524303493950进入printAA
线程名称为:C...在1524303494083离开printC
线程名称为:A... 在 1524303497087离开printA
线程名称为:B...在1524303497089进入printB
线程名称为:B...在1524303497091离开printB
以上结果显示成异步的效果的原因,是它们持有的不是同一把锁,printC持有的是一把对象锁,printA,printB持有的是同一把锁(Class锁),Class锁可以对该类的所有Class锁都有效。
synchronied(Class)使用
synchronied(Class)
代码块锁也是 Class
锁。它与 synchronied static
方法的作用一致,也是对整个类加锁。
改造刚刚那个printA,printB,printC
,方法。
//第三个非静态方法synchronized
public static void printC() {
synchronized (ThreadEntity601.class) {
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
+ System.currentTimeMillis() + "进入printC");
System.out.println("线程名称为:" + Thread.currentThread().getName() + "在"
+ System.currentTimeMillis() + "离开printC");
}
}
结果:
线程名称为:A...在 1524305002343进入printAA
线程名称为:A... 在 1524305005480离开printA
线程名称为:C...在1524305005483进入printC
线程名称为:C...在1524305005484离开printC
线程名称为:B...在1524305005486进入printB
线程名称为:B...在1524305005488离开printB
这样就完全实现同步了。
同步synchronied方法无限等待与解决
创建一个 ThreadEntity601
类,类中定一个定义2个方法,2个printA,printB
同步方法,在 printA
方法中定一个死循环,printB
中打印2句话。然后创建2个线程执行不同的方法。先启动调用 printA
方法的线程,那么就会形成无线等待了,看代码!
public class TestThread601 {
public static void main(String[] args) throws InterruptedException {
//共享类
ThreadEntity601 threadEntity601 = new ThreadEntity601();
//执行第一个静态方法的线程
Runnable myRunnable1 = new MyRunnable601(threadEntity601,"printA");
Thread thread1 = new Thread(myRunnable1);
thread1.setName("A...");
//执行第二个静态方法的线程
Runnable myRunnable2 = new MyRunnable601(threadEntity601,"printB");
Thread thread2 = new Thread(myRunnable2);
thread2.setName("B...");
thread1.start();
thread2.start();
}
}
class MyRunnable601 implements Runnable {
private String flag;
private ThreadEntity601 threadEntity601;
public MyRunnable601(ThreadEntity601 threadEntity601,String flag) {
this.flag = flag;
this.threadEntity601 = threadEntity601;
}
@Override
public void run() {
if (this.flag == "printA") {
try {
this.threadEntity601.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else if (this.flag == "printB") {
this.threadEntity601.printB();
}
}
}
class ThreadEntity601 {
public synchronized void printA() throws InterruptedException {
System.out.println("线程名称为:" + Thread.currentThread().getName() + "进入printA");
boolean flag = true;
while (flag) {
//这里休眠一下让打印执行减慢
Thread.sleep(2000);
System.out.println("死循环中。。。");
}
System.out.println("线程名称为:" + Thread.currentThread().getName() + "离开printA");
}
public synchronized void printB() {
System.out.println("线程名称为:" + Thread.currentThread().getName() + "进入printB");
System.out.println("线程名称为:" + Thread.currentThread().getName() + "离开printB");
}
}
结果:
线程名称为:A...进入printA
死循环中。。。
死循环中。。。
死循环中。。。
死循环中。。。
这种情况下,A线程一直在执行,B线程无法拿到锁就一直,这样就锁死了,现在为了解决这个问题,可以使用同步代码块synchronied()
来解决。
改造printB方法。
public void printB() {
Object object = new Object();
synchronized (object) {
System.out.println("线程名称为:" + Thread.currentThread().getName() + "进入printB");
System.out.println("线程名称为:" + Thread.currentThread().getName() + "离开printB");
}
}
线程名称为:B...进入printB
线程名称为:A...进入printA
线程名称为:B...离开printB
````
死循环中。。。
看看上面的结果是使用了不同的对象锁解决了这个问题。`printB`方法都打印出来了。
## 多线程死锁
在开发中有当一个线程永远地持有一个锁,并且其它线程都尝试去获得这个锁时,那么它们将永远被阻塞,这个我们都知道。如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得锁L,那么这两个线程将永远等待下去,这种情况就是最简单的死锁形式。
当一组`Java`线程发生死锁时,这两个线程就永远不能再使用了,并且由于两个线程分别持有了两个锁,那么这两段同步代码/代码块也无法再运行了----除非终止并重启应用。
不过死锁造成的影响很少会立即显现出来,一个类可能发生死锁,并不意味着每次都会发生死锁,这只是表示有可能。当死锁出现时,往往是在最糟糕的情况----高负载的情况下。
比如2个人一起吃饭但是只有一双筷子,2人轮流吃(同时拥有2只筷子才能吃)。某一个时候,一个拿了左筷子,一人拿了右筷子,2个人都同时占用一个资源,等待另一个资源,这个时候甲在等待乙吃完并释放它占有的筷子,同理,乙也在等待甲吃完并释放它占有的筷子,这样就陷入了一个死循环,谁也无法继续吃饭。。。
再比如:某计算机系统中只有一台打印机和一台输入设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程`P2` 所占用,而P2在未释放打印机之前,又提出请求使用正被`P1`占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。
写一个简单的例子吧。
```java
ublic class TestThread601 {
public static void main(String[] args) throws InterruptedException {
//共享类
ThreadEntity601 threadEntity601 = new ThreadEntity601();
//执行第一个静态方法的线程
Runnable myRunnable1 = new MyRunnable601(threadEntity601,"method1");
Thread thread1 = new Thread(myRunnable1);
//执行第二个静态方法的线程
Runnable myRunnable2 = new MyRunnable601(threadEntity601,"method2");
Thread thread2 = new Thread(myRunnable2);
thread1.start();
thread2.start();
}
}
class MyRunnable601 implements Runnable {
private String flag;
private ThreadEntity601 threadEntity601;
public MyRunnable601(ThreadEntity601 threadEntity601,String flag) {
this.flag = flag;
this.threadEntity601 = threadEntity601;
}
@Override
public void run() {
if (this.flag == "method1") {
try {
this.threadEntity601.method1();
} catch (Exception e) {
e.printStackTrace();
}
} else if (this.flag == "method2") {
try {
this.threadEntity601.method2();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class ThreadEntity601 {
private final Object lock1 = new Object() ;
private final Object lock2 = new Object() ;
public void method1() throws Exception {
synchronized (lock1) {
System.out.println("mothod1执行了lock1");
//为了演示效果明显这里休眠2秒
Thread.sleep(2000) ;
//这里拿到了method2方法中的lock2对象锁
synchronized (lock2) {
System.out.println("mothod1执行了lock2") ;
}
}
}
public void method2() throws Exception {
synchronized (lock2) {
System.out.println("mothod2执行了lock2");
//为了演示效果明显这里休眠2秒
Thread.sleep(2000) ;
//这里拿到了method1方法中的lock1对象锁
synchronized (lock1) {
System.out.println("mothod2执行了lock1") ;
}
}
}
}
写完了如果写得有什么问题,希望读者能够给小编留言,也可以点击[此处扫下面二维码关注微信公众号](https://www.ycbbs.vip/?p=28 "此处扫下面二维码关注微信公众号")
看完两件小事
如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:
- 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
- 关注公众号 「方志朋」,公众号后台回复「666」 免费领取我精心整理的进阶资源教程
本文著作权归作者所有,如若转载,请注明出处
转载请注明:文章转载自「 Java极客技术学习 」https://www.javajike.com