Java 线程的同步与死锁
1、线程的同步产生的原因
没有同步的情况
class MyThread1 implements Runnable
{
private int ticket=5;
@Override
public void run() {
for(int x =0;x<20;x++)
{
if(ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票:"+this.ticket--);
}
}
}
}
public class Test {
public static void main(String[] args) {
MyThread1 mt = new MyThread1();
new Thread(mt,"票贩子A").start();
new Thread(mt,"票贩子B").start();
new Thread(mt,"票贩子C").start();
new Thread(mt,"票贩子D").start();
}
}
票贩子C卖票:5
票贩子D卖票:5
票贩子B卖票:4
票贩子A卖票:3
票贩子D卖票:2
票贩子C卖票:1
票贩子A卖票:-1
票贩子B卖票:0
票贩子D卖票:-2
可以看到又相同的票被卖了,还出现了负数票的情况。
2、线程的同步处理操作
实现同步的关键字synchronized,可以通过两种方式使用
- 一种是同步代码块
- 另外一种是同步方法
在Java里面有四种代码块:普通代码块、构造块、静态块、同步块
使用同步代码块实现同步
class MyThread1 implements Runnable
{
private int ticket=10;
@Override
public void run() {
for(int x =0;x<20;x++)
{
synchronized(this){
if(ticket>0) {
System.out.println(Thread.currentThread().getName()+"卖票:"+this.ticket--);
}
}
}
}
}
public class SynchronizedThread {
public static void main(String[] args) {
MyThread1 mt = new MyThread1();
new Thread(mt,"票贩子A").start();
new Thread(mt,"票贩子B").start();
new Thread(mt,"票贩子C").start();
new Thread(mt,"票贩子D").start();
}
}
票贩子A卖票:10
票贩子D卖票:9
票贩子C卖票:8
票贩子B卖票:7
票贩子C卖票:6
票贩子C卖票:5
票贩子C卖票:4
票贩子D卖票:3
票贩子A卖票:2
票贩子D卖票:1
调用同步方法实现同步
class MyThread1 implements Runnable
{
private int ticket=10;
@Override
public void run() {
for(int x =0;x<20;x++)
{
this.sale();
}
}
public synchronized void sale()
{
if(ticket>0) {
System.out.println(Thread.currentThread().getName()+"卖票:"+this.ticket--);
}
}
}
public class SynchronizedThread {
public static void main(String[] args) {
MyThread1 mt = new MyThread1();
new Thread(mt,"票贩子A").start();
new Thread(mt,"票贩子B").start();
new Thread(mt,"票贩子C").start();
new Thread(mt,"票贩子D").start();
}
}
3、线程的死锁情况
public class DeadLock {
public static void main(String[] args) {
DeadService deadService = new DeadService();
new Thread(new DeadRunnable(deadService),"a").start();
new Thread(new DeadRunnable(deadService),"b").start();
}
}
class DeadRunnable implements Runnable {
DeadService deadService;
public DeadRunnable(DeadService deadService) {
this.deadService = deadService;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
if ("a".equals(name)) {
deadService.methodA();
} else if ("b".equals(name)) {
deadService.methodB();
}
}
}
class DeadService {
final Object objectA = new Object();
final Object objectB = new Object();
/**
* 互斥条件:objectA、objectB对象只能同一时间被一个方法锁住
* 请求与保持条件:methodA()方法锁objectA对象的时候未释放又申请锁objectB对象,methodB()方法锁objectB对象的时候未释放又申请锁objectA对象
* 不可抢占条件: 锁objectA,锁objectB未释放前不能再被其他方法剥夺锁
* 循环等待条件:methodA()等待objectB对象锁的释放、methodB()等待objectA对象锁的释放
*/
public void methodA() {
synchronized (objectA) {
System.out.println("methodA() start.." + Thread.currentThread().getName());
ThreadUtil.sleep(3000);
synchronized (objectB) {
System.out.println("methodA() end.." + Thread.currentThread().getName());
}
}
}
public void methodB() {
synchronized (objectB) {
System.out.println("methodB() start.." + Thread.currentThread().getName());
ThreadUtil.sleep(3000);
synchronized (objectA) {
System.out.println("methodB() end.." + Thread.currentThread().getName());
}
}
}
}
运行结果:
methodA() start..a
methodB() start..b
发现程序卡在这里,系统出现死锁。
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
java 死锁产生的四个必要条件:
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
解决方法:
- 用信号量去控制死锁。
- 调整申请锁的范围
- 调整申请锁的顺序
避免死锁:银行家算法。
排查死锁的方式:
- Java 进程的信息
E:\code\IdeaProjects\learn-java>jps
8096 Jps
13060
18868 DeadLock
17500 Launcher
jps
是 Java Virtual Machine Process Status Tool(Java 虚拟机进程状态工具)的缩写。它是 JDK 提供的一个命令行工具,用于列出当前系统上正在运行的 Java 进程的信息,包括 Java 进程的进程 ID(PID)和主类的名称
- 打印死锁进程的堆栈信息
E:\code\IdeaProjects\learn-java>jstack -l 18868
……
……
……
Java stack information for the threads listed above:
===================================================
"a":
at com.zjw._02.synstatic.DeadService.methodA(DeadLock.java:71)
- waiting to lock <0x0000000622ed3648> (a java.lang.Object)
- locked <0x0000000622ed3638> (a java.lang.Object)
at com.zjw._02.synstatic.DeadRunnable.run(DeadLock.java:48)
at java.lang.Thread.run(java.base@17.0.10/Thread.java:840)
at java.lang.Thread.run(java.base@17.0.10/Thread.java:840)
"b":
at com.zjw._02.synstatic.DeadService.methodB(DeadLock.java:81)
- waiting to lock <0x0000000622ed3638> (a java.lang.Object)
- locked <0x0000000622ed3648> (a java.lang.Object)
at com.zjw._02.synstatic.DeadRunnable.run(DeadLock.java:50)
at java.lang.Thread.run(java.base@17.0.10/Thread.java:840)
Found 1 deadlock.
通过堆栈信息我们可以查看死锁发生的位置。
请解释多个线程访问统一资源时需要考虑哪些情况?有可能带来哪些后果?
多个线程访问同一资源时要考虑到线程间的同步问题,可以使用同步代码或同步方法解决;
同步代码块:synchronized(锁定对象){代码}
同步方法: public synchronized 返回值 方法名称(){代码}
但是过多的使用同步,有可能造成死锁。
---------------
我每一次回头,都感觉自己不够努力,所以我不再回头。
---------------