文章目录
- 一、ReentrantLock类
- 1.使用ReentrantLock
- 2.使用ReentrantLock实现多对多
- 3.公平锁与非公平锁
- 4.ReentrantLock类其他方法的使用
- 二、ReentrantReadWriteLock类
- 1.读读共享
- 2.写写互斥
- 3.读写互斥
- 4.写读互斥
- 总结
一、ReentrantLock类
1.使用ReentrantLock
-
创建reentrantlock.service包,在包下创建MyService类
package reentrantlock.service; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyService { private Lock lock =new ReentrantLock(); public Condition condition = lock.newCondition(); public void await() { try { lock.lock(); System.out.println("await时间为"+System.currentTimeMillis()); condition.await(); } catch (InterruptedException e) { throw new RuntimeException(e); }finally { lock.unlock(); } } public void signal() { try{ lock.lock(); System.out.println("signal时间为"+System.currentTimeMillis()); condition.signal(); }finally { lock.unlock(); } } }
即使用ReentrantLock的同步锁,并且使用它的Condition对象的一个await/signal方法来实现控制并处理线程的状态,使其进入等待或就绪状态。
分别用MyService对象的await、signal方法进行实现。 -
创建ReentrantLock.thread包,并在包下创建ThreadA类
package reentrantlock.thread; import reentrantlock.service.MyService; public class ThreadA extends Thread{ private MyService myService; public ThreadA(MyService myService) { this.myService = myService; } @Override public void run() { myService.await(); } }
即ThreadA线程类通过构造方法给MyService对象赋值,并调用该对象的await方法。
-
创建reentrantlock.thread包,并在包下创建ThreadB类
package reentrantlock.thread; import reentrantlock.service.MyService; public class ThreadB extends Thread{ private MyService myService; public ThreadB(MyService myService) { this.myService = myService; } @Override public void run() { myService.signal(); } }
即ThreadB线程类通过构造方法给MyService对象赋值,并调用该对象的signal方法。
-
创建reentrantlock.test包,并在包下创建Run类
package reentrantlock.test; import reentrantlock.service.MyService; import reentrantlock.thread.ThreadA; import reentrantlock.thread.ThreadB; public class Run { public static void main(String[] args) throws InterruptedException { MyService myService =new MyService(); ThreadA threadA = new ThreadA(myService); threadA.start(); Thread.sleep(3000); ThreadB threadB = new ThreadB(myService); threadB.start(); } }
即通过ThreadA 调用MyService 的await方法,主线程休眠3秒后,ThreadB 调用MyService 的signal方法。
-
await()方法通过调用Unsafe.park方法使线程暂停运行。
如果传入true,则第二个参数单位时间为毫秒,此时第二个单位时间为绝对时间//获取Unsafe类的theUnsafe属性 Field f = Unsafe.class.getDeclaredField("theUnsafe"); //给theUnsafe设置为可使用 f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); System.out.println("begin"+System.currentTimeMillis()); unsafe.park(true,System.currentTimeMillis()+3000); System.out.println("end"+System.currentTimeMillis());
如果传入false,则第二个参数单位时间为纳秒,此时为相对时间
//获取Unsafe类的theUnsafe属性 Field f = Unsafe.class.getDeclaredField("theUnsafe"); //给theUnsafe设置为可使用 f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); System.out.println("begin"+System.currentTimeMillis()); unsafe.park(false,3000000000L); System.out.println("end"+System.currentTimeMillis());
-
signal()方法通过调用Unsafe.unpark方法唤醒线程。
package reentrantlock.thread; import sun.misc.Unsafe; public class MyThread extends Thread{ private Unsafe unsafe; private Thread mainThread; public MyThread(Unsafe unsafe, Thread mainThread) { this.unsafe = unsafe; this.mainThread = mainThread; } @Override public void run() { try { Thread.sleep(6000); unsafe.unpark(mainThread); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
package reentrantlock.test; import reentrantlock.thread.MyThread; import sun.misc.Unsafe; import java.lang.reflect.Field; public class Test2 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException { //获取Unsafe类的theUnsafe属性 Field f = Unsafe.class.getDeclaredField("theUnsafe"); //给theUnsafe设置为可使用 f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); MyThread myThread = new MyThread(unsafe, Thread.currentThread()); //MyThread线程启动休眠6s,再调用unpark方法 myThread.start(); //main线程休眠0.2秒 Thread.sleep(200); //开始打印时间为myThread休眠的200毫秒后,所以从begin到end的时间未5.8秒。 System.out.println("begin"+System.currentTimeMillis()); unsafe.park(false,0L); System.out.println("end"+System.currentTimeMillis()); } }
2.使用ReentrantLock实现多对多
-
创建reentrantlock.service包,然后创建MyService3类
package reentrantlock.service; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyService3 { private Lock lock =new ReentrantLock(); public Condition condition = lock.newCondition(); private boolean hasValue = false; public void set() { try { lock.lock(); while(hasValue==true) { condition.await(); } System.out.println("生产"); hasValue=true; condition.signalAll(); } catch (InterruptedException e) { throw new RuntimeException(e); }finally { lock.unlock(); } } public void get() { try{ lock.lock(); while(hasValue==false) { condition.await(); } System.out.println("消费"); hasValue=false; condition.signalAll(); } catch (InterruptedException e) { throw new RuntimeException(e); }finally { lock.unlock(); } } }
即跟之前的wait/notifyAll机制的多对多一样,而ReentrantLock的多对多实现也是使用While来判断条件,使用Condition对象的signalAll方法来唤醒所有等待线程
-
创建reentrantlock.thread包,然后创建ThreadA3类
package reentrantlock.thread; import reentrantlock.service.MyService3; public class ThreadA3 extends Thread{ private MyService3 myService; public ThreadA3(MyService3 myService) { this.myService = myService; } @Override public void run() { myService.set(); } }
-
创建reentrantlock.thread包,然后创建ThreadB3类
package reentrantlock.thread; import reentrantlock.service.MyService3; public class ThreadB3 extends Thread{ private MyService3 myService; public ThreadB3(MyService3 myService) { this.myService = myService; } @Override public void run() { myService.get(); } }
-
创建reentrantlock.test包,然后创建Run3类
package reentrantlock.test; import reentrantlock.service.MyService3; import reentrantlock.thread.ThreadA3; import reentrantlock.thread.ThreadB3; public class Run3 { public static void main(String[] args) { MyService3 service = new MyService3(); ThreadA3[] A = new ThreadA3[10]; ThreadB3[] B = new ThreadB3[10]; for (int i = 0; i < 10; i++) { A[i] = new ThreadA3(service); B[i] = new ThreadB3(service); A[i].start(); B[i].start(); } } }
即分别创建10个ThreadA3和ThreadB3实例分别调用MyService3实例中的set和get方法。set则是生产操作,get则是消费操作。从而实现多对多。
3.公平锁与非公平锁
-
创建reentrantlock.service包,在包下创建MyService4 类
package reentrantlock.service; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyService4 { public Lock lock; public MyService4(boolean fair) { lock = new ReentrantLock(fair); } public void testMethod() { try { lock.lock(); System.out.println("testMethod"+Thread.currentThread().getName()); //此处的10ms用于配合 array2有更多可能在非公平的情况下找到锁 //如果线程数量够多,也可以不休眠或让休眠时间更小 Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); }finally{ lock.unlock(); } } }
即通过MyService4的构造方法给ReentrantLock传参,生成非公平锁对象。
休眠是为了让其他线程有机会抢到锁对象。 -
创建reentrantlock.thread包,在包下创建MyThread2 类
package reentrantlock.thread; import reentrantlock.service.MyService4; public class MyThread2 extends Thread{ private MyService4 service4; public MyThread2(MyService4 service4) { super(); this.service4 = service4; } @Override public void run() { service4.testMethod(); } }
即线程类用来调用MyService4的testMethod方法。
-
创建reentrantlock.test包,在包下创建Run4类
package reentrantlock.test; import reentrantlock.service.MyService4; import reentrantlock.thread.MyThread2; public class Run4 { public static void main(String[] args) throws InterruptedException { //即通过MyService4的构造方法将bool值传入,再给ReentrantLock传入,生成非公平锁NonfairSync。 MyService4 myService4 = new MyService4(false); MyThread2[] array1 = new MyThread2[1000]; MyThread2[] array2 = new MyThread2[1000]; long begin = System.currentTimeMillis(); for (int i = 0; i < array1.length; i++) { array1[i]= new MyThread2(myService4); array1[i].setName("array1+++"+(i+1)); array1[i].start(); } Thread.sleep(10); for (int i = 0; i < array2.length; i++) { array2[i]= new MyThread2(myService4); array2[i].setName("array2---"+(i+1)); array2[i].start(); } } }
即通过MyService4的构造方法生成一个非公平锁,生成两个线程数组,使用for循环去启动线程,线程组1和线程组2中间的休眠是为了让线程组1先排队,而线程执行期间休眠是为了让线程组2可以在线程运行时,线程组1刚刚释放锁,线程组二有机会可以抢到线程组1刚刚释放的锁,如果线程量够大,线程执行时的休眠可以不要。
非公平锁运行结果:
而公平锁就是将MyService4构造方法传入的值修改为true,它是按申请锁资源的顺序来给锁资源的。
即上述结果中2不会在1之前。
4.ReentrantLock类其他方法的使用
-
public int getHoldCount()方法,作用是查询"当前线程"保持此锁的个数,也就是调用lock方法的次数。
执行lock方法进行锁重入,导致count+1,而unlock方法导致count-1。 -
public final int getQueueLength方法,作用是返回正等待获取此锁的线程的估计数。
-
public int getWaitQueueLength(Condition condition)方法,作用是返回等待此锁相关的给定条件Condition的线程估计数。即返回调用过condition.await方法的线程估计数。
-
public final boolean hasQueuedThread(Thread thread),作用是查询指定的线程是否正在等待获取此锁,即判断参数中的线程是否在等待队列中。
-
public final boolean hasQueuedThreads(),作用是查询是否有线程正在等待获取此锁,即等待队列中是否有等待的线程。
-
public boolean hasWaiters(Condition condition),作用是查询是否有线程正在等待与此锁有关的condition对象,即是否有线程执行了condition对象的await方法而呈等待状态。
-
public final boolean isFair(),作用是判断是不是公平锁。
ReentrantLock默认是非公平锁。 -
public boolean isHeldByCurrentThread(),作用是查询当前线程是否持有此锁。
-
public boolean isLocked(),作用是查询此锁是否有线程持有,并且未释放。
-
public void lockInterruptibly(),作用是当某个线程尝试获得锁并且阻塞在lockInterruptibly()方法时,该线程可以被中断。
即使用lockInterruptibly方法进行加锁时,该锁可以被中断(interrupt)。 -
public boolean tryLock(),作用是当前线程发现锁被其他线程持有了,则返回false,那么程序继续执行后面的代码,而不是呈阻塞等待锁状态的。
即可以将tryLock作为一个判断条件,true(获得锁)则执行true指定的语句,false执行false指定的语句。 -
public boolean tryLock(long timeout,TimeUnit unit),如果在指定的timeout时间内持有了锁,则返回true,如果超过时间则返回false。
-
public boolean await(long time,TimeUnit unit)和public final native void wait(long timeout)方法一样,具有自动唤醒功能。
-
public long awaitNanos(long nanosTimeout),具有自动唤醒功能,时间单位是纳秒。
1000纳秒=1微秒,1000微秒=1毫秒,1000毫秒=1秒。 -
public boolean awaitUntil(Date deadline),作用是在指定的日期结束等待。
13-15的方法都是具有自动唤醒功能的等待方法。 -
public void awaitUninterruptibly(),作用是等待的过程中,不允许被中断。即将await方法换成awaitUninterruptibly方法。
二、ReentrantReadWriteLock类
使用ReentrantLock对象时,对所有的操作都同步,包括读取操作,这样会耗费大量时间,降低运行效率。
1.读读共享
-
创建一个reentrantreadwritelock.service包,在包下创建MyService 类
package reentrantreadwritelock.service; import java.util.concurrent.locks.ReentrantReadWriteLock; public class MyService { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private String username = "abc"; public void testMethod() { try { lock.readLock().lock(); System.out.println("begin "+Thread.currentThread().getName()+ System.currentTimeMillis()); System.out.println("print service"+ username); Thread.sleep(4000); System.out.println("end "+Thread.currentThread().getName()+""+System.currentTimeMillis()); } catch (InterruptedException e) { throw new RuntimeException(e); }finally { lock.readLock().unlock(); } } }
-
创建一个reentrantreadwritelock.thread包,在包下创建ThreadA类
package reentrantreadwritelock.thread; import reentrantreadwritelock.service.MyService; public class ThreadA extends Thread{ private MyService myService = new MyService(); public ThreadA(MyService myService) { this.myService = myService; } @Override public void run() { myService.testMethod(); } }
-
创建一个reentrantreadwritelock.thread包,在包下创建ThreadB类
package reentrantreadwritelock.thread; import reentrantreadwritelock.service.MyService; public class ThreadB extends Thread{ private MyService myService = new MyService(); public ThreadB(MyService myService) { this.myService = myService; } @Override public void run() { myService.testMethod(); } }
-
创建一个reentrantreadwritelock.thread包,在包下创建ThreadB类
package reentrantreadwritelock.test; import reentrantreadwritelock.service.MyService; import reentrantreadwritelock.thread.ThreadA; import reentrantreadwritelock.thread.ThreadB; public class Run { public static void main(String[] args) { MyService myService =new MyService(); ThreadA a = new ThreadA(myService); a.setName("a"); a.start(); ThreadB b = new ThreadB(myService); b.setName("b"); b.start(); } }
即创建一个MyService 实例,将其传入ThreadA和ThreadB实例中,两个线程共用一把锁。读锁是共享锁,所以两个读操作不会互斥,可以同时进行读操作,减少同步耗费的时间。
2.写写互斥
-
创建一个reentrantreadwritelock.service包,在包下创建MyService2 类
package reentrantreadwritelock.service; import java.util.concurrent.locks.ReentrantReadWriteLock; public class MyService2 { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private String username = "abc"; public void write() { try { lock.writeLock().lock(); System.out.println("获得写锁 "+Thread.currentThread().getName()+ System.currentTimeMillis()); Thread.sleep(10000); } catch (InterruptedException e) { throw new RuntimeException(e); }finally { lock.writeLock().unlock(); } } }
-
创建一个reentrantreadwritelock.thread包,在包下创建ThreadA2 类
package reentrantreadwritelock.thread; import reentrantreadwritelock.service.MyService2; public class ThreadA2 extends Thread{ private MyService2 myService = new MyService2(); public ThreadA2(MyService2 myService) { this.myService = myService; } @Override public void run() { myService.write(); } }
-
创建一个reentrantreadwritelock.thread包,在包下创建ThreadB2 类
package reentrantreadwritelock.thread; import reentrantreadwritelock.service.MyService2; public class ThreadB2 extends Thread{ private MyService2 myService = new MyService2(); public ThreadB2(MyService2 myService) { this.myService = myService; } @Override public void run() { myService.write(); } }
-
创建一个reentrantreadwritelock.test包,在包下创建Run2 类
package reentrantreadwritelock.test; import reentrantreadwritelock.service.MyService2; import reentrantreadwritelock.thread.ThreadA2; import reentrantreadwritelock.thread.ThreadB2; public class Run2 { public static void main(String[] args) { MyService2 myService =new MyService2(); ThreadA2 a = new ThreadA2(myService); a.setName("a"); a.start(); ThreadB2 b = new ThreadB2(myService); b.setName("b"); b.start(); } }
即创建一个MyService2实例,2个线程传入同一个MyService2实例,线程调用MyService2同一个方法,该方法使用写锁,此时写写互斥,需要等第一个线程执行方法结束后,第二个线程才能执行方法内的任务。
3.读写互斥
-
创建一个reentrantreadwritelock.service包,在包下创建MyService3 类
package reentrantreadwritelock.service; import java.util.concurrent.locks.ReentrantReadWriteLock; public class MyService3 { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public void read() { try { lock.readLock().lock(); System.out.println("获得读锁"+Thread.currentThread().getName()+System.currentTimeMillis()); Thread.sleep(10000); } catch (InterruptedException e) { throw new RuntimeException(e); }finally { lock.readLock().unlock(); } } public void write() { try { lock.writeLock().lock(); System.out.println("获得写锁"+Thread.currentThread().getName()+System.currentTimeMillis()); Thread.sleep(10000); } catch (InterruptedException e) { throw new RuntimeException(e); }finally { lock.writeLock().unlock(); } } }
-
创建一个reentrantreadwritelock.thread包,在包下创建ThreadA3 类
package reentrantreadwritelock.thread; import reentrantreadwritelock.service.MyService3; public class ThreadA3 extends Thread{ private MyService3 myService = new MyService3(); public ThreadA3(MyService3 myService) { this.myService = myService; } @Override public void run() { myService.read(); } }
-
创建一个reentrantreadwritelock.thread包,在包下创建ThreadB3 类
package reentrantreadwritelock.thread; import reentrantreadwritelock.service.MyService3; public class ThreadB3 extends Thread{ private MyService3 myService = new MyService3(); public ThreadB3(MyService3 myService) { this.myService = myService; } @Override public void run() { myService.write(); } }
-
创建一个reentrantreadwritelock.test包,在包下创建Run3 类
package reentrantreadwritelock.test; import reentrantreadwritelock.service.MyService3; import reentrantreadwritelock.thread.ThreadA3; import reentrantreadwritelock.thread.ThreadB3; public class Run3 { public static void main(String[] args) { MyService3 myService =new MyService3(); ThreadA3 a = new ThreadA3(myService); a.setName("a"); a.start(); ThreadB3 b = new ThreadB3(myService); b.setName("b"); b.start(); } }
即创建一个MyService3实例,2个线程传入同一个MyService3实例,线程A调用MyService3的read方法,线程B调用MyService3的write方法,此时读写互斥,需要等第一个线程执行读方法结束后,第二个线程才能执行写方法内的任务。
4.写读互斥
即跟读写互斥相同,线程AB调用的方法互换即可。
总结
- 使用Condition对象,Condition对象是跟ReentrantLock对象绑定的。Condition有await和signal、signalAll方法,通过awiat方法使线程进入等待状态,signal唤醒线程,signalAll唤醒全部线程。
- 通知部分线程时,可以使用创建多个Condition实例,通过调用指定的Condition对象的signal/signalAll方法唤醒指定部分的线程。
- 实现生产/消费模式多对多时,需要使用while来判断,并且唤醒方法用signalAll,还有就是用多个线程实例来操作这个生产和消费的方法。生产和消费的方法使用同步锁lock。
- 公平锁采用先到先得的策略,每次获取锁之前都会检查队列有没有排队等待的线程,没有才会尝试获取锁,如果有就将当前线程追加到队列中。
非公平锁,采用“有机会插队”的策略,一个线程获取锁之前,要先尝试获取锁,而不是在队列中等待;如果获取锁没有成功,那么将自身追加到队列中进行等待。即比公平锁多了一个线程未入队时的尝试获取锁操作(插队操作)。
公平锁在进入方法后,就会申请锁资源,查看等待队列中是否有线程等待,有则加入该等待队列,没有则获取锁资源。 - ReentrantLock具有完全互斥排他的特点,同一时间只有一个县城在执行ReentrantLock.lock()方法后面的任务,保证了同时写实例变量的线程安全性,但效率非常低下。
ReentrantReadWriteLock读写锁,可以在同时进行读操作时不需要同步执行,提升了运行速度,加快运行效率。
ReentrantLock和ReentrantReadWriteLock之间没有继承关系。
读写锁表示有2个锁,一个是读操作相关的锁,也叫共享锁,另一个是写操作相关的锁,也叫排它锁。读锁之间不互斥,读锁和写锁与写锁和写锁之间互斥。即出现写锁就出现互斥同步的效果。 - Lock对象比synchronized关键具有更多更细的方法,Lock对象是synchronized关键字的进阶。
并发包中,大量的的类使用了Lock接口作为同步的处理方式。