线程同步死锁与生产者消费者

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;
@Override
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;
@Override
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();

@Override
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(多线程)

linuxbingc(多线程)

linuxbingc(多线程)

java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)

JavaSE基础(十 一 )--<线程>线程同步,死锁,Lock锁,线程通信,生产消费问题,新增的线程创建方式

Linux系统编程 多线程