java并发lock锁详解和使用

您所在的位置:网站首页 lockinterruptibly源码 java并发lock锁详解和使用

java并发lock锁详解和使用

2023-09-02 21:52| 来源: 网络整理| 查看: 265

锁是用于通过多个线程控制对共享资源的访问的工具,通常锁提供对共享资源的独占访问,一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读写锁。在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的。JDK1.5之后并发包中新增了Lock接口以及相关实现类来实现锁功能。synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。

如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:1.获取锁的线程执行完了该代码块,然后线程释放对锁的占有2.线程执行发生异常,此时JVM会让线程自动释放锁

那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能地等待,这多么影响程序执行效率。因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。

Lock接口简介和使用

通过查看Lock的源码可知,Lock是一个接口,接口的实现类ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock

public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }

lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的,unLock()方法是用来释放锁的。Lock中声明了四个方法来获取锁,那么这四个方法有何区别呢?

1)lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:

Lock lock=new ReentrantLock(); lock.lock(); try{ //处理任务 }catch(Exception ex){ }finally{ lock.unlock(); //释放锁 }

2)tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回,在拿不到锁时也不会一直在那等待。tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。所以,一般情况下通过tryLock来获取锁时是这样使用的:

Lock lock=new ReentrantLock(); if(lock.tryLock()) { try{ //处理任务 }catch(Exception ex){ }finally{ lock.unlock(); //释放锁 } }else { //如果不能获取锁,则直接做其他事情 }

3)lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。因此lockInterruptibly()一般的使用形式如下:

public void method() throws InterruptedException { Lock lock=new ReentrantLock(); lock.lockInterruptibly(); try { //..... } finally { lock.unlock(); } }

注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

4)newCondition()获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait()方法,而调用后,当前线程将释放锁。

Lock接口的实现类:ReentrantLock

ReentrantLock意思是“可重入锁”,ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。ReentrantLock和synchronized关键字一样可以用来实现线程之间的同步互斥,功能比synchronized关键字更强大而且更灵活。通过查看Lock的源码可知,主要方法有:

ReentrantLock() //创建一个 ReentrantLock 的实例 ReentrantLock(boolean fair) //创建一个具有给定公平策略的 ReentrantLock int getHoldCount() //查询当前线程保持此锁的次数 protected Thread getOwner() //返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null protected Collection getQueuedThreads() //返回一个collection,它包含可能正等待获取此锁的线程 int getQueueLength() //返回正等待获取此锁的线程估计数 protected Collection getWaitingThreads(Condition condition) //返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程 int getWaitQueueLength(Condition condition) //返回等待与此锁相关的给定条件的线程估计数 boolean hasQueuedThread(Thread thread) //查询给定线程是否正在等待获取此锁 boolean hasQueuedThreads() //查询是否有些线程正在等待获取此锁 boolean hasWaiters(Condition condition) //查询是否有些线程正在等待与此锁有关的给定条件 boolean isFair() //如果此锁的公平设置为 true,则返回true boolean isHeldByCurrentThread() //查询当前线程是否保持此锁 boolean isLocked() //查询此锁是否由任意线程保持 void lock() //获取锁 void lockInterruptibly() //如果当前线程未被中断,则获取锁。 Condition newCondition() //返回用来与此 Lock 实例一起使用的 Condition 实例 boolean tryLock() //仅在调用时锁未被另一个线程保持的情况下,才获取该锁 boolean tryLock(long timeout, TimeUnit unit) //如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁 void unlock() //试图释放此锁

通过一些实例看具体看一下如何使用ReentrantLock

1)lock()的使用方法 public class LockTest2 { private Lock lock = new ReentrantLock(); public static void main(String[] args) { final LockTest2 test = new LockTest2(); 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++) { System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1))); } } catch (Exception e) { } finally { System.out.println(thread.getName() + "释放了锁"); lock.unlock(); } } }

从运行结果可以看出,当一个线程运行完毕后才把锁释放,其他线程才能执行,其他线程的执行顺序是不确定的。2)tryLock()的使用方法

