线程高级——线程状态、volatile关键字、原子性、并发包、死锁、线程池

 一、线程状态

1.1 线程状态概述

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在API java.lang.Thread.State 这个枚举中给出了六种线程状态:
1、NEW( 新建):
线程刚被创建,但是并未启动。还没调用 start 方法。 MyThread t = new MyThread 只有线程对象,没有线程特征。
2、 Runnable( 运行):
线程可以在 java 虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决 于操作系统处理器。调用了 t.start() 方法 :就绪(经典教法)
3、 Blocked( 锁阻 ):
当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入 Blocked状态;当该线程持有锁时,该线程将变成 Runnable 状态
4、 Waiting( 无限 等待 ):
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入 Waiting 状态。进 入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify 或者 notifyAll 方法 才能够唤
5、 Timed Waiting( 计时 等待 ):
waiting 状态,有几个方法有超时参数,调用他们将进入 Timed Waiting 状态。这 一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有 Thread.sleep 、 Object.wait

6、 Teminated(被终止):

因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

1.2 睡眠sleep方法

sleep()静态方法,该方法可以使当前执行的线程睡眠(暂时停止执行)指定的毫秒数。

public class Test{
public static void main(String[] args){
for(int i = 1;i<=5;i++){
Thread.sleep(1000);
System.out.println(i)
}
}
}
这时我们发现主线程执行到 sleep 方法会休眠 1 秒后再继续执行。

1.3 等待和唤醒

Java中,可以通过配合调用Object对象的weit()方法和notify()方法来实现线程之间的通信。

wait()方法:在线程中调用wait()方法,将阻塞当前线程,并释放锁,直至等到其他线程调用了notify()方法进行通知之后,当前线程才能从wait()方法中返回,继续执行下面的操作,此方法必须锁对象调用。

public class Demo1_wait {
public static void main(String[] args) throws InterruptedException {
// 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行
new Thread(() -> {
try {
System.out.println("begin wait ....");
synchronized ("") {
"".wait();
}
System.out.println("over");
} catch (Exception e) {
}
}).start();
}

notify方法:即唤醒,notify方法使原来对象上wait的线程退出waitting状态,使得该线程从等待队列中移入到同步队列中去,等待下一次能够有机会获取到对象监视器锁,此方法必须锁对象调用.

public class Demo2_notify {
public static void main(String[] args) throws InterruptedException {
// 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
new Thread(() -> {
try {
System.out.println("begin wait ....");
synchronized ("") {
"".wait();
}
System.out.println("over");
} catch (Exception e) {
}
}).start();
//步骤2: 加入如下代码后, 3秒后,会执行notify方法, 唤醒wait中线程.
Thread.sleep(3000);
new Thread(() -> {
try {
synchronized ("") {
System.out.println("唤醒");
"".notify();
}
} catch (Exception e) {
}
}).start();
}
}

二、volatile关键字

2、1概念、

volatile关键字解决内存可见性问题,是一种弱形式的同步。

2、2原理

Java语言提供了一种弱同步机制,即volatile变量,用来确保变量的更新操作通知到其他线程。当把变量声明为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的,volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取 volatile 类型的变量时总会返回最新写入的值。

public class VolatileThread extends Thread {
// 定义成员变量
private boolean flag = false ;
public boolean isFlag() { return flag;}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将flag的值更改为true
this.flag = true ;
System.out.println("flag=" + flag);
}
}
public class VolatileThreadDemo {// 测试类
public static void main(String[] args) {
// 创建VolatileThread线程对象
VolatileThread volatileThread = new VolatileThread() ;
volatileThread.start();
// main方法
while(true) {
if(volatileThread.isFlag()) {
System.out.println("执行了======");
}
}
}
}
通过观察控制台,我们看到, VolatileThread 线程中已经将 flag 设置为 true ,但 main() 方法中始终没有读到, 从而没有打印。

2.3. JMM

