Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)

并发

概念:同一个对象被多个线程同时操作

线程同步

Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第1张图片
线程同步sync形成条件(实现原理):队列和锁
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第2张图片

三大不安全案例:

案例1:不安全买票
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第3张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第4张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第5张图片

输出:
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第6张图片

案例2:不安全的取钱
sleep可以放大问题的发生性

Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第7张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第8张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第9张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第10张图片
不加延时可能不会出现问题,因为一开始第一个线程执行完了之后再执行第二个线程的话,第二个线程就拿到变化之后的资源进行判断了,加延时的话他们都拿到了未变化的资源。

案例3:线程不安全的集合
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第11张图片

那么怎么解决?

同步方法

  • synchronized加在方法上做关键字修饰符不可以指定锁对象默认为this当前实例对象

  • 程序执行完同步代码块会释放代码块。

  • 程序在执行同步代码块是出现异常,JVM会自动释放锁去处理异常。

Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第12张图片

同步方法的弊端:
只有修改的需要锁,只读的不需要锁
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第13张图片
案例1:同步买票
synchronized默认锁住当前类对象比如这个类对象名字叫BuyTicket
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第14张图片

案例2:取钱
不能直接锁在run方法上
可以使用同步块,然后指定同步监视器(锁对象)

总结:

  • synchronize修饰方法的锁对象只能是this当前对象

  • synchronize修饰代码块可以修改锁对象
    Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第15张图片

锁的话注意是锁的对象,默认是this,锁要锁增删改的对象,比如这个是银行
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第16张图片

案例3:锁list对象
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第17张图片

juc CopyOnWriteArrayList安全的集合

并发编程的包叫java.util.concurrent
集合建议都加泛型
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第18张图片

死锁

案例:化妆
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第19张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第20张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第21张图片

程序僵持在这里执行不下去
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第22张图片

解决方案:
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第23张图片
输出
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第24张图片

Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第25张图片

解决办法:
不要像上面那样在一个同步代码块未执行完的时候又去拿另一个资源,这样不会释放第一个锁也拿不到第二个锁,可以把同步代码块放出去弄成同级的这样第一个的同步代码块执行完了之后会释放锁,然后给第二个线程去执行(白雪公主)

同步代码块:执行完才会释放锁

Lock锁

ReentrantLock(润全特Lock)可重入锁
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第26张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第27张图片

案例:买票
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第28张图片

Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第29张图片
效率比synchronized好
但是Lock 内没有同步监视器(也就是锁的对象)

Lock同步的就是lock和unlock中间的代码段,这段代码块只能被一个线程获取,其他线程尝试获取会进入lock对象的等待队列

线程协作通讯:生产者消费者

Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第30张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第31张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第32张图片

总结:同步synchroized,通讯wait notify
操作的是一个对象,生产者生产,消费者消费(互为依赖)
wait会释放锁

解决方案1:缓冲区
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第33张图片

解决方案2:True false信号灯法
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第34张图片

案例1:缓冲区 方法名字叫做(管程法)
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第35张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第36张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第37张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第38张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第39张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第40张图片

案例2:信号灯法
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第41张图片

Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第42张图片

Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第43张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第44张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第45张图片

线程池

Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第46张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第47张图片

案例:
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第48张图片

callable案例:
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第49张图片

总结多线程学习一 二

实现线程的三种方法
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第50张图片
Java多线程学习二(同步、死锁、lock锁、线程通讯生产消费者、线程池)_第51张图片

其他还有线程池和springboot @Async注解等

面试题需知一些情况

  1. synchronize修饰方法和synchroize修饰代码块的不同:

synchronize修饰方法的锁对象只能是this当前对象(锁的对象是方法的调用者),synchronize修饰代码块可以修改锁对象

同步方法锁的范围比较大,而同步代码块范围要小点,一般同步的范围越大,性能就越差,一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好

  1. 非静态的同步方法是锁定类的实例的,而静态的同步方法是锁定类的;

非静态的同步方法是锁定类的实例(引用对象)的,而静态的同步方法是锁定类的;
也就是说,对于非静态的同步方法,在同一时刻,一个类的一个实例中,只有一个线程能进入同步的方法。但是对于多个实例,每一个实例的一个线程都可以进入同一同步的方法。

(具体看我文章JUC并发编程一)
3. lock锁和synchronize的区别

比较点 lock synchronize
所处层面 是Java中的一个接口,它有许多的实现类来为它提供各种功能 Java中的一个关键字,当我们调用它时会从在虚拟机指令层面加锁
获得锁的方式 Lock的使用离不开它的实现类AQS,而它的加锁并不是针对对象的,而是针对当前线程的,并且AQS中有一个原子类state来进行加锁次数的计数 可对实例方法、静态方法和代码块加锁,相对应的,加锁前需要获得实例对象的锁或类对象的锁或指定对象的锁。说到底就是要先获得对象的监视器(即对象的锁)然后才能够进行相关操作。
获锁失败 使用Lock加锁的程序中,获锁失败的线程会被自动加入到AQS的等待队列中进行自旋,自旋的同时再尝试去获取锁,等到自旋到一定次数并且获锁操作未成功,线程就会被阻塞 加锁的程序中,获锁失败的对象会被加入到一个虚拟的等待队列中被阻塞,直到锁被释放;1.6以后加入了自旋操作
释放锁 1、可调用ulock方法去释放锁比synchronized更灵活;2、还可以使用trylock(尝试获取锁)方法,意思是没等到释放就结束了; 不能指定解锁操作,1、执行完代码块的对象会自动释放锁2、占有锁的线程发生了异常,此时JVM会让线程自动释放锁3、调用wait方法,释放锁进入wating状态
造成死锁 而Lock必须在finally中主动unlock锁,否则就会出现死锁 synchronized在发生异常时,会自动释放掉锁,故不会发生死锁现(此时的死锁一般是代码逻辑引起的),比如:1、线程1(获得锁1,【一秒后获得锁2】),线程2(获得锁2,【一秒后获得锁1】),然后就会一直等待;2、线程1(获得锁,阻塞)、线程2(等待,傻傻的等)造成死锁
效率方面 比较高 比较低
是否判断获得锁 可以判断获得锁 无法判断获得锁的状态
重入锁 都是可重入锁,不可中断,非公平锁 可重入锁,可以判断锁什么时候中断,非公平(可自己设置)
适用范围 适合锁少量的代码同步 适合锁大量的代码同步
  1. 死锁产生条件:

1、 互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

2、 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

3、 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

4、 循环等待条件: 若干进程间形成首尾相接循环等待资源的关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

你可能感兴趣的:(Java基础学习)