day14多线程02 有关死锁的java实现

day14 多线程02

1. lock锁

java.util.concurrent(多线程的使用)

      Lock锁对象 VS synchoronized 锁对象
区别
    1.  synchronized 锁对象,只提供了用来模拟锁状态的标志位(加锁和释放锁),
       但是加锁和释放锁的行为,都是由jvm隐式完成(和synchronized 锁对象没关系),
       所以synchronized 锁对象不是一把完整的锁

    2.一个Lock对象,就代表一把锁,而且还是一把完整的锁,(模拟了对象和行为)
      Lock对象,它如果要实现加锁和释放锁,不需要synchronized关键字配合,它自己就可以完成
      Lock(接口): lock() 加锁
                  unlock 释放锁

    3. 两种锁对象,实现完全不同的
联系: 都可以实现线程同步
     1.  synchronized(锁对象) {
           需要同步的代码
         }

     2.  lock.lock()
         需要同步的代码
         lock.unlock()

  Lock锁对象的标准使用
  Lock l = ...;
  l.lock();
  try {
      // access the resource protected by this lock
  } finally {
      l.unlock();
  }

 学习了Lock锁对象之后,我们就有两种方式,构造同步代码块,从而实现线程通过(构造原子操作),
 实际开发的时,使用哪种方式呢? 推荐 synchronized代码块
  1. 两种方式,实现线程同步,效果相同,但是 使用synchronized代码块的方式要简单的多

  2. 虽然说在jdk早期版本中,两种方式加锁和释放锁,确实有效率上的差别,Lock锁机制加锁释放锁效率高一些,但是,在今天的jdk中,两种方式加锁和释放锁的效率已经相差无几了

对电影院售票问题继续改进:

将synchronized代码块替换为lock即可:(修改的部分)

// Lock锁对象(放在SaclesTask对象中)
    private Lock lock = new ReentrantLock();

            //在run()方法中
            // 加锁
            lock.lock();
            try {
                if (tickets > 0) {
                    // double check
                    System.out.println(Thread.currentThread().getName() + ": 售卖除了第" + this.tickets-- + "张票");
                }
            } finally {
                // 释放锁
                lock.unlock();
            }

2.死锁问题

 死锁问题:
    死锁是指两个以上的线程在执行过程中,因为争夺资源而产生的一种相互等待的现象
    同步另一个弊端:如果出现了嵌套锁,可能产生死锁

 什么时候会出现嵌套锁? 如果说,在某些情况下,一个线程需要同时持有多把锁,此时就会出现所谓的嵌套锁
比如: 某个线程要同时持有两把锁lockA 和 lockB两把锁,换个说法,
      该线程,成功持有lockA锁的情况下,在持有lockB锁

         synchronized (lockA) {
               //当某线程的代码,执行到这里
               synchronized (lockB) {
                 // 执行到这里,意味着当前线程在持有lockA锁的情况下,又持有了lockB这把锁,所以此时当前线程就同时持有两把锁
               }
         }

案例:

  1. 假设,在多线程运行环境下,假设我们的程序要访问一个共享变量
  2. 利用共享变量,计算得到一些结果,但是对于一些计算的中间结果,需要利用打印机打印出来
    而打印机,一次只允许一个线程访问(多线程对打印机的访问,还要做一次线程同步)

所以很显然,一个线程要完成上述工作,得获取对共享变量的访问权(对被用来同步共享访问,的那把锁加锁),还要获取,对打印机的访问权(对被用来同步访问,打印机的锁成功加锁)

假设对象共享变量的同步,使用的锁lockA
假设对象访问打印机的同步,使用的锁lockB

实现代码:

public class Demo1 {

    public static Object lockA = new Object();
    public static Object lockB = new Object();

    public static void main(String[] args) {

        ABThread abThread = new ABThread();
        BAThread baThread = new BAThread();

        abThread.start();
        baThread.start();
    }
}

/*
        先访问共享变量,在访问打印机
 */
