java并发学习--第七章 JDK提供的线程工具类

时间:2021-12-17 04:49:01

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

运行的结果:

java并发学习--第七章 JDK提供的线程工具类

根据运行结果我们可以清楚的看到,在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();
}
}

运行的结果:

java并发学习--第七章 JDK提供的线程工具类

可以看到如果只有两个线程,交换的数据是确定的,如果有多个线程呢,我们在测试类中多添加几个线程:

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

运行的结果:

java并发学习--第七章 JDK提供的线程工具类

可以看到,多个线程进行交换数据,数据的交换是随机的。

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

  运行的结果:

java并发学习--第七章 JDK提供的线程工具类

根据运行的结果我们可以看到,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(); } }

运行的结果:

  java并发学习--第七章 JDK提供的线程工具类

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