初阶多线程安全问题(II)

线程相关知识总结:

一、进程与线程之间的区别?
(I)进程是系统分配资源的最小单位,线程是系统调度的最小单位
(II)进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈
(III)由于统一进程的个线程之间共享内存和文件资源,可以不通过内核进行直接通信
(IV)线程的创建、切换及终止效率更高(线程的创建、切换、终止也比较耗时,这里只是相对于进程来说效率更高)

二、多线程的使用场景?(一般要考虑性能和安全 以提高性能和效率为目标)
(I)多个任务且任务量较大
(II)多个任务且会阻塞

三、多线程中的特性及状态转换?
(一)多线程的特性注意事项

  1. 线程的创建:申请系统创建一个线程,比较耗时 —>new Thread() > 线程的用户态和内核态切换 —>系统接口调用(如线程中的IO操作)
    线程的时间片轮转调度—>系统调度
  2. 线程数的确定
    (1)计算公式:CPU核数/(1-阻塞系数) 某县城执行任务的总时间=执行任务的总阻塞时间+真正执行任务的时间(非阻塞) 阻塞系数=真正执行任务的时间/总时间
    (2)计算密集型的任务:CPU核数/CPU核数+1 作为线程数 (阻塞系数=0)
    (3)IO密集型的任务:使用计算公式

(二)常见的API导致的状态转换

1.线程启动:start() ,系统调度CPU执行线程,注意run()只是任务的定义

  1. 线程阻塞:
    (1)Thread.sleep(long) —>当前线程休眠
    (2)t.join() / t.join(long) —>t线程加入当前线程,当前线程等待long或t线程执行完
    (3)synchronized—>竞争所失败的线程,进入阻塞态 (竞争失败的线程不停的再阻塞态和运行态之间切换(被JVM唤醒再次竞争对象锁),如果竞争失败的线程数量很多,对系统的性能影响较大)
    synchronized的使用前提:多线程对同一个共享变量的操作(更新)
  2. 线程的中断:通过设置中断标志位让某个线程中断,而不是停止线程,是一个“建议“,是否中断由该线程代码决定

(三)线程状态转换
在这里插入图片描述

多线程案例 --单例模式 代码

class SingletonTest {
//    //1.饿汉模式    
//    //一个类里提供一个类的实例对象  
//    private static SingletonTest instance = new SingletonTest();
//    private SingletonTest(){}
//    public static SingletonTest getInstance(){
//        return instance;
//    }

//    //2.懒汉模式---单线程
//    private static SingletonTest instance = null;
//    private SingletonTest(){}
//    public static SingletonTest getInstance(){
//        if (instance == null) {
//            instance = new SingletonTest();
//        }
//        return instance;
//    }

//    //3.懒汉模式---多线程,低性能
//    private static SingletonTest instance = null;
//    private SingletonTest(){}
//    public synchronized static SingletonTest getInstance(){
//        //第一次实例化之后,应该允许多线程并行并发执行获取同一个对象
//        if (instance == null) { 
//            instance = new SingletonTest();
//        } //第一次是写操作,之后全是读操作
//        return instance;
//    }

    //4.懒汉模式---多线程,二次判断,高性能
    private static volatile SingletonTest instance = null; 
    //volatile 保证可见性(工作内存和主存,每次操作都是在主存中操作),禁止指令重排序,不保证原子性(赋值操作本身不具备原子性)
    //特别注意的非原子性代码:一行代码分解为多条指令(n++,n--,++n,--n,new对象【1.创建初始化内存空间2.new对象3.赋值给共享变量】,若是允许重排序)
    //TODO volatile常见的使用场景:一般进行读写分离操作,提供性能
    //                 1.写操作不依赖共享变量,赋值是一个常量(依赖共享变量的赋值不是原子性操作)
    //                 2.  作用在读,写依赖于其他手段(加锁)
    private SingletonTest(){}
    public  static SingletonTest getInstance(){
        if (instance == null) {
            synchronized (SingletonTest.class) { //竞争对象锁,多个线程使用同一个对象
                if (instance == null) {
                    instance = new SingletonTest(); //不满足原子性
                }
            }
        }
        return instance;
    }
}

分析:volatile不禁止指令重排序,会有什么问题?

我们将程序分解为如下步骤:

在这里插入图片描述

如果volatile不禁止指令重排序,则步骤4-2和4-3的顺序是无法确定的。假设有两个线程(线程A,线程B),A,B竞争对象锁,A竞争成功,进入第4步,并先执行了4-3而没有执行4-2,此时因为instance已经非null。这时候线程B执行到了4-1处,判断instance非null并将其返回使用,因为此时SingletonTest实际上还未初始化(引用指向了内存空间,保存的是还没有初始化完的无效对象),就会出错。
volatile不禁止指令重排序的作用:分解后的指令有volatile修饰的变量,那么这行(4-3,建立了内存屏障)禁止指令重排序

线程安全单例模式:双重校验锁
满足:
(1)性能:初始化对象操作完成之后(4-2完成),其他线程进入第一行(并行并发)
(2)线程安全:同时初始化操作(多个线程同时进入1)使用加锁操作

再次校验是因为单例模式需要

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值