1.synchronize缺陷
代码块被synchronize修饰时,当一个线程获取了对应的线程锁并执行该代码块时,其他线程只能一直等待,直到获取锁的线程释放锁,而这里得到锁的线程释放锁只有两种情况:
(1)得到锁的线程执行完了该代码块,然后线程释放线程锁
(2)线程执行发生异常,此时JVM会让线程自动释放锁
如果这个得到锁的线程由于要等待IO或者其他原因(如调用了sleep方法)被阻塞了,但是又没有释放锁,其他线程只能干巴巴等待,这非常影响程序执行的效率。因此,就需要有一种机制可以不让等待的线程一直无限期等待下去(比如只等待一段时间或者能够响应中断),通过Lock就可以实现
再有一个例子:
当多个线程读写文件时,读写、写写都会发生冲突,但是读读操作不会发生冲突。但是如果采用synchronize关键字来实现同步的话,就会导致一个问题:如果多个线程只是进行读操作,当一个线程正在进行读操作时,其他线程只能等待而无法进行读操作。因此,就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,Lock就可办到。另外,通过Lock可以知道有没有成功获取到锁。这个是synchronize无法办到的。
2.synchronize与Lock的区别
(1)synchronize是Java的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问
(2)synchronize不需要用户手动去释放锁:当synchronize方法或代码块执行完后,系统会让线程自动释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放,则会造成死锁现象
3.锁分类
(1)可重入锁
如果锁具备可重入性,则可以成为可重入锁,像synchronized和ReentrantLock都是可重入锁,可重入性实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单例子:当一个线程执行到某个synchronized方法时,比如method1,而在method1中会调用另外一个synchronized方法method2时,就不用重新申请锁,而是可以直接执行method2
看看如下代码:
class MyClass
{
public synchronized void method1()
{
method2();
}
public synchronized void method2()
{
}
}
上述代码中的两个方法都是用了synchronized,加入某一止咳,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,加入synchronized不具有可重入性,此时线程A需要重新申请锁。但是这样就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请该对象的锁,这样线程A会直等待而永远不会得到申请的锁。
由于synchronized和Lock都具备可重入性,所以不会发生上述现象
(2)可中断锁
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,B不想再等了,想先去处理其他事情,我们可以让他中断自己或者在别的线程中中断它,这种就是可中断锁。
synchronized是不可中断锁,Lock是可中断锁
(3)公平锁
公平锁即尽量以请求锁的顺序来获取锁。比如同时有多个线程在等待同一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这就是公平锁。
synchronized是非公平锁,也就是说无法保证锁的获取是按照请求锁的顺序进行的,这可能导致一些线程永远获取不到锁。
ReentrantLock和ReentrantReadWriteLock,默认是非公平锁,但是可以设置为公平锁。
在ReentrantLock中定义了公平和非公平锁两个类
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
我们在创建ReentrantLock对象时,可以通过以下方式设置锁的公平性
ReentrantLock lock = new ReentrantLock(true);
true为公平锁,false为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
另外,在ReentrantLock中定义了很多方法:
public final boolean isFair() { //用来判断是否是公平锁
return sync instanceof FairSync;
}
public boolean isLocked() { //用来判断锁是否已经被获取了
return sync.isLocked();
}
public final boolean hasQueuedThreads() { //判断是否有线程在等待该锁
return sync.hasQueuedThreads();
}
ReentrantReadWriteLock也有类似的方法,同样也可以设置为公平锁和非公平锁。注意,ReentrantReadWriteLock并没有实现Lock接口,实现的是ReadWriteLock
(4)读写锁
读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。正因为有了读写所,才使得多个线程之间的读操作不会发生冲突
3.Lock
通过代码查看,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,lockInterruptibly,tryLock都是用来获取锁的,unLock是用来释放锁的
(1)lock()
使用最多的方法,就是为了获取锁,如果锁已经被其他线程获取,则进行等待
如果采用Lock,则必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此,一般来说,使用lock必须在try、catch中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被释放,防止死锁的发生。
Lock lock = ...;
lock.lock();
try
{
//处理任务
}
catch(Exception ex)
{
}
finally
{
lock.unlock(); //释放锁
}
(2)tryLock()
有返回值,用来表示是否获得了锁,如果返回true,则说明获取成功,如果获取失败(锁已经被其他线程获取),则返回false。也就是说,这个方法无论如何都会立即返回,拿不到不会一直等
Lock lock = ...;
if(lock.tryLock())
{
try
{
//处理任务
}
catch(Exception ex)
{
}
finally
{
lock.unlock(); //释放锁
}
}
else
{
//如果不能获取锁,则直接做其他事情
}
(3)tryLock(long time, TimeUnit unit)
和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到或者在等待时间内拿到了锁,就返回false
(4)lockInterruptibly()
当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断。其实也就是说两个线程A、B同时通过lock.lockInterruptibly()想获取某个锁时,假如A获取到了锁,而B在等待,那么对B调用threadB.interrput()方法能够中断B线程的等待过程。
public void method() throws InterruptedException
{
lock.lockInterruptibly();
try
{
//.....
}
finally
{
lock.unlock();
}
}
注意:
当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为单独调用interrupt()方法只能中断阻塞中的线程,而不能中断正在运行的线程。
因此当通过lockInterruptibly()获取线程锁时,如果不能获取到,只有在进行等待的状态下,是可以响应中断的。
而用synchronize修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只能一直等待下去。
4.ReentrantLock
意思是可重入锁。ReentrantLock是唯一实现了Lock接口的类。
(1)使用lock()获取锁
public class TestLock
{
List<Integer> list = new ArrayList<Integer>();
public static void main(String[] args)
{
final TestLock test = new TestLock();
new Thread(new Runnable() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
}).start();
}
protected void insert(Thread currentThread)
{
Lock lock = new ReentrantLock();
lock.lock();
try
{
System.out.println(currentThread.getName()+" get lock");
for(int i=0; i<5;i++)
list.add(i);
}
catch(Exception e)
{}
finally
{
System.out.println(currentThread.getName()+" release lock");
lock.unlock();
}
}
}
结果为:
Thread-0 get lock
Thread-1 get lock
Thread-1 release lock
Thread-0 release lock
为何是这样呢?因为各个线程使用的是自己的Lock,那么肯定就起不到同步的作用了,所以将lock改为成员变量即可
public class TestLock
{
List<Integer> list = new ArrayList<Integer>();
Lock lock = new ReentrantLock();
public static void main(String[] args)
{
final TestLock test = new TestLock();
new Thread(new Runnable() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
}).start();
}
protected void insert(Thread currentThread)
{
lock.lock();
try
{
System.out.println(currentThread.getName()+" get lock");
for(int i=0; i<5;i++)
list.add(i);
}
catch(Exception e)
{}
finally
{
System.out.println(currentThread.getName()+" release lock");
lock.unlock();
}
}
}
结果为:
Thread-0 get lock
Thread-0 release lock
Thread-1 get lock
Thread-1 release lock
(2)使用tryLock()获取锁
public class TestLock
{
static List<Integer> list = new ArrayList<Integer>();
Lock lock = new ReentrantLock();
public static void main(String[] args)
{
final TestLock test = new TestLock();
new Thread(new Runnable() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
}).start();
}
protected void insert(Thread currentThread)
{
if(lock.tryLock())
{
try
{
System.out.println(currentThread.getName()+" get lock");
for(int i=0; i<5;i++)
list.add(i);
}
catch(Exception e)
{}
finally
{
System.out.println(currentThread.getName()+" release lock");
lock.unlock();
}
}
else
{
System.out.println(currentThread.getName()+" can get lock");
}
}
}
结果为:
Thread-0 get lock
Thread-0 release lock
Thread-1 get lock
Thread-1 release lock
(3)使用lockInterruptibly()获取锁
public class TestLock
{
static List<Integer> list = new ArrayList<Integer>();
Lock lock = new ReentrantLock();
public static void main(String[] args)
{
final TestLock test = new TestLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
});
t1.start();
t2.start();
try
{
Thread.sleep(20);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
t2.interrupt();
}
protected void insert(Thread currentThread)
{
try
{
lock.lockInterruptibly();
System.out.println(currentThread.getName() + " get lock");
for (int i = 0; i < 5; i++)
list.add(i);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
finally
{
System.out.println(currentThread.getName() + " release lock");
lock.unlock();
}
}
}
也就是说在20ms之后还没有得到锁,那么就interrupt线程。
5.ReadWriteLock
ReadWriteLock也是一个接口,其中只定义了2个方法:
public interface ReadWriteLock {
/**
* @return the lock used for reading.
*/
Lock readLock();
/**
* @return the lock used for writing.
*/
Lock writeLock();
}
一个用来获取读锁,一个用来获取写锁。也就是说,将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。
ReentrantReadWriteLock是ReadWriteLock接口的一个实现类。加入有多个线程要同时进行读操作的话,先看一下synchronized达到的效果。
public class TestLock
{
public static void main(String[] args)
{
final TestLock test = new TestLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
test.get(Thread.currentThread());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
test.get(Thread.currentThread());
}
});
t1.start();
t2.start();
try
{
Thread.sleep(20);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
t2.interrupt();
}
synchronized protected void get(Thread currentThread)
{
long start = System.currentTimeMillis();
while(System.currentTimeMillis()-start<=1)
System.out.println(currentThread.getName()+"正在进行读操作");
System.out.println(currentThread.getName()+"完成读操作");
}
}
结果为:
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0完成读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1完成读操作
也就是说,知道thread1读操作完成之后,才会执行thread2的读操作
下面使用读写锁:
public class TestLock
{
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args)
{
final TestLock test = new TestLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
test.get(Thread.currentThread());
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
test.get(Thread.currentThread());
}
});
t1.start();
t2.start();
try
{
Thread.sleep(20);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
t2.interrupt();
}
protected void get(Thread currentThread)
{
rwl.readLock().lock();
try
{
long start = System.currentTimeMillis();
while(System.currentTimeMillis()-start<=1)
System.out.println(currentThread.getName()+"正在进行读操作");
System.out.println(currentThread.getName()+"完成读操作");
}
catch(Exception e)
{
}
finally
{
rwl.readLock().unlock();
}
}
}
结果为:
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1完成读操作
Thread-0正在进行读操作
Thread-0完成读操作
说明两个线程可以同时进行读操作,这样就大大提高了读操作的效率。
注意:
(1)如果有一个线程已经占用了读锁,此时如果其他线程要申请写锁,则申请写锁的线程会一直等待释放读锁
(2)如果一个线程已经占用了写锁,此时其他线程如果申请读锁或者写锁,则申请的线程会一直等待释放写锁
6.Lock与synchronized区别
(1)Lock是一个接口,而synchronized是java中的关键字
(2)synchronized发生异常时,会自动释放线程中占有的锁,因此不会导致线程死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能会造成死锁现象,因此使用使用Lock需要在finally中释放锁(3)Lock可以让等待锁的线程中断,而synchronized不能,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
(4)通过Lock可以知道有没有成功获得锁,而synchronized无法办到
(5)Lock可以提高多个线程进行读操作的效率
从性能上来说,如果资源竞争不激烈,两者的性能差不多,但是当有大量线程同时竞争时,此时Lock的性能要远远优于synchronized