Java 线程安全特性与问题

如果你的代码在单线程下或者在多线程下执行都能获得一样的结果,那么你的代码就是线程安全的。当进行多线程编程时,我们又会面临哪些线程安全的要求呢?又该如何去解决的呢?

有序性

有序性指的是,程序执行的顺序按照代码的先后顺序执行。以下面这段代码为例:

boolean started = false; // 语句1
long counter = 0L; // 语句2
counter = 1; // 语句3
started = true; // 语句4

从代码顺序上看,上面四条语句应该依次执行,但实际上 JVM 真正在执行这段代码时,并不保证它们一定完全按照此顺序执行。

处理器为了提高程序整体的执行效率,可能会对代码进行优化,其中的一项优化方式就是调整代码顺序,按照更高效的顺序执行代码。

讲到这里,有人要着急了——什么,CPU 不按照我的代码顺序执行代码,那怎么保证得到我们想要的结果呢?实际上,大家大可放心,CPU 虽然并不保证完全按照代码顺序执行,但它会 保证程序最终的执行结果和代码顺序执行时的结果一致。

死锁

指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

关于死锁发生的条件:

  • 如果一个线程占用了某资源,那么其他线程必须处于等待状态,直到该资源被释放。

  • 线程 T1 至少已经保持了对一个资源 R1 的占用,但又提出对另一个资源 R2 的请求,而此时,资源 R2 被另外一个线程 T2 占用,于是该线程 T1 也必须等待,但又对自己保持的资源 R1 不释放。

  • 线程已获得的资源,在未使用完之前,不能被其他线程剥夺,只能在使用完以后由自己释放。

  • 最直观的理解是,p0 等待 p1 占用的资源,而 p1 而在等待 p0 占用的资源,于是两个进程就相互等待

活锁

线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2 也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。

马路中间有条小桥,只能容纳一辆车经过,桥两头开来两辆车A和B,A比较礼貌,示意B先过,B也比较礼貌,示意A先过,结果两人一直谦让谁也过不去。

饥饿

如果一个线程因为 CPU 时间全部被其他线程抢走而得不到 CPU 运行时间,这种状态被称之为 饥饿。而该线程被 饥饿致死 正是因为它得不到 CPU 运行时间的机会。(例如:高优先级线程会吞噬掉所有的低优先级线程的 CPU 时间。)

公平

Java 不可能实现 100% 的公平性,依然可以通过同步结构在线程间实现公平性的提高。

首先来学习一段简单的同步态代码:

public class Synchronizer{
    public synchronized void doSynchronized () {
        // do a lot of work which takes a long time
    }
}

如果有多个线程调用 doSynchronized() 方法,在第一个获得访问的线程未完成前,其他线程将一直处于阻塞状态,而且在这种多线程被阻塞的场景下,接下来将是哪个线程获得访问是没有保障的。为了提高等待线程的公平性,改为 使用锁方式替代同步块。

public class Synchronizer{
    Lock lock = new Lock();
    public void doSynchronized() throws InterruptedException{
        this.lock.lock();
        // Critical section, do a lot of work which takes a long time
        this.lock.unlock();
    }
}

常用的保证 Java 操作原子性的工具是 锁和同步方法(或者同步代码块)。使用锁,可以保证同一时间只有一个线程能拿到锁,也就保证了同一时间只有一个线程能执行申请锁和释放锁之间的代码。

public void testLock () {
    synchronized (anyObject){
        int j = i;
        i = j + 1;
    }
}

CAS(compare and swap)

基础类型变量自增(i++)是一种常被新手误以为是原子操作而实际它不是原子操作。Java 中提供了对应的 原子操作类 来实现该操作,并保证原子性,其本质是利用了 CPU 级别的 CAS 指令。由于是 CPU 级别的指令,其开销比需要操作系统参与的锁的开销小。AtomicInteger 使用方法如下:

AtomicInteger atomicInteger = new AtomicInteger();
for(int b = 0; b < numThreads; b++) {
    new Thread(() -> {
        for(int a = 0; a < iteration; a++) {
            atomicInteger.incrementAndGet();
        }
    }).start();
}

如何确保可见性?

Java 提供了 volatile 关键字来保证可见性。当使用 volatile 修饰某个变量时,它会保证对该变量的修改会立即被更新到内存中,并且将其它线程对该变量的缓存设置成无效,因此其它线程需要读取该值时必须从主内存中读取,从而得到最新的值。volatile 适用于不需要保证原子性,但却需要保证可见性的场景。

关于线程安全的几个为什么?

1,平时项目中使用锁和 synchronized 比较多,而很少使用 volatile,锁和 synchronized 保证可见性吗?

锁和 synchronized 即可以保证原子性,也可以保证可见性。都是通过保证同一时间只有一个线程执行目标代码段来实现的。

2,锁和 synchronized 为何能保证可见性?

根据 JDK 7的Java doc 中对concurrent包的说明,一个线程的写结果保证对另外线程的读操作可见,只要该写操作可以由 happen-before 原则推断出在读操作之前发生。

3,既然锁和 synchronized 可保证原子性也可保证可见性,为何还需要volatile

synchronized 和锁需要通过 操作系统 来仲裁谁获得锁,开销比较高,而volatile 开销小很多。因此在只需要保证可见性的条件下,使用 volatile 的性能要比使用锁和 synchronized 高得多。

4,既然锁和 synchronized 可以保证原子性,为什么还需要 AtomicInteger 这种的类来保证原子操作?

锁和 synchronized 需要通过 操作系统 来仲裁谁获得锁,开销比较高,而AtomicInteger 是通过 CPU 级的 CAS 操作来保证原子性,开销比较小。所以使用 AtomicInteger 的目的还是为了提高性能。

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

推荐阅读更多精彩内容