ReentrantLock 的一些方法

参考知乎关于lockInterruptibly用法
参考这位博主的多线程文章

getHoldCount() 方法:查询当前线程保持此锁定的个数,也就是调用 lock() 的次数;请注意这是当前线程锁定的次数

getQueueLength() 方法:返回正等待获取此锁定的线程估计数目;

isFair() 方法:判断是不是公平锁;


lockInterruptibly()

此方法比较特别,指的深入探究一下,通过网上查阅资料,首先研究一下线程的中断机制

  • 线程在sleep或wait,join, 此时如果别的进程调用此进程的 interrupt()方法,此线程会被唤醒并被要求处理InterruptedException,即需要手工处理异常
  • 此线程在运行中, 则不会收到提醒。但是 此线程的 “打扰标志”会被设置, 可以通过isInterrupted()查看并 作出处理。

lockInterruptibly的方法原理是:

  • 线程在请求lock并被阻塞时,如果被interrupt,则“此线程会被唤醒并被要求处理InterruptedException”。
  • 并且如果线程已经被interrupt,再使用lockInterruptibly的时候,此线程也会被要求处理interruptedException

下面是原作者的例子:


public class TestLock {

	// @Test
	public void test() throws Exception {
		final Lock lock = new ReentrantLock();
		lock.lock();

		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				lock.lock();
				System.out.println(Thread.currentThread().getName()+" "+Thread.currentThread().isInterrupted());
				System.out.println(Thread.currentThread().getName() + " interrupted.");
			}
		}, "child thread -1");

		t1.start();
		Thread.sleep(1000);

		t1.interrupt();

		Thread.sleep(4000);
		System.out.println(1);
		lock.unlock();
		
	}

	public static void main(String[] args) throws Exception {
		new TestLock().test();
	}

}
运行结果:
1
child thread -1 true
child thread -1 interrupted.

可以看出lock方法对于子线程在等待锁过程中的中断,没有采取任何措施,只是将子线程的中断位设置了


public class TestLockInterruptibly {

	// @Test
	public void test3() throws Exception {
		final Lock lock = new ReentrantLock();
		lock.lock();

		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
				
					lock.lockInterruptibly(); //子线程响应中断抛出异常
				} catch (InterruptedException e) {
					System.out.println(Thread.currentThread().getName() + " interrupted.");
				}
			}
		}, "child thread -1");

		t1.start();
	
		Thread.sleep(1000);

		t1.interrupt();

		Thread.sleep(1000000);
	}

	public static void main(String[] args) throws Exception {
		new TestLockInterruptibly().test3();
	}
}

结果:
child thread -1 interrupted.

这种情况对应线程已经被中断,然后又lockInterruptibly,会抛出中断异常
public class TestLockInterruptibly {

	// @Test
	public void test3() throws Exception {
		final Lock lock = new ReentrantLock();
		lock.lock();

		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(500);
					lock.lockInterruptibly();
				} catch (InterruptedException e) {
					System.out.println(Thread.currentThread().getName() + " interrupted.");
				}
			}
		}, "child thread -1");

		t1.start();
		t1.interrupt();
		Thread.sleep(1000);

		t1.interrupt();

		Thread.sleep(1000000);
	}

	public static void main(String[] args) throws Exception {
		new TestLockInterruptibly().test3();
	}
}

运行结果:
child thread -1 interrupted.

ReentrantReadWriteLock的知识

ReentrantReadWriteLock 有两个锁:一个是与读相关的锁,称为“共享锁”;另一个是与写相关的锁,称为“排它锁”,也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。

在没有线程进行写操作时,进行读操作的多个线程都可以获取到读锁,而写操作的线程只有获取写锁后才能进行写入操作。即:多个线程可以同时进行读操作,但是同一时刻只允许一个线程进行写操作。

读读共享;
写写互斥;
读写互斥;
写读互斥;

写写共享的例子:
public class ReentrantReadWriteLockDemo {

	private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

	public static void main(String[] args) {

		ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();

		new Thread(() -> demo.read(), "ThreadA").start();
		new Thread(() -> demo.read(), "ThreadB").start();
	}

