多线程2 目录
7.线程的通信
7.1 线程间通信
为什么要处理线程间通信?
当我们需要多个线程
共同完成一个任务,并且我们希望它们有规律的执行
,那么多线程之间需要通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。
比如:线程A是用来生产包子,线程B用来吃包子。那么包子就是同一资源,线程A和B处理的动作就是:A是生产者,B是消费者。此时线程B必须等到线程A完成后才能执行,那么线程A和B就需要线程通信,即 等待唤醒机制
。
7.2 等待唤醒机制
1)它是多个线程间的一种协作机制。线程间除了有竞争
(race,比如:争夺锁),还有协作机制。
当线程满足某个条件时,就进入等待状态(wait / wait(time
)),等待其他线程执行完他们的 指定代码后再将其唤醒(notify()
);或进入指定 wait的时间,等时间到了自动唤醒;在有多个线程 进行等待时,如果需要,可以使用 notifyAll()
来唤醒所有的等待线程。wait / notify 就是线程间的一种协作机制
2)三个方法的使用:
wait()
:线程一旦执行此方法,就进入等待状态。同时,释放同步监视器的调用notify()
:线程一旦执行此方法,就会唤醒 被wait()的线程中 优先级最高的线程的那一个线程(如果优先级相同,随机选择一个)。被唤醒的线程从当初 被wait()的位置继续执行。notifyAll()
:线程一旦执行此方法,就会唤醒 被wait()的所有线程。
3)注意点:
- 这三各方法的使用,必须是在
同步代码块 或同步方法中
;(Lock需要配合Condition实现线程间的通信)- 这三个方法的调用者,必须是同步监视器。否则报illegalmonitorstateexception
synchronized (obj) { ... obj.notify(); ... obj.wait(); }
- 这三个方法声明在Object类中。
7.3 举例-调用wait和notify
使用两个线程交替打印数字:1~100
package com.mMultithreadOther;
/**
* 线程通信
* 使用两个线程交替打印数字:1~100
*/
class PrintNumber implements Runnable {
private int number = 1;
Object obj = new Object();
@Override
public void run() {
while (true) {
/*illegalmonitorstateexception 非法监视器状态异常
synchronized (obj) {//obj和`this`.notify()中的this不是同一个类*/
synchronized (this) {
this.notify();
if (number <= 100) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//线程一旦执行此方法,就进入等待状态。同时,释放同步监视器的调用
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class Test5PrintNumber {
public static void main(String[] args) {
PrintNumber p = new PrintNumber();
Thread t1 = new Thread(p, "线程1");
Thread t2 = new Thread(p, "线程2");
t1.start();
t2.start();
}
}
7.4 wait()和 sleep()的区别?
1)相同点:
- 一旦执行,当前线程都会进入阻塞;
2)不同点:
不同 | wait() | sleep() |
---|---|---|
声明不同 | 声明在Object类中 | 声明在Thread类中,静态的 |
使用场景不同 | 只能用在同步代码块 或同步方法 中 | 可以在任何需要的场景中使用 |
同步代码块 或同步方法 | 一旦执行,会释放同步监视器 | 即便执行,也不会释放同步监视器 |
结束阻塞的方式 | 到达指定时间,自动结束 或被notify()唤醒,结束阻塞 | 到达指定时间自动结束阻塞 |
7.5 生产者&消费者
1)生产者(Producer)将产品交给店员(Clerk),而消费者(Consumer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20)。如果生产者试图生产更多的产品,店员会让生产者暂停一下,等到店中有空位了再通知生产者继续生产;如果没有产品了店员会告诉消费者等一下,等到店里有产品了再通知消费者取走产品。
2)分析:
-
- 是否是多线程问题? 是 生产者、消费者
-
- 是否有共享数据? 是 共享产品
-
- 是否有线程安全问题? 是 有共享数据
-
- 是否需要处理线程安全问题? 是 如何处理? 使用同步机制
-
- 是否存在线程间的通信? 是
package com.mMultithreadOther;
/**
* 生产者(Producer)&消费者(Consumer)
*/
public class Test6ProducerConsumer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer pro1 = new Producer(clerk);
Consumer con1 = new Consumer(clerk);
Consumer con2 = new Consumer(clerk);
pro1.setName("生产者1");
con1.setName("消费者1");
con2.setName("消费者2");
pro1.start();
con1.start();
con2.start();
}
}
/**
* 店员
*/
class Clerk {
//产品数量
private int productNum = 0;
//增加产品数量
public synchronized void addProduct() {
if (productNum >= 20) {
try {
wait();//等待消费者消费产品
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
productNum++;
System.out.println(Thread.currentThread().getName() + "生产了第" + productNum + "个,产品。。。");
//唤醒消费者
notify();
}
}
//减少产品数量
public synchronized void minusProduct() {
if (productNum <= 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println(Thread.currentThread().getName() + "消费第" + productNum + "个,产品");
productNum--;
}
//唤醒消费者
notifyAll();
}
}
/**
* 生产者
*/
class Producer extends Thread {
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
System.out.println("生产者开始生产。。。");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.addProduct();
}
}
}
/**
* 消费者
*/
class Consumer extends Thread {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
System.out.println("消费者开始消费");
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.minusProduct();
}
}
}
8.JDK5 新增的两种创建线程-JUC
JUC:高并发
-
实现Callable接口
与实现Runnable接口
相比有哪些优点?- call()方法有返回值,更灵活;
- call()可以使用 throws的方式处理异常,更灵活;
- Callable使用了泛型参数,可以指明具体的 call()方法的返回值类型,更灵活。
-
实现Callable接口方式创建线程有哪些缺点?
4. 如果主线程需要获取子线程 call()方法的返回值,则此时主线程是阻塞状态。
8.1 实现Callable接口,创建线程(了解)
示例,步骤:
- 创建一个实现 Callable的实现类
- 实现 call()方法,将此线程需要执行的操作声明在 call()方法中
- 创建 Callable接口实现类对象
- 将此 Callable接口实现类的对象作为值传递到 FutureTask的构造器中,创建 FutureTask的对象;
- 将 FutureTask的对象作为参数传递到 Thread类的构造器中,创建 Thread对象,并用 Start()方法;
- 获取 Callable中 call()方法的返回值;
package com.mMultithreadOther.newThread2;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 创建多线程的方式三:实现 Callable接口(JDK5.0新特性)
*/
public class Test7Callable {
public static void main(String[] args) {
//3. 创建 Callable接口实现类对象
NumThread numThread = new NumThread();
/*4. 将此 Callable接口实现类的对象作为值传递到 FutureTask的构造器中,
创建 FutureTask的对象 */
FutureTask futureTask = new FutureTask(numThread);
/*5. 将 FutureTask的对象作为参数传递到 Thread类的构造器中,创建
Thread对象,并用 Start()方法 */
Thread t1 = new Thread(futureTask);
t1.start();
try {
/*6. 获取 Callable中 call()方法的返回值;
get()返回值即为FutureTask构造器参数Callable实现重写 call()返回值,
并且 get()方法默认有一个阻塞,等到 call()有返回值了,才会执行get()。*/
Object sum = futureTask.get();
System.out.println("1~100偶数总和为:" + sum);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 1. 创建一个实现 Callable的实现类
* 1-100的偶数和
*/
class NumThread implements Callable {
//2. 实现 call()方法,将此线程需要执行的操作声明在 call()方法中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
Thread.sleep(100);
}
return sum;
}
}
8.2 使用线程池 pool
1)现有问题:
如果并发的线程数量特别多,并且执行一个很短的时间任务就结束了,这样频繁的创建线程就会大大降低系统的效率,因为频繁的创建线程 和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,即完成一个任务后线程并不会被销毁,而是可以继续执行其他的任务。
2)思路:
提前创建好多线程,放入线程池中,使用完后放回池中。这样可以避免线程被频繁的创建销毁,实现线程的重复利用。类似生活中的公共交通工具。
3)好处:
- 提高了程序执行的效率。(线程已经提前准备好了);
- 提高了资源的复用率。(执行完的线程并未销毁,而是可以继续执行其他的任务)
- 可以设置相关的参数,对线程池中的线程的使用进行管理。
示例1:
package com.mMultithreadOther.newThread2;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 创建多线程的方式四:线程池
*/
public class Test9ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池属性:线程数上限
System.out.println(service.getClass());//ThreadPoolExecutor
service1.setMaximumPoolSize(25);
/*2. 执行指定的线程的操作。需要提供实现 Runnable接口或
Callable接口实现类的对象 */
service.execute(new NumThread1());//适用于Runnable
service.execute(new NumThread2());//适用于Runnable
//service.submit(Callable callable);适用于Callable
//3. 关闭连接池
service.shutdown();
}
}
class NumThread1 implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName()+":" + i);
}
}
}
}
class NumThread2 implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 1) {
System.out.println(Thread.currentThread().getName()+":" + i);
}
}
}
}
示例2,步骤:
- 实现Callable接口,需要返回值类型
- 重写 call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:Future< Boolean > result1 = ser.submit(t1);
- 获取结果:boolean r1 = result1.get();
- 关闭服务:ser.shutdownNow();
package com.mMultithreadOther.newThread2;
import java.io.IOException;
import java.util.concurrent.*;
public class Test8Callable implements Callable {
//网络图片地址
private String url;
//保存的文件名字
private String fileName;
public Test8Callable(String url, String fileName) {
this.url = url;
this.fileName = fileName;
}
//重写 call();方法,图片下载执行体
@Override
public Boolean call() throws Exception {
WebDownloader3 webDownloader = new WebDownloader3();
webDownloader.downloader(url, fileName);
System.out.println("下载的文件名:" + fileName);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Test8Callable t1 = new Test8Callable("https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF", "7.jpg");
Test8Callable t2 = new Test8Callable("https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF", "8.jpg");
Test8Callable t3 = new Test8Callable("https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF", "9.jpg");
/*
预想:先下载1.jpg; 再下载2.jpg; 最后下载3.jpg
实际:同时执行,由CPU决定先执行谁
*/
// 4.创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
// 5.提交执行
Future<Boolean> result1 = ser.submit(t1);
Future<Boolean> result2 = ser.submit(t2);
Future<Boolean> result3 = ser.submit(t3);
// 6.获取结果,(call方法返回的结果)
boolean r1 = result1.get();
boolean r2 = result2.get();
boolean r3 = result3.get();
// 7.关闭服务
ser.shutdownNow();
}
}
/**
* 下载器
*/
class WebDownloader3 {
//下载方法
public void downloader(String url, String fileName) {
try {
FileUtils.copyURLToFile(new URL(url), new File(fileName));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,download方法出现问题");
}
}
}