概述: JMM(Java Memory Model)Java 内存模型 , java 虚拟机规范中所定义的一种内存模型。Java内存模型 (Java Memory Model) 描述了 Java 程序中各种变量 ( 线程共享变量 ) 的访问规则,以及在 JVM 中将 变量存储到内存和从内存中读取变量这样的底层细节。 所有的共享变量都存储于主内存。这里所说的变量指的是实例变量和类变量。不包含局部变量,因为局部变 量是线程私有的,因此不存在竞争问题。每一个线程还存在自己的工作内存,线程 的工作内存,保留了被线程使用的变量的工作副本。线程对变量的所有的操作( 读,取 ) 都必须在工作内存中 完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问 对方工作内存中的变量,线程间变量的值的传递需要通过主内存完成.

2、4 volatile 与 synchronized 的区别

相似处:volatile 的内存语义和 synchronized 有相似之处,具体来说就是,当线程写入了 volatile 变量值时就等价于线程退出 synchronized 同步块(把写入工作内存的变量值同步到主内存),读取 volatile 变量值时就相当于进入 synchronized 同步块( 先清空本地内存变量值,再从主内存获取最新值)。

区别:使用锁的方式可以解决共享变量内存可见性问题,但是使用锁太笨重,因为它会带来线程上下文的切换开销。具体区别如下:

volatile 本质是在告诉 jvm 当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住;
volatile 仅能使用在变量级别;synchronized 则可以使用在变量、方法、和类级别的;
volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性;
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞;
volatile 标记的变量不会被编译器优化;synchronized 标记的变量可以被编译器优化

三、原子性

3、1概述

所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何 因素的干扰而中断,要么所有的操作都不执行。
public class VolatileAtomicThread implements Runnable {
// 定义一个int类型的遍历
private int count = 0 ;
@Override
public void run() {
// 对该变量进行++操作,100次
for(int x = 0 ; x < 100 ; x++) {
count++ ;
System.out.println("count =========>>>> " + count);
}
}
}
public class VolatileAtomicThreadDemo {
public static void main(String[] args) {
// 创建VolatileAtomicThread对象
VolatileAtomicThread volatileAtomicThread = new VolatileAtomicThread() ;
// 开启100个线程对count进行++操作
for(int x = 0 ; x < 100 ; x++) {
new Thread(volatileAtomicThread).start();
}
}
}
执行结果:不保证一定是 10000

3.2. 问题原理说明

以上问题主要是发生在 count++ 操作上:
count++ 操作包含 3 个步骤:
从主内存中读取数据到工作内存
对工作内存中的数据进行 ++ 操作
将工作内存中的数据写回到主内存
count++ 操作不是一个原子性操作,也就是说在某一个时刻对某一个操作的执行,有可能被其他的线程打断

1 )假设此时 x 的值是 100 ,线程 A 需要对改变量进行自增 1 的操作,首先它需要从主内存中读取变量 x 的值。由
CPU 的切换关系,此时 CPU 的执行权被切换到了
B 线程。 A 线程就处于就绪状态, B 线程处于运行状态
2 )线程 B 也需要从主内存中读取 x 变量的值 , 由于线程 A 没有对 x 值做任何修改因此此时 B 读取到的数据还是 100
3 )线程 B 工作内存中 x 执行了 +1 操作,但是未刷新之主内存中
4 )此时 CPU 的执行权切换到了 A 线程上,由于此时线程 B 没有将工作内存中的数据刷新到主内存,因此 A 线程
工作内存中的变量值还是 100 ,没有失效。
A 线程对工作内存中的数据进行了 +1 操作
5 )线程 B 101 写入到主内存
6 )线程 A 101 写入到主内存
虽然计算了 2 次,但是只对 A 进行了 1 次修改。

3.3. volatile原子性测试

// 定义一个int类型的变量
private volatile int count = 0 ;
小结:在多线程环境下, volatile 关键字可以保证共享数据的可见性,但是并不能保证对数据操作的原子性
(在多线程环境下 volatile 修饰的变量也是线程不安全的)。
在多线程环境下,要保证数据的安全性,我们还需要使用锁机制。

