Java 线程的同步与死锁

概念: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、线程的死锁情况

Java 实例 - 死锁及解决方法

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的资源。这样就形成了一个等待环路。

解决方法:

  • 用信号量去控制死锁。
  • 调整申请锁的范围
  • 调整申请锁的顺序

避免死锁:银行家算法。

排查死锁的方式:

  1. 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)和主类的名称

  1. 打印死锁进程的堆栈信息
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 返回值 方法名称(){代码}
但是过多的使用同步,有可能造成死锁。

posted @ 2020-09-11 08:23  雨中遐想  阅读(145)  评论(0编辑  收藏  举报