synchronized缺陷有
Lock是可中断锁,通过lockInterruptibly()实现。
synchronized是非公平锁,它无法保证等待的线程获取锁的顺序;ReentrantLock和ReentrantReadWriteLock默认情况下是非公平锁,但是可以设置为公平锁。
1、可重入锁
如果锁具备可重入性,则称作为可重入锁,像synchronized和ReentrantLock都是可重入锁。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
首先我们来说一说为什么会出现Lock接口,之所以出现Lock接口,是因为synchronized同步方式存在某些不足。
1、如果一个代码块被synchronized修饰,当一个线程获取了对应的锁,其他线程只能一直等待获取锁的线程释放锁,而获取锁的线程释放锁只会有两种情况:
1)获取锁的线程执行完了代码块,然后线程释放对锁的占有
2)线程执行发生异常,此时JVM会自动释放线程占有的锁
如果获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便就只能一直等待下去,这样会影响程序执行效率,因此就需要有一种机制可以不让等待的线程无期限地等待下去,、Lock接口可以实现,比如让线程只等待一定的时间或者让等待的线程能够响应中断。
2、当有多个线程读写文件时,读操作和写操作会发生冲突,写操作和写操作会发生冲突,但是读操作和读操作不会发生冲突。但是采用synchronized来实现同步的话,就会导致一个问题,如果多个线程都只是进行读操作,当一个线程在进行读操作时,其他线程只能等待无法进行读操作。因此就需要一种机制来使得多个线程可以同时进行读操作,通过ReadWriteLock接口就可以办到。
3、通过Lock接口可以知道线程有没有成功获取到锁,这个是synchronized无法办到的。
Lock接口:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
Lock接口中lock()、tryLock()、tryLock(long time, TimeUnit unit)、lockInterruptibly()是用来获取锁的,unLock()方法是用来释放锁的。采用Lock接口同步线程,在发生异常时,JVM不会自动释放锁,必须由程序员主动去释放锁,因此使用Lock接口必须在try{ }catch{ }块中进行,并且将释放锁的操作放在finally{ }块中进行,以保证锁一定被被释放,防止死锁的发生。
1)lock()方法用来获取锁,如果锁已被其他线程获取,则进行等待,通常使用lock()方法来进行同步的话,是以下面这种形式去使用的:
2)tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回,在拿不到锁时不会一直等待。tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
Lock lock = ...;
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}
3)当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只能等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。因此当通过lockInterruptibly()方法获取某个锁时,只有等待锁的线程是可以响应中断的,线程获取了锁之后,是不会被interrupt()方法中断的,而用synchronized修饰的话,当线程等待锁时是无法被中断的,只能一直等待下去。由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。
public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
//.....
}
finally {
lock.unlock();
}
}
2、ReentrantLock
ReentrantLock(可重入锁)是唯一实现了Lock接口的类,lock()方法使用如
import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
private ArrayList<Integer> arrayList = new ArrayList<Integer>();
private Lock lock = new ReentrantLock(); //注意这个地方,lock声明为成员变量
public static void main(String[] args) {
final Main test = new Main();
new Thread(){
public void run() {
test.insert(Thread.currentThread());
};
}.start();
new Thread(){
public void run() {
test.insert(Thread.currentThread());
};
}.start();
}
public void insert(Thread thread) {
lock.lock();
try {
System.out.println(thread.getName()+"得到了锁");
for(int i=0;i<5;i++) {
arrayList.add(i);
}
} catch (Exception e) {
// TODO: handle exception
}finally {
System.out.println(thread.getName()+"释放了锁");
lock.unlock();
}
}
}
下:
lock.lock结果为,等待锁,释放后继续执行:
-
Thread-0得到了锁
-
Thread-0释放了锁
-
Thread-1得到了锁
-
Thread-1释放了锁
如果改成if(lock.tryLock()),则结果为,一个获取锁,另一个无法获取
-
Thread-0得到了锁
-
Thread-1获取锁失败
-
Thread-0释放了
lockInterruptibly()使用如下:
import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
Main main = new Main();
MyThread thread1 = new MyThread(main);
MyThread thread2 = new MyThread(main);
thread1.start();
thread2.start();
thread2.interrupt();
}
public void insert(Thread thread) throws InterruptedException{
lock.lockInterruptibly();
try {
System.out.println(thread.getName()+"得到了锁");
}catch(Exception e) {
e.printStackTrace();
}
finally {
System.out.println(Thread.currentThread().getName()+"执行finally");
lock.unlock();
System.out.println(thread.getName()+"释放了锁");
}
}
}
class MyThread extends Thread {
private Main test = null;
public MyThread(Main test) {
this.test = test;
}
@Override
public void run() {
try {
test.insert(Thread.currentThread());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+"被中断");
}
}
}
结果为:
-
Thread-1被中断
-
Thread-0得到了锁
-
Thread-0执行finally
-
Thread-0释放了锁
Thread-0、Thread-1同时获取锁,但是Thread-0获取到了,接着执行任务,finally中释放锁;Thread-1未获取到锁,进入等待状态,随后被中断。
3、ReadWriteLock
ReadWriteLock也是一个接口,在它里面只定义了两个方法:
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading.
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing.
*/
Lock writeLock();
}
读锁不互斥
import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Main {
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
final Main test = new Main();
new Thread(){
public void run() {
test.get(Thread.currentThread());
};
}.start();
new Thread(){
public void run() {
test.get(Thread.currentThread());
};
}.start();
}
public void get(Thread thread) {
rwl.readLock().lock();
try {
for(int i=0;i<5;i++) {
Thread.currentThread().sleep(10);
System.out.println(thread.getName()+"正在进行读操作");
}
System.out.println(thread.getName()+"读操作完毕");
} catch(Exception e) {
}finally {
System.out.println(thread.getName()+"释放了锁");
rwl.readLock().unlock();
}
}
}
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0读操作完毕
Thread-0释放了锁
Thread-1正在进行读操作
Thread-1读操作完毕
Thread-1释放了锁