3.4. 问题解决

使用锁机制:
我们可以给 count++ 操作添加锁,那么 count++ 操作就是临界区的代码,临界区只能有一个线程去执行,所 以count++ 就变成了原子操作。
public class VolatileAtomicThread implements Runnable {
// 定义一个int类型的变量
private volatile int count = 0 ;
private static final Object obj = new Object();
@Override
public void run() {
// 对该变量进行++操作,100次
for(int x = 0 ; x < 100 ; x++) {
synchronized (obj) {
count++ ;
System.out.println("count =========>>>> " + count);
}
}
}
}

四、原子类

4、1AtomicInteger

AtomicInteger 工具类提供了对整数操作的原子封装。

在 java 中,当我们在多线程情况下,对一个整型变量做加减操作时,如果不加任何的多线程并发控制,大概率会出现线程安全问题,也就是说当多线程同时操作一个整型变量的增减时,会出现运算结果错误的问题。AtomicInteger 工具类就是为了简化整型变量的同步处理而诞生的。

案例改造
public class VolatileAtomicThread implements Runnable {
// 定义一个int类型的变量
private AtomicInteger atomicInteger = new AtomicInteger() ;
@Override
public void run() {
// 对该变量进行++操作,100次
for(int x = 0 ; x < 100 ; x++) {
int i = atomicInteger.getAndIncrement();
System.out.println("count =========>>>> " + i);
}
}
}

4、2 CAS机制实现线程安全

CAS 的全称是: Compare And Swap( 比较再交换 ); 是现代 CPU 广泛支持的一种对内存中的共享数据进行操作 的一种特殊指令。CAS 可以将 read-modify-check-write 转换为原子操作,这个原子操作直接由处理器保证。 CAS机制当中使用了 3 个基本操作数:内存地址 V ,旧的预期值 A ,要修改的新值 B 。 举例:
1. 在内存地址 V 当中,存储着值为 10 的变量。

2. 此时线程 1 想要把变量的值增加 1 。对线程 1 来说,旧的预期值 A=10 ,要修改的新值 B=11

 3. 在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11

4. 线程 1 开始提交更新,首先进行 A 和地址 V 的实际值比较( Compare ),发现 A 不等于 V 的实际值,提交失败。

5. 线程 1 重新获取内存地址 V 的当前值,并重新计算想要修改的新值。此时对线程 1 来说, A=11 B=12 。 这个重新尝试的过程被称为自旋。

6. 这一次比较幸运,没有其他线程改变地址 V 的值。线程 1 进行 Compare ,发现 A 和地址 V 的实际值是相等的。

 7. 线程1进行SWAP,把地址V的值替换为B,也就是12

五、 并发包

5、1ConcurrentHashMap

为什么要使用 ConcurrentHashMap
1. HashMap 线程不安全,会导致数据错乱
2. 使用线程安全的 Hashtable 效率低下
基于以上两个原因,便有了 ConcurrentHashMap 的登场机会。
HashMap 线程不安全演示。
公有、静态的集合:
public class Const {
public static HashMap<String,String> map = new HashMap<>();
}
线程,向 map 中写入数据:
public void run() {
for (int i = 0; i < 500000; i++) {
Const.map.put(this.getName() + (i + 1), this.getName() + i + 1);
}
System.out.println(this.getName() + " 结束!");
}
测试类:
public class Demo {
public static void main(String[] args) throws InterruptedException {
Thread1A a1 = new Thread1A();
Thread1A a2 = new Thread1A();
a1.setName("线程1-");
a2.setName("线程2-");
a1.start();
a2.start();
//休息10秒,确保两个线程执行完毕
Thread.sleep(1000 * 5);
//打印集合大小
System.out.println("Map大小:" + Const.map.size());
}
}
说明:两个线程分别向同一个 map 中写入 50000 个键值对,最后 map size 应为: 100000 ,但多运行几次会
发现有以下几种错误:
1. 假死:

 2. 异常:

 3. 错误结果:

为了保证线程安全,可以使用 Hashtable 。注意:线程中加入了计时
公有、静态的集合:
public class Const {
public static Hashtable<String,String> map = new Hashtable<>();
}
线程,向 map 中写入数据:
public void run() {
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
Const.map.put(this.getName() + (i + 1), this.getName() + i + 1);
}
long end = System.currentTimeMillis();
System.out.println(this.getName() + " 结束!用时:" + (end - start) + " 毫
秒");
}
测试类:
public class Demo {
public static void main(String[] args) throws InterruptedException {
Thread1A a1 = new Thread1A();
Thread1A a2 = new Thread1A();
a1.setName("线程1-");
a2.setName("线程2-");
a1.start();
a2.start();
//休息10秒,确保两个线程执行完毕
Thread.sleep(1000 * 5);
//打印集合大小
System.out.println("Map大小:" + Const.map.size());
}
}
执行结果:

可以看到, Hashtable 保证的线程安全,时间是 2 秒多。
再看 ConcurrentHashMap
公有、静态的集合:
public class Const {
public static ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>
();
}
线程,向 map 中写入数据:
public void run() {
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
Const.map.put(this.getName() + (i + 1), this.getName() + i + 1);
}
long end = System.currentTimeMillis();
System.out.println(this.getName() + " 结束!用时:" + (end - start) + " 毫
秒");
}
测试类:
public class Demo {
public static void main(String[] args) throws InterruptedException {
Thread1A a1 = new Thread1A();
Thread1A a2 = new Thread1A();
a1.setName("线程1-");
a2.setName("线程2-");
a1.start();
a2.start();
//休息10秒,确保两个线程执行完毕
Thread.sleep(1000 * 5);
//打印集合大小
System.out.println("Map大小:" + Const.map.size());
}
}
执行结果:

ConcurrentHashMap 仍能保证结果正确,而且提高了效率。
HashTable 效率低下原因:
public synchronized V put(K key, V value)
public synchronized V get(Object key)
HashTable 容器使用 synchronized 来保证线程安全,但在线程竞争激烈的情况下 HashTable 的效率非常低 下。因为当一个线程访问HashTable 的同步方法,其他线程也访问 HashTable 的同步方法时,会进入阻塞状 态。如线程1 使用 put 进行元素添加,线程 2 不但不能使用 put 方法添加元素,也不能使用 get 方法来获取元 素,所以竞争越激烈效率越低。

 5.2 CountDownLatch

CountDownLatch 允许一个或多个线程等待其他线程完成操作,再执行自己。
例如:线程 1 要执行打印: A C ,线程 2 要执行打印: B ,但线程 1 在打印 A 后,要线程 2 打印 B 之后才能打印 C,所以:线程 1 在打印 A 后,必须等待线程 2 打印完 B 之后才能继续执行。
CountDownLatch 构造方法 :
public CountDownLatch(int count)// 初始化一个指定计数器的CountDownLatch对象
CountDownLatch 重要方法 :
public void await() throws InterruptedException// 让当前线程等待
public void countDown() // 计数器进行减1
示例
1). 制作线程 1
public class ThreadA extends Thread {
private CountDownLatch down ;
public ThreadA(CountDownLatch down) {
this.down = down;
}
@Override
public void run() {
System.out.println("A");
try {
down.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("C");
}
}
2). 制作线程 2
public class ThreadB extends Thread {
private CountDownLatch down ;
public ThreadB(CountDownLatch down) {
this.down = down;
}
@Override
public void run() {
System.out.println("B");
down.countDown();
}
}
3). 制作测试类:
public class Demo {
public static void main(String[] args) {
CountDownLatch down = new CountDownLatch(1);//创建1个计数器
new ThreadA(down).start();
new ThreadB(down).start();
}
}
4). 执行结果:
会保证按: A B C 的顺序打印。
说明:
CountDownLatch count down 是倒数的意思, latch 则是门闩的含义。整体含义可以理解为倒数的门栓, 似乎有一点“ 三二一,芝麻开门 的感觉。
CountDownLatch 是通过一个计数器来实现的,每当一个线程完成了自己的任务后,可以调用 countDown() 方法让计数器-1 ,当计数器到达 0 时,调用 CountDownLatch
await() 方法的线程阻塞状态解除,继续执行。