public class LockTest2 { private Lock lock = new ReentrantLock(); public static void main(String[] args) { final LockTest2 test = new LockTest2(); new Thread(){ public void run(){ test.insert(Thread.currentThread()); } }.start(); 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){ if(lock.tryLock()) { try { System.out.println(thread.getName()+"得到了锁"); for (int i = 0; i < 3; i++) { System.out.println("ThreadName=" + Thread.currentThread().getName() + (" " + (i + 1))); } } catch (Exception e) { }finally { System.out.println(thread.getName()+"释放了锁"); lock.unlock(); } } else { System.out.println(thread.getName()+"获取锁失败"); } } }

3)lockInterruptibly()响应中断的使用方法

public class LockTest3 { private Lock lock = new ReentrantLock(); public static void main(String[] args) { LockTest3 test = new LockTest3(); MyThread thread1 = new MyThread(test); MyThread thread2 = new MyThread(test); thread1.start(); thread2.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } thread2.interrupt(); } public void insert(Thread thread) throws InterruptedException { lock.lockInterruptibly(); //注意,如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将InterruptedException抛出 try { System.out.println(thread.getName() + "得到了锁"); long startTime = System.currentTimeMillis(); for (; ; ) { if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) break; //插入数据 } } finally { System.out.println(Thread.currentThread().getName() + "执行finally"); lock.unlock(); System.out.println(thread.getName() + "释放了锁"); } } static class MyThread extends Thread { private LockTest3 test; public MyThread(LockTest3 test) { this.test = test; } @Override public void run() { try { test.insert(Thread.currentThread()); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName() + "被中断"); } } } } Condition接口使用

synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。

在使用notify/notifyAll()方法进行通知时,被通知的线程是有JVM选择的,使用ReentrantLock类结合Condition实例可以实现“选择性通知”,这个功能非常重要,而且是Condition接口默认提供的。

而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程

接口的主要实现方法:

void await() //造成当前线程在接到信号或被中断之前一直处于等待状态。 boolean await(long time, TimeUnit unit) //造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 long awaitNanos(long nanosTimeout) //造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。 void awaitUninterruptibly() //造成当前线程在接到信号之前一直处于等待状态。 boolean awaitUntil(Date deadline) //造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。 void signal() //唤醒一个等待线程。 void signalAll() //唤醒所有等待线程。

1)Condition实现等待/通知机制

