并发编程中的volatile、synchronized和lock

作者 : 开心源码 本文共4574个字,预计阅读时间需要12分钟 发布时间: 2022-05-13 共139人阅读

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的性能要佳。所以说,在具体使用时要根据情况适入选择。

欢迎大家关注公众号,理解更多文章,大家一起进步!

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 并发编程中的volatile、synchronized和lock

发表回复