死锁

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

死锁避免方法

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

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;
    }
 }

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

  1. Java synchronized和 Lock 的区别与用法

    在分布式开发中,锁是线程控制的重要途径.Java为此也提供了2种锁机制,synchronized和lock.做为Java爱好者,自然少不了对比一下这2种机制,也能从中学到些分布式开发需要注意的地方.  ...

  2. synchronized 与 lock 的区别

    synchronized 和 lock 的用法区别 synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized 可以加在方法上,也可以加在特定代码块中,括号中表示需要锁 ...

  3. 第44天学习打卡(JUC 线程和进程 并发和并行 Lock锁 生产者和消费者问题 如何判断锁(8锁问题) 集合类不安全)

    什么是JUC 1.java.util工具包 包 分类 业务:普通的线程代码 Thread Runnable 没有返回值.效率相比Callable相对较低 2.线程和进程 进程:一个程序.QQ.exe, ...

  4. 死锁、Lock锁、等待唤醒机制、线程组、线程池、定时器、单例设计模式_DAY24

    1:线程(理解) (1)死锁 概念: 同步中,多个线程使用多把锁之间存在等待的现象. 原因分析: a.线程1将锁1锁住,线程2将锁2锁住,而线程1要继续执行锁2中的代码,线程2要继续执行锁1中的代码, ...

  5. 第43天学习打卡(JVM探究)

    JVM探究 请你谈谈你对JVM的理解?Java8虚拟机和之前的变化更新? 什么是OOM,什么是栈溢出StackOverFlowError? 怎么分析? JVM的常用调优参数有哪些? 内存快照如何抓取, ...

  6. 第40天学习打卡(静态代理 Lambda表达式 线程状态 线程同步 同步方法)

    静态代理  package com.kuang.demo03; //静态代理模式总结 //真实对象和代理对象都要实现同一个接口 //代理对象要代理真实角色 //好处:  //代理对象可以做很多真实对象 ...

  7. 第46天学习打卡(四大函数式接口 Stream流式计算 ForkJoin 异步回调 JMM Volatile)

    小结与扩展 池的最大的大小如何去设置! 了解:IO密集型,CPU密集型:(调优)  //1.CPU密集型 几核就是几个线程 可以保持效率最高 //2.IO密集型判断你的程序中十分耗IO的线程,只要大于 ...

  8. 第39天学习打卡(多线程 Thread Runnable 初始并发问题 Callable )

    多线程详解 01线程简介 Process与Thread 程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念. 进程则是执行程序的一次执行过程,它是一个动态的概念.是系统资源分配的 ...

  9. 【等待事件】序列等待事件总结(enq: SQ - contention、row cache lock、DFS lock handle和enq: SV - contention)

    [等待事件]序列等待事件总结(enq: SQ - contention.row cache lock.DFS lock handle和enq: SV -  contention) 1  BLOG文档结 ...

随机推荐

  1. 【LAMP】搭建Web网站过程中的记录【Ubuntu18.04+Apache2.4+PHP7.2+MySQL5.7】

    全文使用的环境如题,主机使用的是腾讯云主机. 内容应该会是linux和apache这些所有部分都有一点,因为是遇见一个问题就记录一个. 配置LAMP环境 这部分可以参考这篇文章:https://www ...

  2. [译]Rxjs&Angular-退订可观察对象的n中方式

    原文/出处: RxJS & Angular - Unsubscribe Like a Pro 在angular项目中我们不可避免的要使用RxJS可观察对象(Observables)来进行订阅( ...

  3. ES模块化的导入和导出

    目录 环境准备 模块化 export.import export import import * as export default import 和 require 的区别 参考 环境准备 三个文件 ...

  4. 一文弄懂-《Scalable IO In Java》

    目录 一. <Scalable IO In Java> 是什么? 二. IO架构的演变历程 1. Classic Service Designs 经典服务模型 2. Event-drive ...

  5. Educational Codeforces Round 39

    Educational Codeforces Round 39  D. Timetable 令\(dp[i][j]\)表示前\(i\)天逃课了\(j\)节课的情况下,在学校的最少时间 转移就是枚举第\ ...

  6. Codeforces Round #651 (Div. 2) D. Odd-Even Subsequence(二分)

    题目链接:https://codeforces.com/contest/1370/problem/D 题意 给出一个含有 $n$ 个数的数组 $a$,从中选出 $k$ 个数组成子序列 $s$,使得 $ ...

  7. A. Little Pony and Expected Maximum

    Twilight Sparkle was playing Ludo with her friends Rainbow Dash, Apple Jack and Flutter Shy. But she ...

  8. Codeforces Round #614 (Div. 1) A. NEKO's Maze Game (思维,模拟)

    题意:有一个\(2\)X\(n\)的矩阵,你想从\((1,1)\)走到\((2,n)\),每次可以向上下左右四个方向走,但在某些时间段某个点会被堵住,如果已经被堵住,那么即恢复正常,每次对某个点操作, ...

  9. hdu5289 Assignment

    Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission ...

  10. Codeforces Round #540 (Div. 3) B. Tanya and Candies (后缀和)

    题意:有\(n\)个数,你可以任意去除某个位置的元素然后得到一个新数组,使得新数组奇数位和偶数的元素相等,现在问你有多少种情况合法. 题解:先求个后缀和,然后遍历,记录奇数和偶数位置的前缀和,删去\( ...