java多线程-Lock
来源:互联网 发布:网络摄像头连接拾音器 编辑:程序博客网 时间:2024/06/01 09:04
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() {@Overridepublic void run() {test.insert(Thread.currentThread());}}).start();new Thread(new Runnable() {@Overridepublic 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 lockThread-1 get lockThread-1 release lockThread-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() {@Overridepublic void run() {test.insert(Thread.currentThread());}}).start();new Thread(new Runnable() {@Overridepublic 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 lockThread-0 release lockThread-1 get lockThread-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() {@Overridepublic void run() {test.insert(Thread.currentThread());}}).start();new Thread(new Runnable() {@Overridepublic 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 lockThread-0 release lockThread-1 get lockThread-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() {@Overridepublic void run() {test.insert(Thread.currentThread());}});Thread t2 = new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic void run() {test.get(Thread.currentThread());}});Thread t2 = new Thread(new Runnable() {@Overridepublic 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() {@Overridepublic void run() {test.get(Thread.currentThread());}});Thread t2 = new Thread(new Runnable() {@Overridepublic 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
- Java多线程---Lock类
- java多线程-Lock
- Java 多线程:Lock 关键字
- Java 多线程:Lock接口
- Java 多线程:Lock 关键字
- Java多线程--Lock接口
- Java多线程4- Lock、Condition
- Java多线程4- Lock、Condition
- Lock的使用 java多线程
- Java 锁LOCK&多线程安全
- JAVA Lock 多线程顺序执行
- Java多线程--Lock的使用
- java多线程:8、初用Lock
- java多线程Lock的使用。
- Java 多线程之Lock接口
- Java多线程(四)Lock锁
- java多线程之Lock介绍
- Java多线程----java 对象锁(synchronized/lock)
- 将china译成密码
- CentOS安装rpm包时遇到Header V3 DSA signature: NOKEY时解决办法
- Qt for Linux/X11
- Java避免空指针
- kfifo
- java多线程-Lock
- 关于Linux下LAMP环境的搭建(一):Ubuntu安装
- 模型搜索 及 轮廓搜索 的原理
- Android四大组件介绍与生命周期
- 图解Android - Binder 和 Service
- javaFX初探(使用Image Ops API)
- Java实现敏感词过滤
- VirtualBox直接使用物理硬盘作虚拟机磁盘
- php中关于怎么样消除notice的做法