synchronized,volatile与Atomic简介

线程安全需要保证几个基本特性:

  • 原子性:相关操作不会中途被其他线程干扰,一般通过同步实现
  • 可见性:一个线程修改了某个共享变量,其状态能够立即被其它线程知晓
  • 有序性:保证线程内串行语义,避免指令重排等

1. synchronized

1.1 sychronized(class)代码块与静态同步synchronized方法

两者锁定的都是对应的class,在效果上是等价的,sychronized(class)代码块的粒度会小一些

public class SynchronizedTask {
    // sychronized(class)代码块
    public static void printA() {
        synchronized (SynchronizedTask.class) {
            try{
                System.out.println("线程" + Thread.currentThread().getName() + "开始进入方法printA, 时间:" + System.currentTimeMillis());
                Thread.currentThread().sleep(3000);
                System.out.println("线程" + Thread.currentThread().getName() + "开始退出方法printA, 时间:" + System.currentTimeMillis());
            }catch (Exception e) {
                System.out.println(e.getMessage());
            }

        }
    }

    // 静态同步synchronized方法
    synchronized public static void printB() {
        try{
            System.out.println("线程" + Thread.currentThread().getName() + "开始进入方法printB, 时间:" + System.currentTimeMillis());
            Thread.currentThread().sleep(3000);
            System.out.println("线程" + Thread.currentThread().getName() + "开始退出方法printB, 时间:" + System.currentTimeMillis());
        }catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}
public class ThreadA extends Thread {
    @Override
    public void run() {
        SynchronizedTask.printA();
    }
}
public class ThreadB extends Thread {
    @Override
    public void run() {
        SynchronizedTask.printB();
    }
}
public class SynchronizedMain {
    public static void main(String[] args) {
        ThreadA threadA = new ThreadA();
        threadA.setName("A");
        threadA.start();

        ThreadB threadB = new ThreadB();
        threadB.setName("B");
        threadB.start();

    }
}

由于线程A先持有class锁,因此线程A执行完printA方法后,线程B才获得class锁,执行printB方法。
image.png

1.2 synchronized方法和sychronized(this)代码块

两者锁定的都是当前对象,在效果上是等价的,sychronized(this)代码块的粒度会小一些

package Synchronized.synchronized_current_object;

/**
 * Created by xq on 2018/7/5.
 */
public class SynchronizedTask {
    // sychronized(this)代码块
    public void printA() {
        synchronized (this) {
            try{
                System.out.println("线程" + java.lang.Thread.currentThread().getName() + "开始进入方法printA, 时间:" + System.currentTimeMillis());
                java.lang.Thread.currentThread().sleep(3000);
                System.out.println("线程" + java.lang.Thread.currentThread().getName() + "开始退出方法printA, 时间:" + System.currentTimeMillis());
            }catch (Exception e) {
                System.out.println(e.getMessage());
            }

        }
    }

    // synchronized方法
    synchronized public void printB() {
        try{
            System.out.println("线程" + java.lang.Thread.currentThread().getName() + "开始进入方法printB, 时间:" + System.currentTimeMillis());
            java.lang.Thread.currentThread().sleep(3000);
            System.out.println("线程" + java.lang.Thread.currentThread().getName() + "开始退出方法printB, 时间:" + System.currentTimeMillis());
        }catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}
public class ThreadA extends Thread {
    private SynchronizedTask synchronizedTask;

    public ThreadA(SynchronizedTask synchronizedTask) {
        super();
        this.synchronizedTask = synchronizedTask;
    }

    @Override
    public void run() {
        synchronizedTask.printA();
    }
}
public class ThreadB extends Thread {
    private SynchronizedTask synchronizedTask;

    public ThreadB(SynchronizedTask synchronizedTask) {
        super();
        this.synchronizedTask = synchronizedTask;
    }

    @Override
    public void run() {
        synchronizedTask.printB();
    }
}
public class SynchronizedMain {
    public static void main(String[] args) {
        SynchronizedTask synchronizedTask = new SynchronizedTask();
        ThreadA threadA = new ThreadA(synchronizedTask);
        threadA.setName("A");
        threadA.start();

        ThreadB threadB = new ThreadB(synchronizedTask);
        threadB.setName("B");
        threadB.start();

    }
}

由于线程A先持有当前对象锁(synchronizedTask),因此线程A执行完printA方法后,线程B才获得对象锁,执行printB方法。
image.png

1.3 sychronized(非this对象)

java支持对“任意对象”作为对象监视器来实现同步的功能。锁非this对象具有一定的优点:如果在一个类中有很多个sychronized方法。这时不同线程调用这些方法虽然能实现同步,但会受到阻塞,影响运行效率。如果使用同步代码块锁非this对象,不同对象监视器所在的方法是异步执行的,从而提高运行效率。

public class SynchronizedTask {
    private Object object1;
    private Object object2;

    public SynchronizedTask(Object object1, Object object2) {
        this.object1 = object1;
        this.object2 = object2;
    }

    // synchronized(object1)代码块
    public void printA() {
        synchronized (object1) {
            try{
                System.out.println("线程" + java.lang.Thread.currentThread().getName() + "开始进入方法printA, 时间:" + System.currentTimeMillis());
                java.lang.Thread.currentThread().sleep(3000);
                System.out.println("线程" + java.lang.Thread.currentThread().getName() + "开始退出方法printA, 时间:" + System.currentTimeMillis());
            }catch (Exception e) {
                System.out.println(e.getMessage());
            }

        }
    }