5.3 CyclicBarrier

CyclicBarrier 的字面意思是可循环使用( Cyclic )的屏障( Barrier )。它要做的事情是,让一组线程到达一 个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线 程才会继续运行。
例如:公司召集 5 名员工开会,等 5 名员工都到了,会议开始。
我们创建 5 个员工线程, 1 个开会线程,几乎同时启动,使用 CyclicBarrier 保证 5 名员工线程全部执行后,再 执行开会线程。
CyclicBarrier 构造方法:
public CyclicBarrier(int parties, Runnable barrierAction)// 用于在线程到达屏障时,优先执
行barrierAction,方便处理更复杂的业务场景
CyclicBarrier 重要方法:
public int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻
塞
示例代码:
1). 制作员工线程:
public class PersonThread extends Thread {
private CyclicBarrier cbRef;
public PersonThread(CyclicBarrier cbRef) {
this.cbRef = cbRef;
}
@Override
public void run() {
try {
Thread.sleep((int) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 到了! ");
cbRef.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
2). 制作开会线程:
public class MeetingThread extends Thread {
@Override
public void run() {
System.out.println("好了,人都到了,开始开会......");
}
}
3). 制作测试类:
public class Demo {
public static void main(String[] args) {
CyclicBarrier cbRef = new CyclicBarrier(5, new MeetingThread());//等待5个线程
执行完毕,再执行MeetingThread
PersonThread p1 = new PersonThread(cbRef);
PersonThread p2 = new PersonThread(cbRef);
PersonThread p3 = new PersonThread(cbRef);
PersonThread p4 = new PersonThread(cbRef);
PersonThread p5 = new PersonThread(cbRef);
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
}
}
使用场景
使用场景: CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的场景。
需求:使用两个线程读取 2 个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作。

5.4 Semaphore

Semaphore (发信号)的主要作用是控制线程的并发数量。
synchronized 可以起到 " " 的作用,但某个时间段内,只能有一个线程允许执行。
Semaphore 可以设置同时允许几个线程执行。
Semaphore 字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。
Semaphore 构造方法:
public Semaphore(int permits) permits 表示许可线程的数量
public Semaphore(int permits, boolean fair) fair 表示公平性,如果这个设为 true
的话,下次执行的线程会是等待最久的线程
Semaphore 重要方法:
public void acquire() throws InterruptedException 表示获取许可
public void release() release() 表示释放许可
示例一:同时允许 1 个线程执行
1). 制作一个 Service 类:
public class Service {
private Semaphore semaphore = new Semaphore(1);//1表示许可的意思,表示最多允许1个线程
执行acquire()和release()之间的内容
public void testMethod() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()
+ " 进入 时间=" + System.currentTimeMillis());
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()
+ " 结束 时间=" + System.currentTimeMillis());
semaphore.release();
//acquire()和release()方法之间的代码为"同步代码"
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2). 制作线程类:
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
3). 测试类:
public class Demo {
public static void main(String[] args) {
Service service = new Service();
//启动5个线程
for (int i = 1; i <= 5; i++) {
ThreadA a = new ThreadA(service);
a.setName("线程 " + i);
a.start();//5个线程会同时执行Service的testMethod方法,而某个时间段只能有1个线程执
行
}
}
}
4). 结果:

示例二:同时允许 2 个线程同时执行
1). 修改 Service 类,将 new Semaphore(1) 改为 2 即可:
public class Service {
private Semaphore semaphore = new Semaphore(2);//2表示许可的意思,表示最多允许2个线程
执行acquire()和release()之间的内容
public void testMethod() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()
+ " 进入 时间=" + System.currentTimeMillis());
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()
+ " 结束 时间=" + System.currentTimeMillis());
semaphore.release();
//acquire()和release()方法之间的代码为"同步代码"
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2). 再次执行结果:

 5.5 Exchanger

