多线程同步与死锁深入分析
前言
在多线程开发中,同步与死锁是非常重要的,在本篇文章中,读者将会明白一下几点:
1、哪里需要同步
2、如何实现同步
3、以及实现同步之后会有哪些副作用
例子
问题的引出
以买火车票为例,如果现在要是想买火车票的话可以去火车站买或者各个售票点,但是不管多少个地方可以买火车票,最终一趟列车的车票数是固定的,如果把各个售票点理解为各个线程的话,则所有线程应该共同拥有同一份的票数。
package com.ywx;
class MyThread implements Runnable{
private int ticket = 10;
public void run() {
for(int i=0;i<100;i++){
if(ticket>0){
System.out.println("卖票:ticket="+ticket--);
}
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
Thread thread1 = new Thread(mt1);
Thread thread2 = new Thread(mt1);
thread1.start();
thread2.start();
}
}
卖票:ticket=10
卖票:ticket=9
卖票:ticket=8
卖票:ticket=7
卖票:ticket=6
卖票:ticket=5
卖票:ticket=4
卖票:ticket=3
卖票:ticket=1
卖票:ticket=2
结果分析:从运行结果来看,售票数确实达到了共享,但是因为延迟的原因,先后反馈的信息明显对应不上。
问题的解决
如果想要解决这样的问题,就必须使用同步,所谓同步就是指多个线程操作在同一个时间段内只能有一个线程进行,其他线程就要等待此线程完成之后才可以继续执行。
同步效果工作效果如下图:
使用同步问题
要想解决资源共享的同步操作问题,可以使用同步代码块及同步方法两种方式完成。
1、同步代码块(同步代码块分为四种):在代码块上加上"synchronized"关键字,则此代码块就称为同步代码块。
同步的时候必须指明同步的对象,一般情况下会将当前对象作为同步的对象,使用this表示。
package com.ywx;
class MyThread implements Runnable{
private int ticket = 10;
public void run() {
for(int i=0;i<100;i++){
synchronized (this) {//对当前对象进行同步
if(ticket>0){
System.out.println("卖票:ticket="+ticket--);
}
}
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
Thread thread1 = new Thread(mt1);
Thread thread2 = new Thread(mt1);
thread1.start();
thread2.start();
}
}
运行结果:
卖票:ticket=10
卖票:ticket=9
卖票:ticket=8
卖票:ticket=7
卖票:ticket=6
卖票:ticket=5
卖票:ticket=4
卖票:ticket=3
卖票:ticket=2
卖票:ticket=1
卖票:ticket=9
卖票:ticket=8
卖票:ticket=7
卖票:ticket=6
卖票:ticket=5
卖票:ticket=4
卖票:ticket=3
卖票:ticket=2
卖票:ticket=1
结果分析:从运行结果可以发现,程序加入同步操作后,所以不会发生卖票先后顺序对应不上的问题了。但是问题来了,这样程序的运行效率就降低了。
出了使用同步代码块解决问题,还可以使用同步方法。
同步方法:
package com.ywx;
class MyThread implements Runnable{
private int ticket = 10;
public void run() {
for(int i=0;i<100;i++){
this.sale();
}
}
//同步方法
public synchronized void sale(){
if(ticket>0){
System.out.println("卖票:ticket="+ticket--);
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
Thread thread1 = new Thread(mt1);
Thread thread2 = new Thread(mt1);
thread1.start();
thread2.start();
}
}
运行效果和同步代码块一样:
卖票:ticket=10
卖票:ticket=9
卖票:ticket=8
卖票:ticket=7
卖票:ticket=6
卖票:ticket=5
卖票:ticket=4
卖票:ticket=3
卖票:ticket=2
卖票:ticket=1
卖票:ticket=9
卖票:ticket=8
卖票:ticket=7
卖票:ticket=6
卖票:ticket=5
卖票:ticket=4
卖票:ticket=3
卖票:ticket=2
卖票:ticket=1
死锁
1、资源共享时需要进行同步操作。
2、程序中过多的同步会产生死锁。
解释:死锁一般情况下就是表示在互相等待,是在程序运行时出现的一种问题,下面通过一个代码来模拟一个死锁的概念。
package com.ywx;
class Zhangsan{
public void say(){
System.out.println("张三对李四说:“你把钱给我,我就把冰淇淋给你!”");
}
public void get(){
System.out.println("张三拿到了3块钱!");
}
}
class Lisi{
public void say(){
System.out.println("李四对张三说:“你把冰淇淋给我,我就把钱给你!");
}
public void get(){
System.out.println("李四拿到了冰淇淋!");
}
}
class MyThread implements Runnable{
private static Zhangsan zs = new Zhangsan();
private static Lisi ls = new Lisi();
public boolean flag = false;
public void run() {
if(flag){
synchronized (zs) {
zs.say();
synchronized (ls) {
zs.get();
}
}
}else{
synchronized (ls) {
ls.say();
synchronized (zs) {
ls.get();
}
}
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
mt1.flag = true;
mt2.flag = false;
Thread thread1 = new Thread(mt1);
Thread thread2 = new Thread(mt2);
thread1.start();
thread2.start();
}
}
运行结果2:
结果分析:结果1中程序出现了相互等待状态,结果2程序可以正常运行。但是不管如何,过多的使用同步操作则会出现以上死锁状态。所以在开发中,避免过多的使用同步操作。