线程同步死锁与生产者消费者
Posted wx62b6dba7e04cf
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程同步死锁与生产者消费者相关的知识,希望对你有一定的参考价值。
一、问题引出
多个线程访问同一个资源时,如果操作不当就很容易产生意想不到的错误,比如常见的抢票程序:
public class Demo1
public static void main(String[] args)
Ticket tt = new Ticket();
new Thread(tt, "甲").start();
new Thread(tt, "乙").start();
class Ticket implements Runnable
private int ticketCount = 10;
public void run()
while (ticketCount > 0)
try
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + "抢到了第【" + (10 - ticketCount + 1) + "】张火车票");
ticketCount--;
结果:
乙抢到了第【1】张火车票
甲抢到了第【2】张火车票
乙抢到了第【3】张火车票
甲抢到了第【4】张火车票
乙抢到了第【5】张火车票
甲抢到了第【6】张火车票
乙抢到了第【7】张火车票
甲抢到了第【8】张火车票
乙抢到了第【9】张火车票
甲抢到了第【9】张火车票
乙抢到了第【11】张火车票
以上代码可以看出,第9张票被两个人抢,且出现了第11张票,这不符合实际。为什么会出现这种现象?原因是当乙抢到第9张票但还未执行ticketCount–;语句时,甲也进入了run()方法,此时票数仍然是第9张票,也打印出了第9票,因此第9张票被抢了两遍。之后甲和乙都会执行ticketCount–;语句,当ticketCount =0时,无论哪个线程抢到资源都会打印抢到了第【11】张火车票,当ticketCount = -1时,停止。
综上分析,问题出现的主要原因就是甲、乙两个线程同时访问同一资源,造成了资源污染。
二、线程同步
上面可知当多个线程同时访问同一资源,就可能会造成了资源污染,解决这个问题的方法就是在某个线程访问资源时,其他线程在资源或者方法外面等待,也就是线程同步,或者叫加锁。
线程同步就是指多个操作在同一时间段内只能有一个线程进行,而其他线程要等待此线程完成之后才可以继续进行。
要实现线程同步,需要通过关键字synchronized关键字,利用这个关键字可以定义同步方法或者代码块。格式如下:
synchronized(同步对象)
操作;
一般要进行同步对象处理时,采用当前对象this进行同步。上面的抢票代码加上同步后如下:
public class Demo1
public static void main(String[] args)
Ticket tt = new Ticket();
new Thread(tt, "甲").start();
new Thread(tt, "乙").start();
class Ticket implements Runnable
private int ticketCount = 10;
public void run()
synchronized(this)
while (ticketCount > 0)
try
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + "抢到了第【" + (10 - ticketCount + 1) + "】张火车票");
ticketCount--;
结果:
甲抢到了第【1】张火车票
甲抢到了第【2】张火车票
甲抢到了第【3】张火车票
甲抢到了第【4】张火车票
甲抢到了第【5】张火车票
甲抢到了第【6】张火车票
甲抢到了第【7】张火车票
甲抢到了第【8】张火车票
甲抢到了第【9】张火车票
甲抢到了第【10】张火车票
加锁或者同步处理以后,虽然多线程同时访问同一资源的问题虽然解决了,但是线程同步会降低整体性能。
三、死锁
死锁就是多个线程间相互等待的状态。
class MyThread implements Runnable
int flag = 1;
// 必须是静态资源
static Object o1 = new Object();
static Object o2 = new Object();
public void run()
System.out.println("flag= " + flag);
if(flag == 1)
synchronized (o1)
System.out.println(Thread.currentThread().getName() + "我抢到了o1,还需要o2");
try
Thread.sleep(500);
catch (InterruptedException e)
e.printStackTrace();
synchronized (o2)
System.out.println("111");
if(flag == 0)
synchronized (o2)
System.out.println(Thread.currentThread().getName() + "我抢到了o2,还需要o1");
try
Thread.sleep(500);
catch (InterruptedException e)
e.printStackTrace();
synchronized (o1)
System.out.println("222");
public class DeadLock
public static void main(String[] args)
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
// 设置线程1先抢占o1
myThread1.flag = 1;
// 设置线程1先抢占o2
myThread2.flag = 0;
Thread t1 = new Thread(myThread1);
Thread t2 = new Thread(myThread2);
t1.start();
t2.start();
有两个线程,都需要锁住同样的2个对象(a、b)才能完成操作,其中线程1已经锁住了a对象,线程2锁住了b对象,两个线程都不释放锁就会造成死锁。程序无法停止。
需要注意的是 o1
和 o2
必须是static的,若不是static则不是共享变量,而是线程各自拥有,则不会有死锁问题。
3.1 死锁检测
一旦出现死锁,业务是可感知的,因为不能继续提供服务了,那么只能通过dump线程查看到底是哪个线程出现了问题。
① Jstack命令
jstack是java虚拟机自带的一种堆栈跟踪工具。
jstack用于打印出Java堆栈信息,生成java虚拟机当前时刻的线程快照。
线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁
、死循环
、请求外部资源导致的长时间等待
等。
线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
首先,我们通过jps确定当前执行任务的进程号:
jonny@~$ jps
597
1370 JConsole
1362 AppMain
1421 Jps
1361 Launcher
jonny@~$ jstack -F 1362
Attaching to process ID 1362, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 23.21-b01
Deadlock Detection:
Found one Java-level deadlock:
==================linuxbingc(多线程)java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)