如何使用synchronized实现同步访问,在java5.0之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock。此处简单介绍了如何使用synchronized防止并发访问,和使用synchronized存在的缺陷,以及使用Lock来控制程序的并发访问。
并发编程三个概念
原子性: 一个操作或多个操作要么全部执行且执行过程不被中断,要么不执行
可见性: 多个线程修改同一个共享变量时,一个线程修改后,其他线程能马上获得修改后的值
有序性 : 程序执行的顺序按照代码的先后顺序执行
一、synchronized关键字是为了线程安全服务的,线程安全就是当多个线程访问一个类(变量或方法)的时候,这个对象都可以表现出正常的行为。那线程的安全性又表现出三个特性:原子性、可见性及有序性。synchronized保证了同步,原子性。volatile关键字保证了可见性。wait,notify 负责多个线程之间的通信。
synchronized 可以在任意对象及方法上加锁,若一个线程想要执行synchronized修饰的代码块,首先要
step1 尝试获得锁
step2 如果拿到锁,执行synchronized代码体内容
step3 如果拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到为止,而且是多个线程同时去竞争这把锁。
注*(线程多了也就是会出现锁竞争的问题,多个线程执行的顺序是按照CPU分配的先后顺序而定的,而并非代码执行的先后顺序)
synchronized 可以修饰方法,修饰代码块,这些都是对象锁。若和static一起使用,则升级为类锁。建议修饰代码块,减小锁的颗粒度,提高系统的性能。
synchronized 锁是可以重入的,当一个线程得到了一个对象的锁后,再次请求此对象时是可以再次得到该对象的锁。锁重入的机制,也支持在父子类继承的场景。
synchronized 同步异步,一个线程得到了一个对象的锁后,其他线程是可以执行非加锁的方法(异步)。但是不能执行其他加锁的方法(同步)。
synchronized 锁异常,当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
private void thisLock () { // 使用对象锁
synchronized (this) {
System.out.println("this 对象锁!");
}
}
private void classLock () { // 使用类锁
synchronized (ITDragonSynchronized.class) {
System.out.println("class 类锁!");
}
}
目前可以使用的场景,在使用单例模式的时候可以使用synchronized关键字,进行二次验证,防止在并发的情况下创建出多个对象。
// 公共静态成员方法,返回唯一实例
public static LoadBalancer GetLoadBalancer(){
// 第一重判断
if (instance == null){
// 锁定代码块
synchronized(syncLocker){
// 第二重判断
if (instance == null){
instance = new LoadBalancer();
}
}
}
return instance;
}
二、volatile关键字虽然不具备synchronized关键字的原子性(同步)但其主要作用就是使变量在多个线程中可见。也就是可见性。用法很简单,直接用来修饰变量。因为其不具备原子性。
volatile 关键字工作原理每个线程都有自己的工作内存,如果线程需要用到一个变量的时,会从主内存拷贝一份到自己的工作内存中。从而提高了效率。每次执行完线程后再将变量从工作内存同步回主内存中。
这样就存在一个问题,变量在不同线程中可能存在不同的值。如果用volatile 关键字修饰变量,则会让线程的执行引擎直接从主内存中获取值。
三、synchronized的缺陷
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2)线程执行发生异常,此时JVM会让线程自动释放锁;
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到,注意:
1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象
使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生
首先要说明的就是Lock,通过查看Lock的源码可知,Lock是一个接口:
public interface Lock {