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

  

  

java并发学习--第七章 JDK提供的线程工具类的更多相关文章

  1. java并发学习--第四章 JDK提供的线程原子性操作工具类

    在了解JDK提供的线程原子性操作工具类之前,我们应该先知道什么是原子性:在多线程并发的条件下,对于变量的操作是线程安全的,不会受到其他线程的干扰.接下来我们就学习JDK中线程的原子性操作. 一.CAS ...

  2. java并发编程系列原理篇--JDK中的通信工具类Semaphore

    前言 java多线程之间进行通信时,JDK主要提供了以下几种通信工具类.主要有Semaphore.CountDownLatch.CyclicBarrier.exchanger.Phaser这几个通讯类 ...

  3. java并发学习--第六章 线程之间的通信

    一.等待通知机制wait()与notify() 在线程中除了线程同步机制外,还有一个最重要的机制就是线程之间的协调任务.比如说最常见的生产者与消费者模式,很明显如果要实现这个模式,我们需要创建两个线程 ...

  4. <<java 并发编程>>第七章:取消和关闭

    Java没有提供任何机制来安全地终止线程,虽然Thread.stop和suspend等方法提供了这样的机制,但是存在严重的缺陷,应该避免使用这些方法.但是Java提供了中断Interruption机制 ...

  5. Java并发(基础知识)——显示锁和同步工具类

    显示锁                                                                                     Lock接口是Java ...

  6. Java NIO学习(Path接口、Paths和Files工具类的使用)

    NIO学习:Paths和Files工具类的使用 JDK1.7引入了新的IO操作类.在java.nio.file包下,Java NIO Path接口和Files类. Path接口:Path表示的是一个目 ...

  7. java并发学习第五章--线程中的锁

    一.公平锁与非公平锁 线程所谓的公平,就是指的是线程是否按照锁的申请顺序来获取锁,如果是遵守顺序来获取,这就是个公平锁,反之为非公平锁. 非公平锁的优点在于吞吐量大,但是由于其不是遵循申请锁的顺序来获 ...

  8. java并发学习--第三章 线程安全问题

    线程的安全问题一直是我们在开发过程中重要关注的地方,出现线程安全问题的必须满足两个条件:存在着两个或者两个以上的线程:多个线程共享着共同的一个资源, 而且操作资源的代码有多句.接下来我们来根据JDK自 ...

  9. java并发编程实战《二十一》无锁工具类

    不安全的累加代码,如下 1 public class Test { 2 long count = 0; 3 void add10K() { 4 int idx = 0; 5 while(idx++ & ...

随机推荐

  1. leetcode 回文二叉树

    C++最简单的方法,遍历存在vector<int> ivec容器中,然后头尾对应比较 O(n)时间,O(n)空间 /** * Definition for singly-linked li ...

  2. delphi DLL image 动态绘图 句柄处理

    在调用DLL 动态在T Image 绘图时,传入  Image.Canvas.Handle 后,却总是绘不上,有时偶尔能绘上,却没搞清原因,而同样的代码,传入窗体的 Handle ,绘图却正常. 经过 ...

  3. Quaternions 四元数

    四元数是一个乱七八糟得到东西还没搞懂搞懂后再补 先添加unity API transform.rotation 是Quaternionlei类并非Vector3向量 不能进行直接转化 那如何将Vect ...

  4. 中国MOOC_面向对象程序设计——Java语言_第1周 类与对象_1分数

    第1周编程题 查看帮助 返回   我们在题目说明中给出了一部分代码,你需要在这部分代码的基础上,按照题目说明编写代码,然后将两部分代码一起提交. 依照学术诚信条款,我保证此作业是本人独立完成的. 温馨 ...

  5. 源特定组播(SSM:Source Specific Multicast)

    源特定组播(SSM:Source Specific Multicast)是一种区别于传统组播的新的业务模型,它使用组播组地址和组播源地址同时来标识一个组播会话,而不是向传统的组播服务那样只使用组播组地 ...

  6. ES6标准入门 第五章:数值的扩展

    1.二进制和八进制数值表示法 二进制前缀:0b(或0B):   八进制前缀:0o(或0O). 注意:ES5的严格模式 和 ES6中不再允许使用 0  表示八进制. 将二进制和八进制数值转换为十进制数值 ...

  7. spring(一) IOC 控制反转 、DI 依赖注入

    IOC 控制反转:创建对象的方式  变成了由Spring来主导 IOC底层原理:对象工厂 1.导入jar包:4个核心jar和1个依赖jar spring-beans-4.3.9.RELEASE.jar ...

  8. 【SD系列】SAP SD模块-送达方和售达方的区别和联系

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[SD系列]SAP SD模块-送达方和售达方的区 ...

  9. 应用安全 - 软件漏洞 - sudo漏洞汇总

    sudo Potential bypass of Runas user restrictions(CVE-2019-14287 ) Date:2019.10.14 类型: sudo提权漏洞 影响版本: ...

  10. “EndExecuteNonQuery”方法没有任何重载采用“0”个参数

    EndExecuteNonQuery需要参数IAsyncResult asyncResult myCmd.ExecuteNonQuery();//执行 ExecuteNonQuery 返回受影响行数