Exchanger (交换者)是一个用于线程间协作的工具类。 Exchanger 用于进行线程间的数据交换。
这两个线程通过 exchange 方法交换数据,如果第一个线程先执行 exchange() 方法,它会一直等待第二个线程 也执行exchange 方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据 传递给对方。
Exchanger 构造方法:
public Exchanger()
Exchanger 重要方法:
public V exchange(V x)
示例一: exchange 方法的阻塞特性
1). 制作线程 A ,并能够接收一个 Exchanger 对象:
public class ThreadA extends Thread {
private Exchanger<String> exchanger;
public ThreadA(Exchanger<String> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
try {
System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值...");
System.out.println("在线程A中得到线程B的值=" + exchanger.exchange("礼物A"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2). 制作 main() 方法:
public class Demo {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<String>();
ThreadA a = new ThreadA(exchanger);
a.start();
}
}

3). 执行结果:

示例二: exchange 方法执行交换
1). 制作线程 A
public class ThreadA extends Thread {
private Exchanger<String> exchanger;
public ThreadA(Exchanger<String> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
try {
System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值...");
System.out.println("在线程A中得到线程B的值=" + exchanger.exchange("礼物A"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2). 制作线程 B
public class ThreadB extends Thread {
private Exchanger<String> exchanger;
public ThreadB(Exchanger<String> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
try {
System.out.println("线程B欲传递值'礼物B'给线程A,并等待线程A的值...");
System.out.println("在线程B中得到线程A的值=" + exchanger.exchange("礼物B"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3). 制作测试类:
public class Demo {
public static void main(String[] args) throws InterruptedException {
Exchanger<String> exchanger = new Exchanger<String>();
ThreadA a = new ThreadA(exchanger);
ThreadB b = new ThreadB(exchanger);
a.start();
b.start();
}
}
4). 执行结果:

示例三: exchange 方法的超时
1). 制作线程 A
public class ThreadA extends Thread {
private Exchanger<String> exchanger;
public ThreadA(Exchanger<String> exchanger) {
super();
this.exchanger = exchanger;
}
@Override
public void run() {
try {
System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值,只等5秒...");
System.out.println("在线程A中得到线程B的值 =" + exchanger.exchange("礼物
A",5, TimeUnit.SECONDS));
System.out.println("线程A结束!");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
System.out.println("5秒钟没等到线程B的值,线程A结束!");
}
}
}
2). 制作测试类:
public class Run {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<String>();
ThreadA a = new ThreadA(exchanger);
a.start();
}
}
3).测试结果:

使用场景
使用场景:可以做数据校对工作
需求:比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水。为了避免错误,采用 AB 岗两人进
行录入,录入到两个文件中,系统需要加载这两个文件,
并对两个文件数据进行校对,看看是否录入一致

六、 线程池方式

6.1 线程池的思想

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大 大降低系统的效率,因为频繁创建线程和销毁线程需要时间,线程也属于宝贵的系统资源。

6.2 07线程池概念

线程池: 其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操 作,无需反复创建线程而消耗过多资源。 由于线程池中有很多操作都是与优化资源相关的我们在这里就不多赘述。我们通过一张图来了线程池的
工作原理:

合理利用线程池能够带来三个好处:
1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多 的内存,而把服务器累趴下( 每个线程需要大约 1MB 内存,线程开的越多,消耗的内存也越大,最后死机)

6.3 线程池的使用

Java 里面线程池的顶级接口是 java.util.concurrent.Executor ,但是严格意义上讲 Executor 并不是一 个线程池,而只是一个执行线程的工具。真正的线程池接口是
java.util.concurrent.ExecutorService
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不 是较优的,因此在 java.util.concurrent.Executors 线程工厂类里面提供了一些静态工厂,生成一些常 用的线程池。官方建议使用Executors 工程类来创建线程池对象。
Executors 类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。 ( 创 建的是有界线程池, 也就是池中的线程个数可以指定最大数量 )
获取到了一个线程池 ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
public Future<?> submit(Runnable task) : 获取线程池中的某一个线程对象,并执行
Future 接口:用来记录线程任务执行完毕后产生的结果。
使用线程池中线程对象的步骤:
1. 创建线程池对象。
2. 创建 Runnable 接口子类对象。 (task)
3. 提交 Runnable 接口子类对象。 (take task)
4. 关闭线程池 ( 一般不做 )
Runnable 实现类代码:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一个教练");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了: " + Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教练回到了游泳池");
}
}
线程池测试类:
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
// 创建Runnable实例对象
MyRunnable r = new MyRunnable();
//自己创建线程对象的方式
// Thread t = new Thread(r);
// t.start(); ---> 调用MyRunnable中的run()
// 从线程池中获取线程对象,然后调用MyRunnable中的run()
service.submit(r);
// 再获取个线程对象,调用MyRunnable中的run()
service.submit(r);
service.submit(r);
// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
// 将使用完的线程又归还到了线程池中
// 关闭线程池
//service.shutdown();
}
}
Callable 测试代码 :
<T> Future<T> submit(Callable<T> task) : 获取线程池中的某一个线程对象,并执行 .
Future : 表示计算的结果 .
V get() : 获取计算完成的结果。
public class ThreadPoolDemo2 {
public static void main(String[] args) throws Exception {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
// 创建Runnable实例对象
Callable<Double> c = new Callable<Double>() {
@Override
public Double call() throws Exception {
return Math.random();
}
};
// 从线程池中获取线程对象,然后调用Callable中的call()
Future<Double> f1 = service.submit(c);
// Futur 调用get() 获取运算结果
System.out.println(f1.get());
Future<Double> f2 = service.submit(c);
System.out.println(f2.get());
Future<Double> f3 = service.submit(c);
System.out.println(f3.get());
}
}

6.4 线程池的练习

需求 : 使用线程池方式执行任务 , 返回 1-n 的和
分析 : 因为需要返回求和结果 , 所以使用 Callable 方式的任务
代码 :
public class Demo04 {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(3);
SumCallable sc = new SumCallable(100);
Future<Integer> fu = pool.submit(sc);
Integer integer = fu.get();
System.out.println("结果: " + integer);
SumCallable sc2 = new SumCallable(200);
Future<Integer> fu2 = pool.submit(sc2);
Integer integer2 = fu2.get();
System.out.println("结果: " + integer2);
pool.shutdown();
}
}
SumCallable.java
public class SumCallable implements Callable<Integer> {
private int n;
public SumCallable(int n) {
this.n = n;
}
@Override
public Integer call() throws Exception {
// 求1-n的和?
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
}

 七 、死锁

7.1 什么是死锁

在多线程程序中 , 使用了多把锁 , 造成线程之间相互等待 . 程序不往下走了。

7.2 产生死锁的条件

1. 有多把锁
2. 有多个线程
3. 有同步代码块嵌套

7.3 死锁代码

public class Demo05 {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
new Thread(mr).start();
new Thread(mr).start();
}
}
class MyRunnable implements Runnable {
Object objA = new Object();
Object objB = new Object();
/*
嵌套1 objA
嵌套1 objB
嵌套2 objB
嵌套1 objA
*/
@Override
public void run() {
synchronized (objA) {
System.out.println("嵌套1 objA");
synchronized (objB) {// t2, objA, 拿不到B锁,等待
System.out.println("嵌套1 objB");
}
}
synchronized (objB) {
System.out.println("嵌套2 objB");
synchronized (objA) {// t1 , objB, 拿不到A锁,等待
System.out.println("嵌套2 objA");
}
}
}
}
注意 : 我们应该尽量避免死锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值