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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容