J2SE复习内容 - 多线程进阶

在之前的一篇文章中,简单梳理了多线程的一些基础。这篇文章要说一些相关的更深入的知识。

系列目录:
J2SE复习内容 - 多线程基础
J2SE复习内容 - 多线程进阶

0. 原子性,可见性和指令重排

在了解相关的知识之前,需要首先掌握这几个概念。

原子性

顾名思义,一个操作如果是不可再分的,那么就称它是原子性的,在多线程环境下,如果一个操作是原子性的,那么就可以保证它在执行时刻不会被中途打断,从而保证数据的安全。

我们常见的Atomic相关类就是原子类,但是对于long和double类型的赋值不是原子性

可见性

可见性是指当前一个线程修改了共享变量的值,其他线程能够立即得知这个修改,我们一般使用volatile关键字来保证多线程操作的变量可见性,而普通的变量却不能保证这一点。
具体的实现可以看后面的文章。

指令重排 (或者有序性)

指令重排是指JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序。指令重排的目的是为了在不改变程序执行结果的前提下,优化程序的运行效率。但是在某些情况下,会影响到多线程的执行结果。使用volatile关键字也可以禁止指令重排,具体的实现可以看后面的文章。

梳理一下。
sychronized只能实现原子性(有序性)
volatile只能实现可见性和有序性

1. 可重入原理

我们所见的显式锁reentrantlock和隐式锁sychronized都是可重入的锁,可重入意味着在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是可以拿到锁的。同样有继承关系的情况下,也可以拿到子类的锁执行父类的方法。
那我们怎么能得知一个锁是不是可重入的?按照定义来看,我们写了如下的一段简单代码。

public class Lock {
    public synchronized void m1(){
        System.out.println("m1");
        m2();
    }
    public synchronized void m2(){
        System.out.println("m2");
    }
}

当一个线程执行了m1方法,获得了锁,又想要在m1中调用m2方法的时候,还可以继续获得这把锁(同样的锁),

Q1:那具体是什么原理呢?

每一个锁关联一个线程持有者计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。

2. 乐观锁与悲观锁

乐观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁,如我们的reentrantlock和sychronized

乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。使用乐观锁可以在一定程度上提高吞吐量

对于CAS不太了解的童鞋可以先科普什么是 CAS 机制,

Q2:那么选择悲观锁还是乐观锁呢?

那么很显然CAS要不断的做写时判断,所以在多写的情境下,很容易出现ABA问题或者CPU高压力,那么此时用悲观锁就比较好。
但是当少写的情况下,CAS的轻量既保证了效率,又提高了吞吐量(其实是一个东西吧),所以比较适合。

3. sychronized和ReentrantLock的对比

回顾一下使用sychronized的情况

synchronized (lockObject) { 
  // update object state
}

在回顾一下ReentrantLock的使用情况

Lock lock = new ReentrantLock();
lock.lock();
try { 
  // update object state
}
finally {
  lock.unlock(); 
}

然后我们根据积累的经验来分析这个问题。

首先java先提供了sychronized这个关键字来实现同步,那这个关键字有什么缺陷呢?

在sychronized没有优化之前,一直是以重量级锁的身份存在的,并且我们都知道,sychronized是系统控制的锁,如果一个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能等待,非常影响程序执行效率。

同时考虑下面一种情况,对于一个文件,写操作和写操作会发生冲突,写操作和读操作会发生冲突,读操作和读操作不会冲突。但我们使用sychronized的时候,所有操作必须排队,而使用Lock就可以做到同时读。

那ReentrantLock又有什么优势和缺陷呢?
在总结之前,我们要看一下它的实现,从而才能得出结论。



实现了Lock接口,那么我们关注一下Lock接口内部的方法。

public interface Lock {
   /*锁定*/
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    /*解锁*/
    void unlock();
    /*线程协作工具*/
    Condition newCondition();
}

最上面4个方法都是锁定,接下来是一个解锁方法,最后是一个线程协作工具,我们在这里重点分析一下前4个方法是有什么区别。