    // synchronized(object2)代码块
    public void printB() {
        synchronized (object2) {
            try{
                System.out.println("线程" + java.lang.Thread.currentThread().getName() + "开始进入方法printA, 时间:" + System.currentTimeMillis());
                java.lang.Thread.currentThread().sleep(3000);
                System.out.println("线程" + java.lang.Thread.currentThread().getName() + "开始退出方法printA, 时间:" + System.currentTimeMillis());
            }catch (Exception e) {
                System.out.println(e.getMessage());
            }

        }
    }
}
public class ThreadA extends Thread {
    private SynchronizedTask synchronizedTask;

    public ThreadA(SynchronizedTask synchronizedTask) {
        super();
        this.synchronizedTask = synchronizedTask;
    }

    @Override
    public void run() {
        synchronizedTask.printA();
    }
}
public class ThreadB extends Thread {
    private SynchronizedTask synchronizedTask;

    public ThreadB(SynchronizedTask synchronizedTask) {
        super();
        this.synchronizedTask = synchronizedTask;
    }

    @Override
    public void run() {
        synchronizedTask.printB();
    }
}
public class SynchronizedMain {
    public static void main(String[] args) {
        Object object1 = new Object();
        Object object2 = new Object();

        SynchronizedTask synchronizedTask = new SynchronizedTask(object1, object2);
        ThreadA threadA = new ThreadA(synchronizedTask);
        threadA.setName("A");
        threadA.start();

        ThreadB threadB = new ThreadB(synchronizedTask);
        threadB.setName("B");
        threadB.start();

    }
}

线程A持有对象锁object1,开始执行printA方法后,线程B持有对象锁object2,执行printB方法,两个线程之间互不影响。
image.png

1.4 思考

1.4.1 synchronized最终是对对象上锁

synchronized(class)锁的是类对象,synchronized(object)锁的是实例对象

1.4.2 synchronized能同时保证原子性和可见性

在Java内存模型中,synchronized规定,线程在加锁时,先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁

2. volatile

2.1 保证可见性

volatile可以保证变量在多个线程之间的可见性,也即每个线程都能够自动发现 volatile 变量的最新值。

2.2 不保证原子性

public class VolatileThread extends Thread {
    volatile public static int count;

    @Override
    public void run() {
        addCount();
    }

    private static void addCount() {
        for(int i = 0; i < 100; i++) {
            count++;
        }
        System.out.println("count=" + count);
    }
}
public class VolatileMain {
    public static void main(String[] args) {
        VolatileThread[] threads = new VolatileThread[100];

        for(int i = 0; i < 100; i++) {
            threads[i] = new VolatileThread();
        }

        for(int i = 0; i < 100; i++) {
            threads[i].start();
        }
    }
}

count最后的值不是10000

下面详细解释下上例中volatile出现非线程安全的原因:
变量在内存中的工作过程

(1)read和load阶段:从主存复制变量到当前线程工作内存
(2)use和assign阶段:执行代码,改变共享变量值
(3)store和write阶段:用工作内存数据刷新主存对应变量的值

  • volatile的可见性保证,假如线程1和线程2在进行read和load操作中,发现主内存中count的值都是5,那么都会加载这个最新的值。
  • 线程1对count进行加1操作,最后将count=6刷新到主内存
  • 线程2执行时,由于已经进行read和load操作,会在5的基础上进行加1操作,最后将count=6刷新到主内存

2.3 思考

一般在多线程中使用volatile变量,为了安全,对变量的写入操作不能依赖当前变量的值:如Num++或者Num=Num5这些操作

3. Atomic

synchronized会导致线程的阻塞,从而降低性能。针对i++,i=i+100等操作,使用synchronized未免太重了,可通过使用原子类Atomic实现非阻塞同步,它可以在没有锁的情况下做到线程安全。

public class AtomicLongTask {
    private AtomicLong count = new AtomicLong(0);

    public void addCount(){
        long startTime = System.currentTimeMillis();
        System.out.println(String.format("线程%s执行开始时间为%d", Thread.currentThread().getName(), startTime ));

        for(int i = 0; i< 1000000000; i++){
            count.addAndGet(1);
        }

        long endTime = System.currentTimeMillis();
        System.out.println(String.format("线程%s执行结束时间为%d", Thread.currentThread().getName(), endTime ));
        System.out.println(String.format("线程%s执行时间为%d秒", Thread.currentThread().getName(), (endTime - startTime)/1000 ));
        System.out.println(String.format("线程%s, count: %d秒", Thread.currentThread().getName(), count.get() ));

    }

    public AtomicLong getCount() {
        return count;
    }

    public void setCount(AtomicLong count) {
        this.count = count;
    }
}
public class AtomicLongThread extends Thread{
    AtomicLongTask atomicLongTask;

    public AtomicLongThread(AtomicLongTask atomicLongTask) {
        this.atomicLongTask = atomicLongTask;
    }

    @Override
    public void run() {
        atomicLongTask.addCount();
    }
}
public class AtomicLongMain {

    // A线程和B线程分别执行1000000000次++操作, 总耗时为42s左右,使用AtomicLong,性能明显优于Synchronized
    public static void main(String[] args){
        AtomicLongTask atomicLongTask = new AtomicLongTask();

        AtomicLongThread t1 = new AtomicLongThread(atomicLongTask);
        t1.setName("A");
        AtomicLongThread t2 = new AtomicLongThread(atomicLongTask);
        t2.setName("B");

        t1.start();
        t2.start();
    }
}

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

推荐阅读更多精彩内容