并发编程中的volatile、synchronized和lock

Java并发编程中经常会用到synchronized、volatile和lock,三者都可以解决并发问题,这里做一个总结。

1、volatile

volatile保证了共享变量的可见性,也就是说,线程A修改了共享变量的值时,线程B能够读到该修改的值。但是,对任意单个volatile变量的读/写具有原子性,但类似于i++这种复合操作不具有原子性。因此,volatile最适用一个线程写,多个线程读的场合。

2、synchronized

synchronized是Java中的关键字,可以用来修饰变量、方法或代码块,保证同一时刻最多只有一个线程执行这段代码。

修饰普通方法(实例方法):

synchronized修饰普通方法时锁的是当前实例对象 ,进入同步代码前要获得当前实例对象的锁。线程A和线程B调用同一实例对象的synchronized方法时才能保证线程安全,若调用不同对象的synchronized方法不会出现互斥的问题。对比如下两段代码:

public class TestSync implements Runnable{

//共享资源

static int i=0;

/** * synchronized 修饰实例方法 */public synchronized void addI(){    i++;}public void run() {    for(int j=0;j<10000;j++){        addI();    }}public static void main(String[] args) throws InterruptedException {    TestSync instance=new TestSync();    Thread t1=new Thread(instance);    Thread t2=new Thread(instance);    t1.start();    t2.start();    t1.join();    t2.join();    System.out.println(i);//输出20000}复制代码

}

public class TestSync implements Runnable{

//共享资源

static int i=0;

/** * synchronized 修饰实例方法 */public synchronized void addI(){    i++;}public void run() {    for(int j=0;j<10000;j++){        addI();    }}public static void main(String[] args) throws InterruptedException {    TestSync instance1=new TestSync();    TestSync instance2=new TestSync();    Thread t1=new Thread(instance1);    Thread t2=new Thread(instance2);    t1.start();    t2.start();    t1.join();    t2.join();    System.out.println(i);//输出可能比20000小}复制代码

}

第二段代码对不同的实例对象加锁,也就是t1和t2使用不同的锁,操作的又是共享变量,因此,线程安全无法保证。解决这种问题的方法是将synchronized作用于静态的addI方法,这样的话,对象锁就是当前类的Class对象,由于无论创建多少个实例对象,但类对象只有一个,在这样的情况下对象锁就是唯一的。

修饰静态方法:

当synchronized作用于静态方法时,锁的是当前类的Class对象锁,由于静态成员不属于任何一个实例对象,是类成员,因此通过Class对象锁可以控制静态成员的并发操作。线程A访问static synchronized方法,线程B访问非static synchronized方法,A和B不互斥,因为使用不同的锁。

public class TestSync implements Runnable{

//共享资源

static int i=0;

/** * synchronized 修饰实例方法 */public static synchronized void addI(){    i++;}public void run() {    for(int j=0;j<10000;j++){        addI();    }}public static void main(String[] args) throws InterruptedException {    TestSync instance1=new TestSync();    TestSync instance2=new TestSync();    Thread t1=new Thread(instance1);    Thread t2=new Thread(instance2);    t1.start();    t2.start();    t1.join();    t2.join();    System.out.println(i);//输出20000}复制代码

}

修饰代码块:

synchronized除了修饰方法(普通方法、静态方法)外,还可以修饰代码块。如果一个方法的方法体较大,而需要同步的代码只是一小部分时就可以用该种使用方式。

public class TestSync implements Runnable{    static String instanceStr=new String();    static int i=0;    @Override    public void run() {            //使用同步代码块对变量i进行同步操作,锁对象为instance        synchronized(instanceStr){            for(int j=0;j<10000;j++){                i++;            }        }    }    public static void main(String[] args) throws InterruptedException {        Thread t1=new Thread(new TestSync());        Thread t2=new Thread(new TestSync());        t1.start();t2.start();        t1.join();t2.join();        System.out.println(i);//20000,如果instanceStr不是static则不能保证线程安全,同上    }}复制代码

除此之外,synchronized还可以对this或Class对象加锁,保证同步的条件同上。

public void run() {            //this加锁        synchronized(this){            for(int j=0;j<10000;j++){                i++;            }        }    }        public void run() {            //Class对象加锁        synchronized(TestSync.class){            for(int j=0;j<10000;j++){                i++;            }        }    }复制代码

3、lock

Lock是一个类,通过这个类可以实现同步访问,先来看一下Lock中的方法,如下:

public interface Lock {  /**    * 获取锁,锁被占用则等待    */  void lock();  /**    * 获取锁时,如果线程处于等待,则该线程能够响应中断而去处理其他事情    */  void lockInterruptibly() throws InterruptedException;  /**    * 尝试获取锁,如果锁被占用则返回false,否则返回true    */  boolean tryLock();  /**    * 较tryLock多一个等待时间,等待时间到了仍不能获得锁则返回false    */  boolean tryLock(long time, TimeUnit unit) throws InterruptedException;  /**    * 释放锁    */  void unlock();}复制代码

常见用法:

Lock lock = new ReentrantLock();//ReentrantLock是Lock的唯一实现类lock.lock();try{        }catch(Exception ex){}finally{    lock.unlock();  }Lock lock = new ReentrantLock();if(lock.tryLock()) {    try{        }catch(Exception ex){    }finally{        lock.unlock();  //释放锁    } }else {    //如果不能获取锁,则处理其他事情}复制代码

Reetrantlock

Reetrantlock是Lock的实现类,它表示可重入锁。ReentrantLock虽然没能像synchronized关键字一样支持隐式的重进入,但是在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。

ReadWriteLock

Reetrantlock属于排他锁,这些锁在同一时刻只允许一个线程进行访问,ReadWriteLock是读写锁,读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

volatile与synchronized的区别

(1)volatile主要应用在多个线程对实例变量更改的场合,通过刷新主内存共享变量的值从而使得各个线程可以获得最新的值;synchronized则是锁定当前变量,通过加锁方式保证变量的可见性。

(2)volatile仅能修饰变量;synchronized则可以使用在变量、方法和类上。

(3)volatile不会造成线程的阻塞;多个线程争抢synchronized锁对象时,会出现阻塞。

(4)volatile仅能实现变量的修改可见性,不能保证原子性。

(5)volatile标记的变量不会被编译器优化,可以禁止进行指令重排;synchronized标记的变量可以被编译器优化。

synchronized与lock的区别

(1)synchronized在执行完同步代码或发生异常时,能自动释放锁;而Lock则需要在finally代码块中主动通过unLock()去释放锁;

(2)Lock可以让等待锁的线程响应中断,Lock提供了更灵活的获取锁的方式,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

(3)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

(4)Lock可以提高多个线程进行读操作的效率。如果竞争资源不激烈,两者的性能是差不多的,而当有大量线程同时竞争时,此时Lock的性能要佳。所以说,在具体使用时要根据情况适当选择。

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

推荐阅读更多精彩内容