• 第二十三章 多线程(二)


    1.线程同步Synchronized和RentranLock用法

     1.1 同步异步

            如果数据将在线程间共享;如正在写的数据ihou可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取;

            当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应使用异步编程,在多数情况下异步途径更有效率;

    同步:多个任务情况下,一个任务A执行结束,才可以执行另一个任务B;

    异步:多个任务情况下,一个任务B正在执行,同时可以执行另一个任务B,任务B不用等待任务A结束才执行。

     1.2 相似点

            Synchronized和ReentranLock这两种同步方式都是加锁方式同步,而且都是阻塞式的同步,就是如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待(进行线程阻塞和唤醒的代价比较高)。

    1.3 不同点

    1.3.1 synchronized

    修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁;

    修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁;

    修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。

    1. public class MyRunnable2 implements Runnable{
    2. @Override
    3. public void run() {
    4. for (int i = 0; i < 3; i++) {
    5. //同步代码块,对目标代码加锁
    6. /*synchronized (this){
    7. System.out.println(Thread.currentThread().getName()+" "+i+" -^w^-");
    8. System.out.println(Thread.currentThread().getName()+" "+i+" -^m^-");
    9. System.out.println(Thread.currentThread().getName()+" "+i+" -^v^-");
    10. }*/
    11. //MyRunnable2.show1(i);
    12. show2(i);
    13. }
    14. }
    15. /**
    16. * 静态方法加锁
    17. * @param i
    18. */
    19. public static synchronized void show1(int i){
    20. System.out.println(Thread.currentThread().getName()+" "+i+" -^w^-");
    21. System.out.println(Thread.currentThread().getName()+" "+i+" -^m^-");
    22. System.out.println(Thread.currentThread().getName()+" "+i+" -^v^-");
    23. }
    24. /**
    25. * 实例方法加锁
    26. * @param i
    27. */
    28. public synchronized void show2(int i){
    29. System.out.println(Thread.currentThread().getName()+" "+i+" -^w^-");
    30. System.out.println(Thread.currentThread().getName()+" "+i+" -^m^-");
    31. System.out.println(Thread.currentThread().getName()+" "+i+" -^v^-");
    32. }
    33. }
    34. public class MyTest3 {
    35. public static void main(String[] args) {
    36. MyRunnable2 myRunnable2 = new MyRunnable2();
    37. new Thread(myRunnable2).start();
    38. new Thread(myRunnable2).start();
    39. new Thread(myRunnable2).start();
    40. }
    41. }

     Synchronized底层原理:

            Synchronized经过编译,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码指令,在执行monitorenter指令时,首先要尝试获取对象锁;如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器减1,当计算器为0时,锁就被释放了;如果获取对象锁失败,那当前线程就要阻塞,知道对象锁被另一个线程释放为止。

            找到刚刚代码编译后的.class文件,用cmd执行反编译命令  javap -v MyRunnable2.class,可以找到这两个指令(同步代码块):

            对于前面三人卖票问题中可能出现同一张票被卖多次或票数卖成负数的问题的解决:

    1. public class MyRunnable implements Runnable{
    2. private int ticket = 20;
    3. @Override
    4. public void run() {
    5. while (ticket>0){
    6. //当前类的实例对象 用的同一个对象锁
    7. synchronized (this){
    8. //解决票数问题 进行二次判断
    9. if(ticket>0){
    10. try {
    11. //通过设置一个休眠可以让问题更容易复现
    12. Thread.sleep(1000);
    13. } catch (InterruptedException e) {
    14. e.printStackTrace();
    15. }
    16. System.out.println(Thread.currentThread().getName()+"卖出一张,剩余"+(--ticket));
    17. }
    18. }
    19. }
    20. }
    21. }

    1.3.2 ReentrantLock

            是JDK 1.5 之后提供的API(application interface)层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成;

            ReentranLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了一些高级功能:

    ①等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,相当于Synchronized来说可以避免出现死锁的情况;

    ②公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentranLock默认的构造函数创建的是非公平锁,可以通过参数true设置为公平锁,但性能不好;

    ③锁绑定多个条件,一个ReentrantLock对象可以同时绑定多个对象。

    1. public class MyRunnable3 implements Runnable {
    2. private int ticket = 50;
    3. private Lock reentrantLock = new ReentrantLock();
    4. @Override
    5. public void run() {
    6. while (ticket>0){
    7. //加锁
    8. reentrantLock.lock();
    9. //解决票数问题 进行二次判断
    10. try{
    11. if(ticket>0){
    12. System.out.println(Thread.currentThread().getName()+"卖出一张,剩余"+(--ticket));
    13. }
    14. }finally {
    15. //解锁
    16. reentrantLock.unlock();
    17. }
    18. }
    19. }
    20. }

    1.4 sleep和wait的区别(阻塞当前线程,让出资源)

            sleep就是正在执行的线程主动让出cpu,cpu去执行其他线程,在sleep指定的时间过后,cpu才会回到这个线程上继续执行,如果当前线程进入了同步锁,sleep方法不会释放锁,即使当前线程使用sleep方法让出了cpu,但其他被同步锁挡住的线程也无法得到执行;

            wait是指在一个已经进入同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了notify方法(notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,不是立刻就得到锁,因为锁没有被释放),调用wait方法的线程就会解除wait状态和程序可以再次得到锁后继续向下运行。

    1. public class MyTest5 {
    2. public static void main(String[] args) {
    3. Object obj = new Object();
    4. Thread t1 = new Thread(new Runnable() {
    5. @Override
    6. public void run() {
    7. synchronized (obj){
    8. System.out.println(Thread.currentThread().getName()+" -A-");
    9. try {
    10. //等待
    11. obj.wait();
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }
    15. System.out.println(Thread.currentThread().getName()+" -B-");
    16. }
    17. }
    18. });
    19. Thread t2 = new Thread(new Runnable() {
    20. @Override
    21. public void run() {
    22. synchronized (obj){
    23. System.out.println(Thread.currentThread().getName()+" -C-");
    24. //唤醒
    25. obj.notify();
    26. System.out.println(Thread.currentThread().getName()+" -D-");
    27. }
    28. }
    29. });
    30. t1.start();
    31. t2.start();
    32. }
    33. }

     如果是 t2 先被执行,t1 在执行完 -A- 之后会一直等待下去。

    2. 死锁与防止死锁

    成因:

            当前线程拥有其他线程需要的资源;

            当前线程等待其他线程已拥有的资源;

            都不放弃自己拥有的资源。 

     死锁实现:

    1. public class Father {
    2. public void say(){
    3. System.out.println("父亲:给我你的成绩单,我给你玩具");
    4. }
    5. public void get(){
    6. System.out.println("父亲得到了成绩单");
    7. }
    8. }
    9. public class Son {
    10. public void say(){
    11. System.out.println("儿子:给我玩具,我给你成绩单");
    12. }
    13. public void get(){
    14. System.out.println("儿子得到了玩具");
    15. }
    16. }
    17. public class FatherSonTask implements Runnable{
    18. private Father father;
    19. private Son son;
    20. /**
    21. * 判断是父亲还是儿子
    22. */
    23. private boolean isFather;
    24. public FatherSonTask(Father father, Son son, boolean isFather) {
    25. this.father = father;
    26. this.son = son;
    27. this.isFather = isFather;
    28. }
    29. /**
    30. * 定义锁资源(玩具,成绩单)
    31. */
    32. private static Object toysLock = new Object();
    33. private static Object resultsLock = new Object();
    34. @Override
    35. public void run() {
    36. if(isFather){
    37. synchronized (toysLock){
    38. father.say();
    39. try {
    40. Thread.sleep(100);
    41. } catch (InterruptedException e) {
    42. e.printStackTrace();
    43. }
    44. synchronized (resultsLock){
    45. father.get();
    46. }
    47. }
    48. }else {
    49. synchronized (resultsLock){
    50. son.say();
    51. try {
    52. Thread.sleep(10);
    53. } catch (InterruptedException e) {
    54. e.printStackTrace();
    55. }
    56. synchronized (toysLock){
    57. son.get();
    58. }
    59. }
    60. }
    61. }
    62. }
    63. public class FatherSonTest {
    64. public static void main(String[] args) {
    65. Father father = new Father();
    66. Son son = new Son();
    67. FatherSonTask fatherTask = new FatherSonTask(father,son,true);
    68. FatherSonTask sonTask = new FatherSonTask(father,son,false);
    69. new Thread(fatherTask).start();
    70. new Thread(sonTask).start();
    71. }
    72. }

    防止死锁:

    ①避免多次锁定,尽量避免同一个线程对多个Lock进行锁定;

    ②具有相同的加锁顺序;如果多个线程需要对多个Lock进行锁定,则应该保证他们以相同的顺序请求加锁;

    ③使用定时锁;调用方法加锁时可以指定timeout参数,超过该时间后会自动释放对Lock的锁定;

    ④死锁检测;死锁检测是一种依靠算法机制来实现的死锁预防机制。

  • 相关阅读:
    labview—word报表使用
    Mybatis 框架 ( 五 ) 分页
    分布式事务(一)———CAP、BASE理论
    ClickHouse学习笔记之表引擎
    (二十四)大数据实战——Flume数据流监控之Ganglia的安装与部署
    对接淘宝天猫平台的第一篇
    shell实验
    SpringBoot整合redis实现过期Key监控处理(最简单模式)
    rabbitmq发送消息通用接口
    ESP8266-Arduino网络编程实例-ESP-Now-Many-to-One多设备通信
  • 原文地址:https://blog.csdn.net/m0_71674778/article/details/126905025