第41天学习打卡(死锁 Lock synchronized与Lock的对比 线程协作 使用线程池)

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,可能会发生“死锁”的问题。

死锁避免方法

产生死锁的四个必要条件:

1.互斥条件:一个资源每次只能被一个进程使用。

2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生

 package com.kuang.thread;
 //死锁:多个线程互相抱着对方需要的资源,然后形成僵持
 public class DeadLock {
     public static void main(String[] args) {
         Makeup g1 = new Makeup(0,"灰姑凉");
         Makeup g2 = new Makeup(1,"白雪公主");
 ​
         g1.start();
         g2.start();
    }
 }
 ​
 //口红
 class Lipstick{
 ​
 }
 ​
 //镜子
 class Mirror{
 ​
 }
 ​
 ​
 class Makeup extends Thread {
 ​
     //需要的资源只有一份,用static来保证只有一份
     static Lipstick lipstick = new Lipstick();
     static Mirror mirror = new Mirror();
 ​
     int choice;//选择
     String girlName;//使用化妆品的的人
 ​
     Makeup(int choice, String girlName) {
         this.choice = choice;
         this.girlName = girlName;
    }
 ​
     @Override
     public void run() {
         //化妆
         try {
             makeup();
        } catch (InterruptedException e) {
             e.printStackTrace();
        }
    }
 ​
     //化妆 ,互相持有对方的锁,就是需要拿到对方的资源
     private void makeup() throws InterruptedException {
         if (choice == 0) {
             synchronized (lipstick) {
                 System.out.println(this.girlName + "获得口红的锁");
                 Thread.sleep(1000);
 ​
 ​
                }
                 synchronized (mirror) {//一秒钟后想获得镜子的锁
                     System.out.println(this.girlName + "获得镜子的锁");
            }
        } else {
             synchronized (mirror) {
                 System.out.println(this.girlName + "获得镜子的锁");
                 Thread.sleep(2000);
 ​
 ​
 ​
                }
                 synchronized (lipstick) {//一秒钟后想获得镜子的锁
                     System.out.println(this.girlName + "获得口红的锁");
 ​
            }
        }
    }
 }

Lock(锁)

从JDK5.0Java提供了更强大的线程同步机制---通过现实定义同步锁对象来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应获得Lock对象

