java并发学习--第七章 JDK提供的线程工具类
一、ThreadLocal
ThreadLocal类用于隔离多线程中使用的对象,为ThreadLocal类中传递的泛型就是要隔离的对象,简单的来说:如果我们在主线程创建了一个对象,并且需要给下面的多线程任务都传递这个对象,那么如果这个对象传递到ThreadLocal,那么每个线程获取的对象都是独立的,不会受其他线程的改变而改变。
ThreadLocal中一共有三个常用方法:
get()方法:获取与当前线程关联的ThreadLocal值。
set(T value)方法:设置与当前线程关联的ThreadLocal值。
initialValue()方法:设置与当前线程关联的ThreadLocal初始值。
我们来看一个列子,创建两个线程,两个线程共同使用一个对象,我们来观察这个对象的值以及ThreadLocal中这个对象的值:
对象User类:
public class User { int num; public User(int num) { this.num = num; } /** * 我们只使用get方法,并且每次获取num都为其加1 * * 使用synchronized保证getNum获取的num是线程安全的 * @return */ synchronized public int getNum() { return num++; } }
线程任务类:
public class ThreadlocaDemo extends Thread{ User user; ThreadlocaDemo(User user){ this.user=user; } /** * 创建一个ThreadLocal对象,为它的泛型传入User */ ThreadLocal<User> userLoacl = new ThreadLocal<User>(){ /** * 初始化方法,将设置user中的初始值 */ @Override protected User initialValue() { User user=new User(10); return user; } }; @Override public void run() { for (int i = 1; i <3 ; i++) { //休息一会儿 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //打印user中获取的值 System.out.println(getName()+"user对象的值"+user.getNum()); //打印ThreadLocal中的user对象的值 System.out.println(getName()+"ThreadLocal中的user对象的值"+userLoacl.get().getNum()); } } }
测试类:
public class Main { public static void main(String[] args) { //创建一个共用的对象 User user=new User(10); //创建两个线程任务 ThreadlocaDemo threadlocaDemo1=new ThreadlocaDemo(user); ThreadlocaDemo threadlocaDemo2=new ThreadlocaDemo(user); threadlocaDemo1.setName("这是1号线程:"); threadlocaDemo2.setName("这是2号线程:"); threadlocaDemo1.start(); threadlocaDemo2.start(); } }
运行的结果:
根据运行结果我们可以清楚的看到,在ThreadLocal中的user对象是隔离的,外面的user对象没有被隔离,被两个线程都进行修改过。
二、Exchanger
Exchanger可以交换两个线程的数据,它的实现思想是当线程进行到Exchanger类调用的exchange方法时,会阻塞当前线程,直到有其他线程也进入了exchange方法中就开始交换两个线程的数据。
需要注意的是,exchange只能作为两个线程进行交换数据,如果有多个线程,那么获取的数据是随机的。
我们来看一个列子:
线程任务类,交换一个字符串数据
public class ExchangerThread extends Thread{ //Exchanger对象 Exchanger<String> change; //当前线程中的数据,需要交换的数据 String thisStr; public ExchangerThread(Exchanger<String> change, String thisStr) { this.change = change; this.thisStr = thisStr; } @Override public void run() { try { //exchange方法,将要交换的数据传递到exchange方法中 //获取的值就是交换后的数据 String getStr=change.exchange(thisStr); System.out.println(getName()+getStr); } catch (InterruptedException e) { e.printStackTrace(); } } }
测试类:
public class ExchangerDemo { public static void main(String[] args) { //创建Exchanger对象 Exchanger<String> exchanger=new Exchanger<>(); //创建两个线程交换数据 ExchangerThread exchangerThread1=new ExchangerThread(exchanger,"这是1号线程的数据"); ExchangerThread exchangerThread2=new ExchangerThread(exchanger,"这是2号线程的数据"); exchangerThread1.setName("我是1号线程,交互获取的数据是:"); exchangerThread2.setName("我是2号线程,交互获取的数据是:"); exchangerThread1.start(); exchangerThread2.start(); } }
运行的结果:
可以看到如果只有两个线程,交换的数据是确定的,如果有多个线程呢,我们在测试类中多添加几个线程:
public class ExchangerDemo { public static void main(String[] args) { //创建Exchanger对象 Exchanger<String> exchanger=new Exchanger<>(); //创建两个线程交换数据 ExchangerThread exchangerThread1=new ExchangerThread(exchanger,"这是1号线程的数据"); ExchangerThread exchangerThread2=new ExchangerThread(exchanger,"这是2号线程的数据"); ExchangerThread exchangerThread3=new ExchangerThread(exchanger,"这是3号线程的数据"); ExchangerThread exchangerThread4=new ExchangerThread(exchanger,"这是4号线程的数据"); exchangerThread1.setName("我是1号线程,交互获取的数据是:"); exchangerThread2.setName("我是2号线程,交互获取的数据是:"); exchangerThread3.setName("我是3号线程,交互获取的数据是:"); exchangerThread4.setName("我是4号线程,交互获取的数据是:"); exchangerThread1.start(); exchangerThread2.start(); exchangerThread3.start(); exchangerThread4.start(); } }
运行的结果:
可以看到,多个线程进行交换数据,数据的交换是随机的。
三、CountDownLatch
CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作前,它允许一个或多个线程一直等待。简单的说就是一个线程组正在工作,但是他们执行的任务都有一道门,这道门是关着的,会导致每个线程接下来的任务都暂停进行,简单的说就是线程被阻塞了,只有当所有线程都在这个门前,即所有线程都被阻塞了,才会继续执行。
当然了,上面的例子不太准确,CountDownLatch中实现所有线程都完成任务才能继续进行的方式不是这样,它是有一个计算器,当这个计算器的值等于0时,才会释放当前阻塞的所有线程。
CountDownLatch中常用的方法:
getCount():获取当前计数器剩余计数
countDown():计算器的值减1
await():阻塞当前线程,只有当计算器的值等于0时,才会释放当前线程
例子:
创建一个CountDownLatch对象,它的计数器的值为3,我们创建3个线程,每个线程执行完任务后先countDown(),再调用await()方法等待:
线程任务类:
public class CountDownLatchDemo extends Thread { // 创建CountDownLatch对象 CountDownLatch latch; // 休眠时间 int num; // 构造器传参 public CountDownLatchDemo(CountDownLatch latch, int num) { super(); this.latch = latch; this.num = num; } @Override public void run() { try { System.out.println("当前执行的是:" + getName()); // 让线程执行任务 Thread.sleep(num); // 线程执行完成任务后,对CountDownLatch的值减1 latch.countDown(); System.out.println(getName() + "完成任务,等待其他线程执行任务"); // 这里让线程阻塞,只有当CountDownLatch等于0时才继续执行线程 // 即当所有的线程都完成了任务,才开始一起继续执行下面的任务 latch.await(); System.out.println("所有线程都完成后才执行的代码:" + getName()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
测试类:
public class Main { public static void main(String[] args) { //创建一个CountDownLatch对象,设置值为3,表示当有3个线程都完成任务后,才开始执行阻塞后的代码 CountDownLatch latch=new CountDownLatch(3) ; //创建3个线程,每个线程的CountDownLatch对象是同一个 CountDownLatchDemo countDownLatchDemo1=new CountDownLatchDemo(latch,100); CountDownLatchDemo countDownLatchDemo2=new CountDownLatchDemo(latch,1000); CountDownLatchDemo countDownLatchDemo3=new CountDownLatchDemo(latch,2000); countDownLatchDemo1.setName("1号线程"); countDownLatchDemo2.setName("2号线程"); countDownLatchDemo3.setName("3号线程"); countDownLatchDemo1.start(); countDownLatchDemo2.start(); countDownLatchDemo3.start(); } }
运行的结果:
根据运行的结果我们可以看到,1、2、3号线程在执行完成任务后都等待,等待3号执行完成任务,使得计算器的值为0时,开始释放所有线程。
四、CyclicBarrier、
CyclicBarrier与countDownLatch相识,都有一个计算器,但是两个类不同的是:countDownLatch都是当计算器的值为0时才释放所有线程,而CyclicBarrier是当阻塞的线程数等于计算器的值时,才开始释放线程。这两个类最大的区别就是一个是线程开始时的阻塞,一个是线程结束前的阻塞。
CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
我们来看一个例子:阻塞了三个线程后,开始执行:
线程任务类:
public class CyclicBarrierDemo extends Thread{ //创建CyclicBarrier对象 CyclicBarrier cyclicBarrier; //休眠时间 int num; public CyclicBarrierDemo(CyclicBarrier cyclicBarrier, int num) { super(); this.cyclicBarrier = cyclicBarrier; this.num = num; } @Override public void run() { try { System.out.println("任务开始,当前执行的是:"+getName()); //让线程执行任务 Thread.sleep(num); System.out.println(getName()+"准备工作完成,等待其他线程执行任务"); //执行完成后,等待其他线程完成任务 cyclicBarrier.await(); Thread.sleep(num); System.out.println("所有线程都准备好后才执行的代码:我是"+getName()); } catch (Exception e) { e.printStackTrace(); } } }
测试类:
public static void main(String[] args) { //阻塞的线程数量 int waitNum=3; //创建cyclicBarrier对象 CyclicBarrier cyclicBarrier=new CyclicBarrier(waitNum); CyclicBarrierDemo cyclicBarrierDemo1=new CyclicBarrierDemo(cyclicBarrier,100); CyclicBarrierDemo cyclicBarrierDemo2=new CyclicBarrierDemo(cyclicBarrier,1000); CyclicBarrierDemo cyclicBarrierDemo3=new CyclicBarrierDemo(cyclicBarrier,2000); cyclicBarrierDemo1.setName("1号线程:"); cyclicBarrierDemo2.setName("2号线程:"); cyclicBarrierDemo3.setName("3号线程:"); cyclicBarrierDemo1.start(); cyclicBarrierDemo2.start(); cyclicBarrierDemo3.start(); } }
运行的结果:
五、Semaphore
semaphore的作用是控制资源的访问,他能够限制当前资源能够被多少个线程所访问。它和线程池有些类似,但是与线程池不同的是,线程池是提前生成好了多个线程放进线程池里,如果超出线程池设置的数量,那么线程是不会被创建,但是semaphore超出了设置的线程数,还会继续创建线程。
semaphore中的方法:
acquire(): 获取许可
release(): 释放资源
availablePermits(): 获取当前可用的资源数量
semaphore的构造方法
public Semaphore(int permits,boolean fair)
permits:初始化设置线程的数据
fair:表示是否以线程获取锁的顺序来执行线程,就是是否用公平锁,false表示不使用,默认是false
我们来看一个例子,创建1个线程数组和一个semaphore任务类,线程数组的长度为10,semaphore任务类的值为5,我们观察它们的执行方式:
semaphore任务类:
public class SemaphoreDemo extends Thread { Semaphore semaphore; public SemaphoreDemo(Semaphore semaphore) { super(); this.semaphore = semaphore; } @Override public void run() { try { // 获取许可 semaphore.acquire(); System.out.println("任务开始,当前执行的是:" + getName()); // 让线程执行任务 Thread.sleep(2000); // 执行完成后,释放资源 semaphore.release(); } catch (Exception e) { e.printStackTrace(); } } }
测试类:
public class Main { public static void main(String[] args) { // 创建Semaphore对象 Semaphore semaphore = new Semaphore(5); // 创建一个长度为10的线程数组 Thread[] threadAry = new Thread[10]; for (int i = 0; i < threadAry.length; i++) { // 线程数组的值都添加线程任务 SemaphoreDemo semaphoreDemo = new SemaphoreDemo(semaphore); semaphoreDemo.setName(i + "号线程"); threadAry[i] = semaphoreDemo; } // 执行线程任务 for (int i = 0; i < threadAry.length; i++) { threadAry[i].start(); } } }