• java编程基础总结——31.多线程编程相关知识


    一、乐观锁和悲观锁:

       synchronized和Lock都是悲观锁

    1. 乐观锁:乐观锁认为不会出现并发。因此不会上锁,但是在更新的时候会判断一下,在此期间有没有别人去更新这个数据,可以使用版本号机制和CAS算法实现,

    2. 悲观锁:悲观锁认为一定会发生并发。因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

    二、单例模式:


    1. 饿汉式:
            直接创建对象

            优点:方便,不出现问题

            缺点:浪费内存

        

    2. 懒汉式:
            在第一次调用时创建对象

    存在线程安全问题:多线程中,多个人同时访问第一行。假如第一个线程进了,还没来得及创建,第二个线程进来了,此时就会创建两个对象,出问题。

    所以在高并发情况下懒汉式需要加锁

    建议使用 DCL(double check lock):双重检查锁
            需要禁止指令重排序!!(不禁止单例模式下dcl可能会失效)

    单线程下的懒汉式单例模式:

    1. package com.openlab.day26;
    2. public class Singleton {
    3. // 懒汉式单例模式
    4. private static Singleton singleton = null;//singleton 对象
    5. // 1、私有化构造函数
    6. private Singleton() {}
    7. // 2、给使用者提供一个创建好的对象(无构造函数,不能创建对象,只能通过类名调用)
    8. public static Singleton newInstance() {
    9. if (singleton == null) {
    10. singleton = new Singleton();
    11. }
    12. return singleton;
    13. }
    14. }

     加锁:

    1. public class Sington {
    2. private static Sington sington = null;
    3. // 构造函数私有化
    4. private Sington() {}
    5. public static Sington newInstance() {
    6. if (sington == null) {
    7. synchronized (Sington.class) {
    8. sington = new Sington();
    9. }
    10. }
    11. return sington;
    12. }
    13. }

    一个线程刚进if,还没有进同步代码块时,另一个线程进来了,也就出问题了

    所以我们用学过的知识想到可以加在方法上,但是加在方法上加锁范围太大,

     public synchronized static Sington newInstance() 

    此时用dcl双重检查锁

    在同步代码块中再加一个判断

    1. public class Sington {
    2. private static Sington sington = null;
    3. // 构造函数私有化
    4. private Sington() {
    5. }
    6. public synchronized static Sington newInstance() {
    7. if (sington == null) {
    8. synchronized (Sington.class) {
    9. if (sington == null) {
    10. sington = new Sington();
    11. }
    12. }
    13. }
    14. return sington;
    15. }
    16. }

    但是这样还是有问题,因为高并发的三大要素中的有序性,可能导致内层if中的new代码先执行,后执行里层if,我们应该禁止出现这种情况

    此时我们需要用到volatile关键字

    高并发的三大要素:

    1. 可见性:

            多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修            改的结果。(线程间的通信实现)

    2. 原子性:如果一个操作不可分割,则不可能产生线程安全问题
    3. 排序性:
            java代码,底层最后都会编译成汇编指令执行,汇编指令有可能会优化代码
            这种优化,有可能导致指令的执行顺序出现问题。

    volatile关键字:    

        作用:
            1、被volatile修饰的变量,是线程可见的(可见性)
            2、被volatile修饰的变量,禁止指令重排序

        注意:不具备原子性

    完整单例模式 懒汉式:

    1. public class Sington {
    2. private volatile static Sington sington = null;
    3. // 构造函数私有化
    4. private Sington() {
    5. }
    6. public synchronized static Sington newInstance() {
    7. if (sington == null) {
    8. synchronized (Sington.class) {
    9. if (sington == null) {
    10. sington = new Sington();
    11. }
    12. }
    13. }
    14. return sington;
    15. }
    16. }


    三、线程的生命周期:
        

    线程的生命周期一共分为五个部分分别是:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。由于cpu需要在多条线程中切换因此线程状态也会在多次运行和阻塞之间切换

    1. 新建:

    当一个Thread类或其子类的对象被声明并创建时 (使用new关键字创建了一个线程)

    2. 就绪:

    调用start()方法之后,进入线程队列等待被分配给CPU时间片,即线程正在就绪队列中排队等候得到CPU资源 

    3. 运行:

    线程获得CPU资源正在执行任务( run()方法 ),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。

    4. 阻塞:

    由于某种原因导致正在运行的线程让出CPU并暂停自己的执行(放弃锁),即进入堵塞状态。直到线程进入可运行(runnable)状态,才有机会再次转到运行(running)状态。(无法从阻塞状态直接返回到Runnable状态)

            阻塞的情况分三种:

            等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中

            对象锁阻塞:运行的线程当获取到对象锁时,若该对象锁被别的线程占用,则JVM会把该线程放入锁池中。

            其他阻塞:运行的线程执行了Thread.sleep()或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程就会重新转入就绪状态。(注意:sleep是不会释放持有的锁

    5. 死亡

    线程会以下面三种方式结束,结束后就是死亡状态。

    1)正常结束

    run()或 call()方法执行完成,线程正常结束。

    2)异常结束

    线程抛出一个未捕获的 Exception 或 Error。

    2)调用 stop

    直接调用该线程的stop()方法来结束该线程

    该方法通常容易导致死锁,不推荐使用。

     


    四、死锁(deadLock):

        两个线程之间,进行资源竞争时,彼此之间拿着对方需要的资源,
        但是因为线程的竞争性(请求保持(不放弃)),形成一种相互等待对方释放资源
        的现象,就叫做死锁。
        这种现象不好,尽量避免出现。


    死锁的必要条件:
        1、互斥:一个资源每次只能被一个进程使用(比如都要请求cpu就可能出现死锁)
        2、请求保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放
        3、环路等待:若干进程之间形成一种头尾相接的循环等待资源关系。在发生死锁时,必然存在一个进程–资源的环形链。
        4、不可剥夺性:进程已获得的资源,在末使用完之前,不能强行剥夺,只能在使用完时由自己释放


    五、同步问题:

        协同步调。
        实现方案:
            可以使用lock来实现
            使用同步锁配合唤醒机制(唤醒机制无法单独使用,会报错)
        

    下面介绍 使用同步锁配合唤醒机制:


            
    唤醒机制:javat提供用来进行线程信息交流的一种方案

    线程间不可见,所以出现线程安全问题,如果可见就不会出现线程安全问题,有很多方案,另外一种方案是唤醒机制

    唤醒机制是对象与对象之间的,所以放在了Object类
        
        notify()          // 唤醒已经处于wait状态的对象
        notifyAll()      // 唤醒所有处于wait状态的对象
        wait()            // 进入到等待状态        (sleep()、wait()不会放弃锁)join会放弃

    native,final        不能被重写

     唤醒一个 在等待这个对象的观察者 的线程


        同步锁配合唤醒机制实现同步

        1、必须有一个共有对象
        2、该共有对象就是同步锁的钥匙

    案例:

    做同步一般喜欢  生产者和消费者模式:
        供需的平衡问题

    将消费者设置成守护线程(依赖于生产者,生产者生产了,消费者才能消费)

    customer.setDaemon(true);

    共有对象,盘子:

    1. public class Disk {
    2. private boolean full = true;//默认盘子是空的
    3. private String food;
    4. public boolean isFull() {
    5. return full;
    6. }
    7. public String getFood() {
    8. return food;
    9. }
    10. public void setFull(boolean full) {
    11. this.full = full;
    12. }
    13. public void setFood(String food) {
    14. this.food = food;
    15. }
    16. }

     生产者:

    1. import java.util.Random;
    2. public class Productor extends Thread {
    3. private String[] foods;
    4. private Disk disk;
    5. private Random random;
    6. public Productor(Disk disk) {
    7. this.foods = new String[]{"浆水面 ", "油泼面", "牛筋面", "兰州牛肉面", "削筋面", "担担面", "重庆小面", "岐山臊子面", "扯面 \n"};
    8. this.disk = disk;
    9. this.random = new Random();
    10. }
    11. @Override
    12. public void run() {
    13. makeFood();
    14. }
    15. private void makeFood() {
    16. synchronized (this.disk) {//以共有对象作为key
    17. for (int i = 0; i < 10; i++) {
    18. if (this.disk.isFull()) {
    19. // 创建食物
    20. String food = this.foods[this.random.nextInt(this.foods.length)];
    21. System.out.println("生成了一个食物:" + food);
    22. this.disk.setFood(food);
    23. this.disk.setFull(false);
    24. // 先唤醒消费者
    25. this.disk.notify();
    26. // 让生产者进入等待状态
    27. try {
    28. this.disk.wait();
    29. } catch (InterruptedException e) {
    30. e.printStackTrace();
    31. }
    32. } else {
    33. try {
    34. this.disk.wait();//盘子是满的,不用做
    35. } catch (InterruptedException e) {
    36. e.printStackTrace();
    37. }
    38. }
    39. }
    40. }
    41. }
    42. }

    消费者:

    1. public class Customer extends Thread {
    2. private Disk disk;
    3. public Customer(Disk disk, String name) {
    4. super(name);
    5. this.disk = disk;
    6. }
    7. public Disk getDisk() {
    8. return disk;
    9. }
    10. public void setDisk(Disk disk) {
    11. this.disk = disk;
    12. }
    13. @Override
    14. public void run() {
    15. synchronized (this.disk) {//以共有对象作为key
    16. while (true) {
    17. if (!this.disk.isFull()) {
    18. String food = this.disk.getFood();
    19. System.out.println(currentThread().getName() + "开始消费食物了,食物是:" + food);
    20. this.disk.setFull(true);
    21. this.disk.notify();
    22. try {
    23. this.disk.wait();
    24. } catch (InterruptedException e) {
    25. e.printStackTrace();
    26. }
    27. } else {
    28. try {
    29. this.disk.wait();
    30. } catch (InterruptedException e) {
    31. e.printStackTrace();
    32. }
    33. }
    34. }
    35. }
    36. }
    37. }

  • 相关阅读:
    Java笔记(十)
    RFSoC应用笔记 - RF数据转换器 -08- RFSoC关键配置之RF-DAC内部解析(二)
    五、伊森商城 前端基础-Vue p24
    入门Docker你不得不读的基础知识
    快速了解并掌握CLM 陆面过程模式
    ElasticSearch系列——Kibana,核心概念
    canvas 轮询http接口让小车实时运动
    华为eNSP实验-三层交换机的不同网段通信(通过OSPF路由方式)
    12基于MATLAB的短时傅里叶变换( STFT),连续小波变换( CWT),程序已调通,可以直接运行。
    酷家乐基于 Crane EHPA 的弹性落地实践
  • 原文地址:https://blog.csdn.net/m0_58679504/article/details/126440703