public class UseSingleConditionWaitNotify { public static void main(String[] args) throws InterruptedException { MyService service = new MyService(); ThreadA a = new ThreadA(service); a.start(); Thread.sleep(3000); service.signal(); } static public class MyService { private Lock lock = new ReentrantLock(); public Condition condition = lock.newCondition(); public void await() { lock.lock(); try { System.out.println("await时间为:" + System.currentTimeMillis()); condition.await(); System.out.println("这是condition.await()方法之后的语句,condition.signal()方法之后我才被执行"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signal() { lock.lock(); try { System.out.println("signal时间为" + System.currentTimeMillis()); condition.signal(); Thread.sleep(3000); System.out.println("这是condition.signal()方法之后的语句"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } static public class ThreadA extends Thread { private MyService service; public ThreadA(MyService service) { this.service = service; } @Override public void run() { service.await(); } } }

在使用wait/notify实现等待通知机制的时候我们知道必须执行完notify()方法所在的synchronized代码块后才释放锁。在这里也差不多,必须执行完signal所在的try语句块之后才释放锁,condition.await()后的语句才能被执行。

2)多个Condition实例实现等待/通知机制

public class MyserviceMoreCondition { private Lock lock = new ReentrantLock(); private Condition conditionA = lock.newCondition(); private Condition conditionB = lock.newCondition(); public void awaitA() { lock.lock(); try { System.out.println("begin awaitA时间为" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); conditionA.await(); System.out.println("end awaitA时间为" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void awaitB() { lock.lock(); try { System.out.println("begin awaitB时间为" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); conditionB.await(); System.out.println("end awaitB时间为" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signalAll_A() { lock.lock(); try { System.out.println("signalAll_A时间为" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); conditionA.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signalAll_B() { lock.lock(); try { System.out.println("signalAll_B时间为" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); conditionB.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } } public class UseMoreConditionWaitNotify { public static void main(String[] args) throws InterruptedException { MyserviceMoreCondition service = new MyserviceMoreCondition(); ThreadA a = new ThreadA(service); a.setName("A"); a.start(); ThreadB b = new ThreadB(service); b.setName("B"); b.start(); Thread.sleep(3000); service.signalAll_A(); } static public class ThreadA extends Thread { private MyserviceMoreCondition service; public ThreadA(MyserviceMoreCondition service) { this.service = service; } @Override public void run() { service.awaitA(); } } static public class ThreadB extends Thread { private MyserviceMoreCondition service; public ThreadB(MyserviceMoreCondition service) { this.service = service; } @Override public void run() { service.awaitB(); } } }

3)Condition实现顺序执行

public class ConditionSeqExec { volatile private static int nextPrintWho = 1; // 默认情况下ReentranLock类使用的是非公平锁 final private static ReentrantLock lock = new ReentrantLock(); final private static Condition conditionA = lock.newCondition(); final private static Condition conditionB = lock.newCondition(); final private static Condition conditionC = lock.newCondition(); public static void main(String[] args) { Thread threadA = new Thread() { public void run() { lock.lock(); try { while (nextPrintWho != 1) { conditionA.await(); } for (int i = 0; i < 3; i++) { System.out.println("ThreadA" + (i + 1)); } nextPrintWho = 2; //通知conditionB实例的线程运行 conditionB.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }; Thread threadB = new Thread() { public void run() { lock.lock(); try { while (nextPrintWho != 2) { conditionB.await(); } for (int i = 0; i < 3; i++) { System.out.println("ThreadB" + (i + 1)); } nextPrintWho = 3; //通知conditionB实例的线程运行 conditionC.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }; Thread threadC = new Thread() { public void run() { lock.lock(); try { while (nextPrintWho != 3) { conditionC.await(); } for (int i = 0; i < 3; i++) { System.out.println("ThreadC" + (i + 1)); } nextPrintWho = 1; //通知conditionB实例的线程运行 conditionA.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }; Thread[] array1 = new Thread[5]; Thread[] array2 = new Thread[5]; Thread[] array3 = new Thread[5]; for (int i = 0; i < 5; i++) { array1[i] = new Thread(threadA); array2[i] = new Thread(threadB); array3[i] = new Thread(threadC); array1[i].start(); array2[i].start(); array3[i].start(); } } }

在一个线程运行完之后通过condition.signal()/condition.signalAll()方法通知下一个特定的运行运行,就这样循环往复即可。

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(); }

从源码中可知,ReadWriteLock里面只定义了两个方法:一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作,ReentrantReadWriteLock实现了ReadWriteLock接口。

ReentrantReadWriteLock接口简介和使用

ReentrantLock(排他锁)具有完全互斥排他的效果,即同一时刻只允许一个线程访问,这样做虽然虽然保证了实例变量的线程安全性,但效率非常低下。ReadWriteLock接口的实现类ReentrantReadWriteLock读写锁就是为了解决这个问题。ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。

读写锁维护了两个锁,一个是读操作相关的锁也成为共享锁,一个是写操作相关的锁 也称为排他锁。通过分离读锁和写锁,其并发性比一般排他锁有了很大提升。

多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥(只要出现写操作的过程就是互斥的)。在没有线程Thread进行写入操作时,进行读取操作的多个Thread都可以获取读锁,而进行写入操作的Thread只有在获取写锁后才能进行写入操作。即多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。

1)假如有多个线程要同时进行读操作的话,先看一下synchronized达到的效果:

public class ReentrantReadWriteLockTest2 { public static void main(String[] args) { final ReentrantReadWriteLockTest2 test = new ReentrantReadWriteLockTest2(); new Thread() { public void run() { test.get(Thread.currentThread()); } }.start(); new Thread() { public void run() { test.get(Thread.currentThread()); } }.start(); } synchronized public void get(Thread thread) { long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3