首先是最普通的lock()方法,用来获取一个锁,不多解释。
然后是lockInterruptibly()方法,字面意思可以看出来是允许被打断,再看看两者的注释。



前者是获取一个lock,而后者是在当前线程没有被interrupted的情况下就获取一个锁。

再通俗的联系一下,我们在线程中经常使用Sleep() 方法,这个方法要求处理一个InterruptedException 异常,当外界要打断此线程的时候只需要调用 目标线程的interrupt方法即可打断。

lockInterruptibly()和上面的情况是一样的, 线程在请求lock并被阻塞时,如果被interrupt,则“此线程会被唤醒并被要求处理InterruptedException”。

Q3:则两者的区别再总结一下吧:
当一个线程获取锁的时候使用的是lockInterruptibly()方法,那么他在等待过程中被别人调用了自己的interrupt() 方法就会被打断。

【调用者:等什么呢在这?今晚女神是我的,我已经锁门了,你赶紧回家睡觉去吧!】
【被调用者:哼!回就回!】

当一个线程获取锁的时候是使用lock()方法,那么他在等待的过程中不会被打断,傻乎乎的一直等下去。

【线程A:讲道理今晚临界区女神应该跟我共度良宵的吧,我再等等吧,说不定一会他的门就开了呢!】
【临界区女神:线程B,啊…… (自行脑补)】
【线程B:我今天不开门,你们就等着吧!】

下面还有两个tryLock的方法,这样说起来就很简单了,

引用 http://www.dewen.net.cn/q/9077 评论中的一句话。

lock(), 拿不到lock就不罢休,不然线程就一直block。 比较无赖的做法。
tryLock(),马上返回,拿到lock就返回true,不然返回false。 比较潇洒的做法。
带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false。比较聪明的做法。

这样解释后,我们会发现Lock会比sychronized多一些很自由的方法,一会总结。

再来看看ReentrantLock,中文翻译为可重入锁,所以证实了这个锁也是可重入的,关于可重入上面有提到过。
刚才讲,它实现了Lock接口,我们来看一下它的相关构造方法。


可以看到我们可以通过fair参数来确定初始化一个公平锁还是非公平锁。关于公平与否很好理解。
公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这种就是公平锁。

可以想象得到,实现非公平锁,只需要随机选一个线程即可,而实现公平锁就需要复杂的计算方法,相对投入也就变大了。

sychronized就是非公平锁。

那么现在我们来回答下面这个问题。

Q4: sychronized和ReentrantLock的区别

  1. sychronized 是一个关键字,ReentrantLock是一个类。
  2. 很显然,sychronized不需要我们手动锁定和手动解锁,而ReentrantLock需要我们手动锁定和解锁,如果不在finally里面手动解锁,将会发生意想不到的致命问题。
  3. sychronized是非公平锁,而ReentrantLock是可以做到公平锁的。
  4. sychronized是不可中断锁(在锁定的时候),而ReentrantLock是可以中断的锁(lockInterruptibly)
  5. ReentrantLock有相应的tryLock尝试锁定方法,可以返回锁定状态,而sychronized没有相应机制。
  6. 在jdk1.7之前,sychronized比较重,所以Lock效率更高,在之后sychronized有所优化,所以效率相差不大

补充一点:Lock可以提高多个线程进行读操作的效率。(使用ReentrantReadWriteLock,具体可以去查看相应资料)

4.Volatile和sychronized对比

上面我们简单的提到了Volatile和sychronized的对比,在这里我们再简单的回顾一下。

不了解Volatile的可以先补习:什么是 volatile 关键字?

Q5: Volatile和sychronized区别

  1. volatile 保证了可见性和禁止了指令重排,而sychronized保证了原子性和可见性,这就意味着Volatile在多线程条件下还是不能保证数据的修改安全性,(要理解两者的使用场景。)
  2. volatile只能使用在变量级别;synchronized则可以使用在方法、代码段等。
  3. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

5. 总结

上面总结了一些面试常见问题,也详细解释了一些隐藏很深的知识点,对于深层原理,后面还会有文章详细介绍。

这些内容都应该属于基础部分,是对于并发学习必须所掌握的内容。

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