	private void read() {
		try {
			try {
				lock.writeLock().lock();
				for (int i = 0; i < 5; i++) {
					System.out.println("获得读锁" + Thread.currentThread().getName() + " 时间:" + System.currentTimeMillis());
					Thread.sleep(20);
				}
			} finally {
				lock.writeLock().unlock();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

获得读锁ThreadA 时间:1539446947855
获得读锁ThreadA 时间:1539446947878
获得读锁ThreadA 时间:1539446947898
获得读锁ThreadA 时间:1539446947918
获得读锁ThreadA 时间:1539446947940
获得读锁ThreadB 时间:1539446947963
获得读锁ThreadB 时间:1539446947983
获得读锁ThreadB 时间:1539446948004
获得读锁ThreadB 时间:1539446948024
获得读锁ThreadB 时间:1539446948044


即写锁的获取是串行的

读读的例子:


public class ReentrantReadWriteLockDemo {

	private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

	public static void main(String[] args) {

		ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();

		new Thread(() -> demo.read(), "ThreadA").start();
		new Thread(() -> demo.read(), "ThreadB").start();
	}

	private void read() {
		try {
			try {
				lock.readLock().lock();
				for (int i = 0; i < 5; i++) {
					System.out.println("获得读锁" + Thread.currentThread().getName() + " 时间:" + System.currentTimeMillis());

					Thread.sleep(20);
				}
			} finally {
				lock.readLock().unlock();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

获得读锁ThreadA 时间:1539447173394
获得读锁ThreadB 时间:1539447173396
获得读锁ThreadB 时间:1539447173416
获得读锁ThreadA 时间:1539447173416
获得读锁ThreadA 时间:1539447173436
获得读锁ThreadB 时间:1539447173436
获得读锁ThreadA 时间:1539447173456
获得读锁ThreadB 时间:1539447173456
获得读锁ThreadA 时间:1539447173476
获得读锁ThreadB 时间:1539447173476

可以看出A/B交替运行!

CountDownLatch

CountDownLatch是一个多线程控制工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。

比如下面这个例子,车子等人,当七个人到齐后,开车
public class CountDownLatchDemo {
	  private static final int THREAD_COUNT_NUM = 7;
	    private static CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT_NUM);

	    public static void main(String[] args) throws InterruptedException {

	        for (int i = 1; i <= THREAD_COUNT_NUM; i++) {
	            int index = i;
	            new Thread(() -> {
	                try {
	                    System.out.println("第" + index + "个人上车!");
	                    //模拟不同人上车时间
	                    Thread.sleep(new Random().nextInt(1000));
	                } catch (InterruptedException e) {
	                    e.printStackTrace();
	                }
	                //每上车一个人,需要等待的数减1
	                countDownLatch.countDown();
	            }).start();
	        }
	        //等待检查,即上述7个线程执行完毕之后,执行await后边的代码
	        countDownLatch.await();
	        System.out.println("七个人到齐,开车");
	    }
}

一般使用的时候,在主线程启动启动子线程后,会执行countDownLatch.await();阻塞主线程,让子线程得到机会执行,
每一次子线程执行完毕后,执行 countDownLatch.countDown();通知主线程,当countdown到0后,主线程开始执行 countDownLatch.await();后面的代码

循环屏障 CyclicBarrier

CyclicBarrier 和 countDownLatch 都是类似功能,但是CyclicBarrier 会阻塞子进程,而countdownLatch会阻塞主进程,而且CyclicBarrier可以重置操作,countDownLatch则是一次性的

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。

CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。

CyclicBarrier 强调的是 N 个线程,大家相互等待,只要有一个没完成,所有人都得等着。

下面是从网上博客摘抄的例子,召唤神龙,需要7个法师去寻找龙珠,但这7个法师并不是一下子就能号召起来的,所以要等待召集齐7个法师,然后在秋名山顶烧香拜佛为这7位法师送行,让他们同时出发,前往不同的地方寻找龙珠(敲黑板:这是第一个屏障点),在这七位法师临行时约定找到龙珠之后,还回到这个地方等待其他法师找到龙珠之后一起去见我。几年之后,第一个法师回来了,然后等待其他的法师……最后所有的法师全部到齐(敲黑板:这是第一个屏障点),然后组队来找我召唤神龙。

public class SummonDragonDemo {

    private static final int THREAD_COUNT_NUM = 7;

    public static void main(String[] args) {

        //设置第一个屏障点,等待召集齐7位法师
        CyclicBarrier callMasterBarrier = new CyclicBarrier(THREAD_COUNT_NUM, new Runnable() {
            @Override
            public void run() {
                System.out.println("7个法师召集完毕,同时出发,去往不同地方寻找龙珠!");
                summonDragon();
            }
        });
        //召集齐7位法师
        for (int i = 1; i <= THREAD_COUNT_NUM; i++) {
            int index = i;
            new Thread(() -> {
                try {
                    System.out.println("召集第" + index + "个法师");
                    callMasterBarrier.await(); //子线程阻塞自己
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    /**
    * 召唤神龙:1、收集龙珠;2、召唤神龙
    */
    private static void summonDragon() {
        //设置第二个屏障点,等待7位法师收集完7颗龙珠,召唤神龙
        CyclicBarrier summonDragonBarrier = new CyclicBarrier(THREAD_COUNT_NUM, new Runnable() {
            @Override
            public void run() {
                System.out.println("集齐七颗龙珠!召唤神龙!");
            }
        });
        //收集7颗龙珠
        for (int i = 1; i <= THREAD_COUNT_NUM; i++) {
            int index = i;
            new Thread(() -> {
                try {
                    System.out.println("第" + index + "颗龙珠已收集到!");
                    summonDragonBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
执行结果:
召集第1个法师
召集第3个法师
召集第2个法师
召集第4个法师
召集第5个法师
召集第6个法师
召集第7个法师
7个法师召集完毕,同时出发,去往不同地方寻找龙珠!
第1颗龙珠已收集到!
第2颗龙珠已收集到!
第3颗龙珠已收集到!
第4颗龙珠已收集到!
第5颗龙珠已收集到!
第6颗龙珠已收集到!
第7颗龙珠已收集到!
集齐七颗龙珠!召唤神龙!


new CyclicBarrier(int num, Runnable runable) 意思是创建一个内存屏障,只有num个子线程都到达屏障处,则有最后一个到达的线程执行runable里的动作

通过下列图,可以看到其实是thread 6方法在执行召唤神龙的任务
线程学习(2)

相关文章:

  • 2021-12-18
  • 2021-08-01
  • 2021-11-08
  • 2021-07-11
  • 2022-01-11
  • 2022-12-23
  • 2021-10-16
  • 2021-06-16
猜你喜欢
  • 2021-08-04
  • 2021-07-14
  • 2022-12-23
  • 2021-10-16
  • 2021-06-03
  • 2021-11-20
  • 2021-07-08
相关资源
相似解决方案