JUC第二课——volatile关键字、锁优化和CAS

volatile关键字

意思是可变的
作用:

  1. 保证线程可见性(每次写都会被线程读到)
    • MESI:使用CPU的缓存一致性协议
  2. 禁止指令重排序(CPU)
    • DCL单例: Double Check Lock

单例模式加载类对象

饿汉式

package singleton;

/**
 * 饿汉式
 * 类加载到内存后,就实例化一个实例,JVM保证线程安全
 * 简单实用,推荐使用
 * 唯一缺点:不管用不用到,类装载的过程就完成了实例化
 * Class.forName("")
 * @author laimouren
 */
public class singleton01 {
    private static final singleton01 INSTANCE = new singleton01();
    //构造函数被设置为私有,不允许其他方法实例化该类
    private singleton01(){};
    public static singleton01 getInstance(){
        return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args) {
        singleton01 s1 = singleton01.getInstance();
        singleton01 s2 = singleton01.getInstance();
        System.out.println(s1 == s2);
    }
}

懒汉式

package singleton;

/**
 * 懒加载
 * 也称懒汉式
 * 用到的时候再加载
 *但是线程不安全
 * 可以通过synchronized解决,但是效率会下降
 * @author laimouren
 */
public class singleton02 {
    private static singleton02 INSTANCE;
    private singleton02(){};
    public static /*synchronized*/ singleton02 getInstance(){
        if (INSTANCE == null){
            try{
                Thread.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            INSTANCE = new singleton02();
        }
        return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100;i++){
            new Thread(()->{
                System.out.println(singleton02.getInstance().hashCode());
            }).start();
        }
    }
}

优化:双重检查

package singleton;

/**
 * 懒加载
 * 也称懒汉式
 * 用到的时候再加载
 *但是线程不安全
 * 可以通过synchronized解决,但是效率会下降
 * @author laimouren
 */
public class singleton03 {
    private static /*volatile*/ singleton03 INSTANCE;
    private singleton03(){};
    public static singleton03 getInstance(){
    	//业务逻辑代码省略
    	
        if (INSTANCE == null) {
            //双重检查
            synchronized (singleton03.class) {
                if (INSTANCE == null){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new singleton03();
                }
            }
        }
        return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100;i++){
            new Thread(()->{
                System.out.println(singleton03.getInstance().hashCode());
            }).start();
        }
    }
}

问题:双重检查需不需要加volatile?
要,因为不加会产生指令重排序,
对象初始化一半的情况下,instance对象有默认值,这时候另一个线程返回了instance,导致出现instance还是默认值的情况
(因为JVM去new一个对象,首先要申请内存(成员变量默认值),然后成员变量初始化,最后将内存地址赋给对象)

volatile能否代替synchronized

不能!

package character02;

import java.util.ArrayList;
import java.util.List;

/**
 * volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是volatile不能替代synchronized
 * @author laimouren
 */
public class volatile02 {
    volatile int count = 0;
    /*synchronized*/ void m(){
        for (int i = 0; i < 100000; i++) {
            count++;
        }
    }

    public static void main(String[] args) {
        volatile02 v = new volatile02();
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(v::m,"thread-"+i));
        }
        threads.forEach((o)->o.start());
        threads.forEach((o)->{
            try{
                o.join();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        });
        System.out.println(v.count);
    }
}

锁优化

细锁(FineCoarseLock)
同步代码块的语句越少越好

锁细化

package character02;

import java.util.concurrent.TimeUnit;

/**
 * 锁细化
 * @author laimouren
 */
public class Synchronzied {
    private int count = 0;
    synchronized void m1(){
        try{
            TimeUnit.SECONDS.sleep(2);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        count++;
        try{
            TimeUnit.SECONDS.sleep(2);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    void m2(){
        try{
            TimeUnit.SECONDS.sleep(2);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        //业务逻辑只有下面一句话需要sync,这时候不应该给整个方法上锁
        //采用细粒度的锁,可以使线程争用时间变短,从而提高效率
        synchronized(this) {
            count++;
        }
        try{
            TimeUnit.SECONDS.sleep(2);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

但是如果太多线程同时争用锁,可以将锁粗化来减少争用

锁对象需加final修饰

package character02;

import java.util.concurrent.TimeUnit;

/**
 * 锁定某个对象o,如果o的属性发生改变,不影响锁的使用
 * 但是如果o变成了另一个对象,则锁定的对象发生改变
 * 应该避免将锁定对象的引用变成另外的对象
 * @author laimouren
 */
public class Synchronzied02 {
	//使用final来保证对象不可变
    /*final*/ Object o = new Object();
    void m(){
        synchronized (o){
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(1);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    public static void main(String[] args) {
        Synchronzied02 s = new Synchronzied02();
        //启动第一个线程
        new Thread(s::m,"s1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        //创建第二个线程
        Thread t2 = new Thread(s::m,"s2");
        //锁对象发生改变,所以t2线程能够执行,如果注释掉,t2永远不能执行
        s.o = new Object();
        t2.start();
    }

}

CAS(无锁优化 自旋)

Compare And Swap
cas(V,Expected,NewValue)

  • if v == E
    V = new
    otherwise try again or fail
  • CPU原语支持
    解释:如果v值等于期望(expected)的,则将v修改为newValue值
    是cpu指令级别的操作,不能被打断

java.util.concurrent.atomic.Atomic*的类都是使用CAS来保证线程安全

package character02;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 解决同样问题的更高效的方法,使用AtomXX类
 * AtomXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性
 * @author laimouren
 */
public class CAS01 {
	/*volatile*/ //int count = 0; 
    AtomicInteger count = new AtomicInteger(0);
    /*synchronized*/void m(){
        for (int i = 0; i < 10000; i++) {
            count.incrementAndGet(); //相当于count++,只是它是线程安全的
        }
    }

    public static void main(String[] args) {
        CAS01 c = new CAS01();
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(c::m,"thread-"+i));
        }
        threads.forEach((o)->o.start());
        threads.forEach((o)->{
            try{
                o.join();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        });
        System.out.println(c.count);
    }
}

内部调用unsafe类里面的CompareAndSet方法

ABA问题

如果基本类型,无所谓
如果引用类型,就像你女朋友跟你复合,已经是别人的形状了
解释:
两个线程t1,t2;
t1执行cas(Object,1,2),t2先将Object改成了2,再改回1,这时候不影响t1的cas操作,但实际上已经有东西发生了修改
解决:给Object加上一个版本号,修改一次版本号加一
A 1.0
B 2.0
A 3.0

可以使用AtomicStampedReference解决ABA问题

Unsafe类(类似c c++的指针)

不能直接使用

功能:

  1. 直接操作内存
  2. 直接生成类实例
  3. 直接操作类或类变量
  4. CAS相关操作
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值