class ABThread extends Thread {

    @Override
    public void run() {
        synchronized (Demo1.lockA) {
            // 1. 访问共享变量
            System.out.println("ABThread lockA锁加锁成功");

            synchronized (Demo1.lockB) {
                // 2. 根据共享变量的值,完成计算,并将结果发送打印机
                System.out.println("ABThread lockB锁加锁成功");
            }
        }
    }
}

/*
       向获取对打印机的互斥访问权,在访问共享变量,将结果发送打印机
 */
class BAThread extends Thread {

    @Override
    public void run() {
        synchronized (Demo1.lockB) {
          // 先实现对打印机的互斥访问
            System.out.println("BAThread lockB加锁成功");
          synchronized (Demo1.lockA) {
              // 访问共享变量,计算结果,发送打印机
              System.out.println("BAThread lockA加锁成功");
          }

        }
    }
}

运行结果(有可能为):

image-20220115091204897

出现了死锁问题

3.如何解决死锁问题

解决思路一:调整加锁顺序:将两个class的加锁顺序都改为先加锁A,再加锁B

解决思路二:如果加锁顺序不好调整:如果我们能让一个线程一次成功对它需要的锁都加锁成功,即要么对所有需要的锁加锁成功,要么一把锁都不加(将加2把锁变成一组原子操作)

实现代码中,定义一个静态对象并加锁即可:

