java大话锁机制02

上次我大致介绍了一下锁这个抽象概念是个啥,java语言是如何实现互斥锁的,互斥锁的工作原理,以及java对互斥锁的优化,锁的四种状态

无锁编程(其实和上期没太多联系)

cas是啥?~~之前看面经常看到的问题

juc又是啥?

aqs又是啥,具体有啥例子能说说嘛?有一说一按都说不明白,所以花时间恶补在这班门弄斧啦~

假设现在有多个线程想要操作同一个资源对象,很多人包括我第一反应就是加锁呀,但是互斥锁的同步方式是悲观的也就是重锁但是在一些只是读操作的线程使用悲观锁就太重了,咱没必要在每次调用的时候都去锁定资源。所以我们就不要过多使用互斥锁。所以诞生了一种非常巧妙的算法**CAS**,比较然后交换

img

对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

CAS比较与交换的伪代码可以表示为:

do{

备份旧数据;

基于旧数据构造新数据;

}while(!CAS( 内存地址,备份的旧数据,新数据 ))

注:t1,t2线程是同时更新同一变量56的值

因为t1和t2线程都同时去访问同一变量56,所以他们会把主内存的值完全拷贝一份到自己的工作内存空间,所以t1和t2线程的预期值都为56。

假设t1在与t2线程竞争中线程t1能去更新变量的值,而其他线程都失败。(失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试)。t1线程去更新变量值改为57,然后写到内存中。此时对于t2来说,内存值变为了57,与预期值56不一致,就操作失败了(想改的值不再是原来的值)。

(上图通俗的解释是:CPU去更新一个值,但如果想改的值不再是原来的值,操作就失败,因为很明显,有其它操作先改变了这个值。)

就是指当两者进行比较时,如果相等,则证明共享数据没有被修改,替换成新值,然后继续往下运行;如果不相等,说明共享数据已经被修改,放弃已经所做的操作,然后重新执行刚才的操作。容易看出 CAS 操作是基于共享数据不会被修改的假设,采用了类似于数据库的commit-retry 的模式。当同步冲突出现的机会很少时,这种假设能带来较大的性能提升。(因为是操作系统假设是x86的 就是cmpxchg指令支持cas 而 ARM下则是LL/SC实现cas 不需要操作系统的同步原语mutx相对效率更高)

现在我提供了一个简单的需求~假设你需要使用三条线程,将一个值从0累加到100你会怎么做?

以下是错误用例,我们发现多条线程打印了相同的值,则说明线程之间没有正确通信。

//假设你需要使用三条线程,将一个值从0累加到100你会怎么做?
public class test01 {
    static Integer num = 0;
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (num<100){
                        System.out.println("thread name"+Thread.currentThread().getName()+":"+num++);
                    }
                }
            });
            t.start();
        }
    }
}


thread nameThread-1:0
thread nameThread-2:1
thread nameThread-0:0
thread nameThread-0:4
    
最常规的我们可以通过互斥锁来进行线程同步
    public class test01 {
    static Integer num = 0;
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (test01.class){
                        while (num<100){
                            System.out.println("thread name"+Thread.currentThread().getName()+":"+num++);
                        }
                    }
                }
            });
            t.start();
        }
    }
}
此时线程同步了
    
    但是这并不是我们这次讲的重点,我们应该讲的是无锁,如何无锁实现呢? 感谢前人造的轮子(使用AtomicInteger)实现了线程通信。
    
    //假设你需要使用三条线程,将一个值从0累加到100你会怎么做?
public class test01 {
//    static Integer num = 0;
    static AtomicInteger num = new AtomicInteger(0);
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
 //                   synchronized (test01.class){
                        while (num.get()<100){
                            System.out.println("thread name"+Thread.currentThread().getName()+":"+num.incrementAndGet());
                        }
                    }
 //               }
            });
            t.start();
        }
    }
}

可我们关注的不是实现过程,而是他们的底层是如何通过cas来做到无锁同步的

又到了我最喜欢(不是)的扒源码的时候了
AtomicInteger的主要成员变量就是一个unsafe类型的实例和一个long类型的offset
// setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    
    我们进入incrementAndGet方法可以看到直接调用了unsafe对象的getAndAddInt方法
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
    再点进去
    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }
    果然是用到了unsafe的compareAndSwapInt也就是cas方法
    
    while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    我们注意到上面出现了一个循环,实际上这个就是自旋
    那么有没有可能会一直死循环自旋呀,实际上可以通过调参的方式配置的默认是10,所以不会出现死循环的

借张图 ~

img

AtomicInteger.incrementAndGet的实现用了乐观锁技术,调用了类sun.misc.Unsafe库里面的 CAS算法,用CPU指令来实现无锁自增。所以,AtomicInteger.incrementAndGet的自增比用synchronized的锁效率倍增。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352