ReentrantLock(可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁。

 class A{
     private final ReentrantLock lock = new ReenTrantLock();
     public void m(){
         lock.lock();
         try{
             //保证线程安全的代码;
        }
         finally{
             lock.unlock();
             //如果同步代码有异常,要将unlock()写入finally语句块
        }
    }
 }
 package com.kuang.gaoji;
 ​
 import java.util.concurrent.locks.ReentrantLock;
 ​
 //测试Lock锁
 public class TestLock {
     public static void main(String[] args) {
         TestLock2 testLock2 = new TestLock2();
 ​
         new Thread(testLock2).start();
         new Thread(testLock2).start();
         new Thread(testLock2).start();
    }
 }
 class TestLock2 implements Runnable{
     int ticketNums = 10;
 ​
     //定义lock锁
     private final ReentrantLock lock = new ReentrantLock();
     @Override
     public void run() {
 ​
         while (true){
          try {
              lock.lock();//加锁
              if (ticketNums>0){
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  System.out.println(ticketNums--);
              }else {
                  break;
              }
          }finally {
              //解锁
              lock.unlock();
 ​
          }
 ​
 ​
    }
 ​
    }
 }
 ​

synchronized与Lock的对比

Lock是显示锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放

Lock只有代码块锁,synchronized有代码块锁和方法锁

使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

优先使用顺序:

Lock>同步代码块(已经 进入了方法体,分配了相应资源)>同步方法(在方法体之外)

04线程协作

生产 者消费者模式

线程通信

应用场景:生产者和消费者问题

假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。

如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。

如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库再次放入产品为止。

线程通信-分析

这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

对于生产者,没有生产产品之前,要通知消费者等待。而生产产品之后,又需要马上通知消费者消费。

对于消费者,在消费之后。要通知生产者已经结束消费,需要产生新的产品以供消费。

在生产者消费者问题中,仅有synchronized是不够的

synchronized可阻止并发更新同一个共享资源,实现了同步

synchronized不能用来实现不同线程之间的消息传递(通信)

java提供了几个方法解决线程之间通信问题

 方法名                    作用
  wait()             表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
  wait(long timeout)  指定等待的毫秒数
  notify()            唤醒一个处于等待状态的线程
  notifyAll()         唤醒同一个对象上所调用wait()方法的线程,优先级别高的线程优先调度
注意Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IIIegalMonitorStateException

解决方式1

并发协作模式“生产者/消费者模式”--->管程法

生产者:负责生产数据的模块(可能是方法,对象,线程,进程);

消费者:负责处理数据的模块(可能是方法,对象,线程,进程);

缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”

生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

 package com.kuang.gaoji;
 //测试:生产者消费者模型-->利用缓冲区解决:管程法
 //生产者,消费者,产品,缓冲区
 public class TestPC {
     public static void main(String[] args) {
         SynContainer container = new SynContainer();
 ​
         new Productor(container).start();
         new Consumer(container).start();
    }
 }
 //生产者需要一个容器
 class Productor extends Thread{
     SynContainer container;
 ​
     public Productor(SynContainer container) {
         this.container = container;
    }
     //生产
 ​
     @Override
     public void run() {
         for (int i = 0; i < 100; i++) {
             System.out.println("生产了"+i+"只鸡");
             container.push(new Chicken(i));
 ​
 ​
        }
    }
 }
 ​
 //消费者需要一个容器
 class Consumer extends Thread{
     SynContainer container;
     public Consumer(SynContainer container){
         this.container = container;
    }
     //消费
 ​
 ​
     @Override
     public void run() {
         for (int i = 0; i < 100; i++) {
             System.out.println("消费了-->"+container.pop().id+"只鸡");
        }
    }
 }
 ​
 //产品
 class Chicken{
     int id;//产品编号
     public Chicken(int id){
         this.id = id;
    }
 }
 ​
 //缓冲区
 class  SynContainer{
     //需要一个容器
     Chicken[] chickens = new Chicken[10];
     //容器计数器
     int count = 0;
 ​
     //生产者放入产品
     public synchronized void push(Chicken chicken){
         //如果容器满了,就需要等待消费者消费
         if (count==chickens.length){
             //通知消费者消费,生产等待
             try{
                 this.wait();
            }catch (InterruptedException e){
                 e.printStackTrace();
            }
        }
 ​
 ​
 ​
         //如果没有满,我们就需要丢入产品
         chickens[count]=chicken;//chickens放入计数器count中
         count++;
 ​
         //可以通知消费者消费了
         this.notify();
    }
 ​
 ​
     //消费者消费产品
     public synchronized Chicken pop(){
         //判断能否消费
         if (count==0){
             //等待生产者生产,消费者等待
             try{
                 this.wait();
            }catch (InterruptedException e){
                 e.printStackTrace();
            }
 ​
        }
         //如果可以消费
         count--;
         Chicken chicken=chickens[count];
 ​
         //吃完了,通知生产者生产
         this.notify();
         return chicken;
    }
 ​
 ​
 ​
 ​
 ​
 }

解决方式2

并发协作模型“生产者、消费者模式”--->信号灯法

 package com.kuang.gaoji;
 //测试生产者消费者问题2:信号灯法,标志位解决
 public class TestPc2 {
     public static void main(String[] args) {
         TV tv = new TV();
         new Player(tv).start();
         new Watcher(tv).start();
    }
     
 ​
 }
 ​
 //生产者--->演员
 class Player extends Thread{
     TV tv;
     public Player(TV tv){
         this.tv = tv;
    }
 ​
     @Override
     public void run() {
         for (int i = 0; i < 20; i++) {
             if (i%2==0){
                 this.tv.play("快乐大本营播放中");
            }else{
                 this.tv.play("抖音:记录美好生活");
            }
             
        }
    }
 }
 ​
 //消费者--->观众
 class Watcher extends Thread{
     TV tv;
     public Watcher(TV tv){
         this.tv = tv;
    }
 ​
     @Override
     public void run() {
         for (int i = 0; i < 20; i++) {
             tv.watch();
        }
    }
 }
 ​
 //产品--->节目
 class TV{
     //演员表演,观众等待 True
     //观众观看,演员等待 False
     String voice;//表演的节目
     boolean flag = true;
 ​
 ​
 ​
     //表演 只要涉及并发就加synchronized
     public  synchronized void play(String voice){
         if (!flag){//flag不为真就需要演员等待
             try {
                 this.wait();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
         System.out.println("演员表演了:"+voice);
         //通知观众观看
         this.notifyAll();//通知唤醒
         this.voice = voice;
         this.flag = !this.flag;
 ​
    }
 ​
     //观看
     public synchronized void watch(){
         if (flag){
             try {
                 this.wait();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
         System.out.println("观看了:"+voice);
 ​
         //通知演员表演
         this.notifyAll();
         this.flag = !this.flag;
    }
 ​
 }

使用线程池

背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁。类似生活中的公共交通工具。

好处:

提高响应速度(减少了创建新线程的时间)

降低资源消耗(重复利用线程池中线程,不需要每次都创建)

便于线程管理(...)

corePoolSize:核心池的大小

maximumPoolSize:最大线程数

keepAliveTime:线程没有任务时最多保持多长时间后会终止

JDK5.0起提供了线程池相关API:ExecutorService和Executors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

 void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runable
  <T>Future<T>submit(Callable<T>task):执行任务,有返回值,一般又来执行Callable
     void shutdown():关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

 package com.kuang.gaoji;
 ​
 ​
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 ​
 //测试线程池
 public class TestPool {
 ​
 ​
     public static void main(String[] args) {
         //1.创建服务,创建线程池
         //newFixedThreadPool 参数为:线程池大小
         ExecutorService service = Executors.newFixedThreadPool(10);
 ​
         //执行
         service.execute(new MyThread());
         service.execute(new MyThread());
         service.execute(new MyThread());
         service.execute(new MyThread());
 ​
         //2,关闭连接
         service.shutdownNow();
    }
 }
 ​
 ​
 class MyThread implements Runnable{
 ​
     @Override
     public void run() {
 ​
             System.out.println(Thread.currentThread().getName());
 ​
    }
 }
 package com.kuang.gaoji;
 ​
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.FutureTask;
 ​
 //回顾总结线程的创建
 public class ThreadNew {
     public static void main(String[] args) {
         new MyThread1().start();
         new Thread(new MyThread2()).start();
         FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
         new Thread(futureTask).start();
 ​
         try {
             Integer integer = futureTask.get();
             System.out.println(integer);
        } catch (InterruptedException e) {
             e.printStackTrace();
        } catch (ExecutionException e) {
             e.printStackTrace();
        }
    }
 }
 ​
 ​
 //1.继承Thread类
 class MyThread1 extends Thread{
     @Override
     public void run() {
         System.out.println("MyThread1");
    }
 }
 ​
 //2.实现Runnable接口
 class MyThread2 implements Runnable{
     @Override
     public void run() {
         System.out.println("MyThread1");
    }
 }
 ​
 //3.实现Callable接口
 class MyThread3 implements Callable<Integer>{
     @Override
     public Integer call() throws Exception {
         System.out.println("MyThread3");
         return 100;
    }
 }
上一篇:Java-多线程与单例


下一篇:线程安全单例最佳实践,C#中的Lazy是如何保证线程安全的