public static Object allLock = new Object();
@Override
public void run() {
    synchronized (Demo1.allLock) {
        synchronized (Demo1.lockA) {

            System.out.println("ABThread lockA锁加锁成功");
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (Demo1.lockB) {

                System.out.println("ABThread lockB锁加锁成功");
            }
        }
    }

4.生产者消费者问题

先介绍两种方法:wait()和notify()和notifyAll()方法

wait()方法

public final void wait()
阻止自己:
    wait()(jdk中的解释)
    1.在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待
    2.当前线程必须拥有此对象监视器。
    3.该线程发布对此监视器的所有权并等待,
    4.直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来

 1. 阻塞功能:
    当在某线程中,对象上.wait(), 在哪个线程中调用wait(), 导致哪个线程处于阻塞状态
    说法:
    当某线程,因为调用执行某对象的wait(),而处于阻塞状态,我们说,该线程在该对象上阻塞。

2. 唤醒条件
   当某线程,因为某对象A的wait(), 而处于阻塞状态时,如果要唤醒该线程,只能在其他线程中,
   再同一个对象(即对象A)上调用其notify()或notifyAll()

   即在其他线程中,调用notify或notifyAll方法,才能唤醒,在该对象上阻塞的线程

3. 运行条件
      当前线程必须拥有此对象监视器。
      监视器:指synchronized代码块中的锁对象

    即我们只能在,当前线程所持有的synchronized代码块中的,锁对象上调用wait方法,才能正常执行

4. 执行特征
      a.该线程发布(release)对此监视器的所有权
      b.等待(阻塞)

第3,4点的理解(代码):

		Object o = new Object();
        //o.wait();//这样做不会阻塞当前的线程
		//会报错:IllegalMonitorStateException
        synchronized (o) {

            // 会让当前线程放弃锁的持有,即释放这把锁
            o.wait();

        }
        System.out.println("after wait");

notify(),notifyALL()方法

通知别人:
 notify():
   1. 唤醒在此对象监视器上等待的单个线程。
   2. 如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。
   3. 选择是任意性的,并在对实现做出决定时发生。

 notifyAll():
  唤醒在此对象监视器上等待的所有线程

5.实现生产者,消费者问题

版本一:

通过在生产者和消费者进程中做同步,避免死锁的问题:

创建4个类:

  1. 消费者任务类:ConsumerTask
  2. 生产者任务类:ProducerTask
  3. 蒸笼共享区(描述蒸笼中的包子):class Container+class Food
  4. 主线程:创建生产者,消费者类和蒸笼共享区,启动各个线程
/*
        消费者(吃包子的人)任务类
 */
public class ConsumerTask implements Runnable {

  private Container container;

    public ConsumerTask(Container container) {
        this.container = container;
    }

    @Override
    public void run() {

        while (true) {
            synchronized (container) {
                // 1. 判断蒸笼状态
                if (container.isEmpty()) {
                    // 如果蒸笼为空,则阻止自己
                    try {
                        // 在锁对象上调用wait方法
                        // 当前线程会释放,自己持有的锁
                        container.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    // 如果蒸笼不空,拿包子吃,并通知生产者做包子
                    // 吃包子
                    container.eatFood();

                    // 通知别人
                    container.notify();
                }

            }

        }


    }
}
import java.util.Random;

public class ProducerTask implements Runnable {

    private Container container;

    // 模拟菜单
    private Food[] foodBill = {new Food("豆沙包", 1), new Food("粉丝包", 2),
                                new Food("鲜肉包", 3), new Food("狗不理",50)};

    public ProducerTask(Container container) {
        this.container = container;
    }

    @Override
    public void run() {

      while (true) {
          synchronized (container) {
              // 1. 判断蒸笼状态
              if (!container.isEmpty()) {
                  // 如果蒸笼不空,阻止自己
                  try {
                      container.wait();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              } else {

                  // 3. 如果蒸笼为空,做包子放入蒸笼,并通知吃包子的赶紧来吃

                  // 随机选择一种包子,放入蒸笼
                  Random random = new Random();
                  // nextInt(int bound), bound限定随机整数的上界,[0, bound)
                  int foodIndex = random.nextInt(foodBill.length);
                  // 放入包子
                  container.setFood(foodBill[foodIndex]);

                  // 通知别人, 通知吃包子的人赶紧来吃
                  container.notify();

              }
          }
      }

    }
}
/*
      描述蒸笼
 */
public class Container {

    /*
         蒸笼中的包子
     */
    private Food food;

    /*
          让生产者放入包子
     */
    public void setFood(Food food) {
        this.food = food;
        System.out.println(Thread.currentThread().getName() + ": 做了" + food);
    }

    /*
          让消费者拿包子出来吃
     */
    public void eatFood() {
        System.out.println(Thread.currentThread().getName() + ": 吃了" + food);
        // 将包子从蒸笼中拿出来吃掉的行为
        food = null;
    }

    /*
          返回蒸笼的状态,即蒸笼是否为空
     */
    public boolean isEmpty() {
        return  food == null;
    }

}

/*
      描述蒸笼中的包子
 */
class Food {
    private String name;
    private double price;

    public Food(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Container{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}
public class Test {

    public static void main(String[] args) {
        // 创建一个蒸笼对象
        Container container = new Container();

        // 创建任务对象
        ConsumerTask consumerTask = new ConsumerTask(container);
        ProducerTask producerTask = new ProducerTask(container);


        // 运行
        Thread thread1 = new Thread(consumerTask, "吃包子的");
        Thread thread2 = new Thread(producerTask, "做包子的");

        thread1.start();
        thread2.start();

    }
}

版本二:(实现"智能蒸笼")

智能蒸笼:
 1. 线程间同步,因为eat方法运行在消费者线程,setFood运行在生产者线程
    所以对于生产者线程,消费者的线程同步,通过eatFood和setFood实现
    将他们变成同步方法(使用同一个锁对象,即同一个蒸笼对象)

2. 线程间的协作,因为eat方法运行在消费者线程,setFood运行在生产者线程
   所以,我们只需要在eatFood方法和setFood方法体,实现阻止自己,通知别人
   实现线程间的协作了

3. 同时,要实现多个生产者(>=2)和多个消费者(>=2),1个蒸笼共享区不发生死锁的问题(使用notifyAll()方法)

版本2和版本1的代码的区别:

  1. 将蒸笼作为了锁对象
  2. 使用synchronize实现了锁对象中的同步方法
  3. 使用了notifyAll()方法

同样的4个类:

  1. 消费者任务类:ConsumerTask
  2. 生产者任务类:ProducerTask
  3. 蒸笼共享区(描述蒸笼中的包子):class Container+class Food
  4. 主线程:创建生产者,消费者类和蒸笼共享区,启动各个线程
public class ConsumerTask implements Runnable {

    private Container container;

    public ConsumerTask(Container container) {
        this.container = container;
    }

    @Override
    public void run() {
        while (true) {
            // 吃包子
            container.eatFood();
        }
    }
}

生产者任务类:

import java.util.Random;

public class ProducerTask implements Runnable {

    private Container container;

    // 模拟菜单
    private Food[] foodBill = {new Food("豆沙包", 1), new Food("粉丝包", 2),
            new Food("鲜肉包", 3), new Food("狗不理",50)};

    public ProducerTask(Container container) {
        this.container = container;
    }

    @Override
    public void run() {
        while (true) {
            // 做包子放入蒸笼
            // 随机选择一种包子,放入蒸笼
            Random random = new Random();
            // nextInt(int bound), bound限定随机整数的上界,[0, bound)
            int foodIndex = random.nextInt(foodBill.length);
            // 放入包子
            container.setFood(foodBill[foodIndex]);
        }
    }
}

蒸笼共享区(核心!):

public class Container {

    private Food food;
    /*
      让生产者放入包子, 运行在生产者线程中
 */
    public synchronized void setFood(Food food) {
        // 1. 判断蒸笼状态
        if (this.food != null) {
            // 如果有包子,阻止生产者线程
            try {
               wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            // 如果没有包子,才允许将包子放入蒸笼
            System.out.println(Thread.currentThread().getName() + ": 做了" + food);
            this.food = food;

            // 通知别人
            notifyAll();
        }
    }

    /*
          让消费者拿包子出来吃,运行在消费者线程中
     */
    public synchronized void eatFood() {
        if (this.food == null) {
           // 蒸笼为空
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println(Thread.currentThread().getName() + ": 吃了" + food);
            // 将包子从蒸笼中拿出来吃掉的行为
            this.food = null;

            // 通知别人
            notifyAll();//注意这里不是notify();
        }
    }
}

/*
      描述蒸笼中的包子
 */
class Food {
    private String name;
    private double price;

    public Food(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Container{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

Test中的主线程:

public class Test {

    public static void main(String[] args) {

        // 创建一个蒸笼对象
        Container container = new Container();

        // 创建任务对象
        ConsumerTask consumerTask = new ConsumerTask(container);
        ProducerTask producerTask = new ProducerTask(container);


        // 运行
        Thread thread11 = new Thread(consumerTask, "吃包子的1");
        Thread thread12 = new Thread(consumerTask, "吃包子的2");
        Thread thread21 = new Thread(producerTask, "做包子的1");
        Thread thread22 = new Thread(producerTask, "做包子的2");

        thread11.start();
        thread12.start();
        thread21.start();
        thread22.start();
    }
}

6.面试题:聊一聊sleep和wait方法的区别

Thread.sleep  VS Object.wait()
    1. 所属不同:
        a. sleep定义在Thread类,静态方法
        b. wait定义在 Object类中,非静态方法

    2. 唤醒条件不同
        a. sleep: 休眠时间到
        b. wait: 在其他线程中,在同一个锁对象上,调用了notify或notifyAll方法

    3. 使用条件不同:
        a. sleep 没有任何前提条件
        b. wait(), 必须当前线程,持有锁对象,锁对象上调用wait()

    4. 休眠时,对锁对象的持有,不同:(最最核心的区别)
      a. 线程因为sleep方法而处于阻塞状态的时候,在阻塞的时候不会放弃对锁的持有
      b. 但是wait()方法,会在阻塞的时候,放弃锁对象持有

7.java语言中线程的几种状态

image-20220115113855817

几种状态的阐述:

image-20220115114016307

7.线程池和定时任务

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值