线程通信
三个方法:
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
- notify():一旦执行此方法,就会唤醒wait的一个线程,如果有多个线程被wait,就唤醒优先级高的
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
说明: - 1.这三个方法必须使用在同步代码块或者同步方法中
- 2.这三个方法的调用者必须是同步代码块或者同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常
- 3.这三个方法定义在java.lang.Objects类中
package com.chen;
/*
* 线程通信的例子:使用两个线程打印1-100.线程一和线程二交替打印
*
* 三个方法:
* wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
* notify():一旦执行此方法,就会唤醒wait的一个线程,如果有多个线程被wait,就唤醒优先级高的
* notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
*
* 说明:
* 1.这三个方法必须使用在同步代码块或者同步方法中
* 2.这三个方法的调用者必须是同步代码块或者同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常
* 3.这三个方法定义在java.lang.Objects类中
* */
class Number implements Runnable {
private int number = 1;
@Override
public void run() {
while(true){
synchronized (this) {
notify();
if(number <= 100){
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
//wait():使用调用如下wait()方法的线程进入阻塞状态
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else{
break;
}
}
}
}
}
public class CommunicationTest{
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.start();
t2.start();
}
}
输出:
Thread-0:1
Thread-1:2
Thread-0:3
Thread-1:4
Thread-0:5
Thread-1:6
Thread-0:7
Thread-1:8
Thread-0:9
Thread-1:10
Thread-0:11
Thread-1:12
Thread-0:13
Thread-1:14
Thread-0:15
Thread-1:16
Thread-0:17
Thread-1:18
Thread-0:19
Thread-1:20
Thread-0:21
Thread-1:22
Thread-0:23
Thread-1:24
Thread-0:25
Thread-1:26
Thread-0:27
Thread-1:28
Thread-0:29
Thread-1:30
Thread-0:31
Thread-1:32
Thread-0:33
Thread-1:34
Thread-0:35
Thread-1:36
Thread-0:37
Thread-1:38
Thread-0:39
Thread-1:40
Thread-0:41
Thread-1:42
Thread-0:43
Thread-1:44
Thread-0:45
Thread-1:46
Thread-0:47
Thread-1:48
Thread-0:49
Thread-1:50
Thread-0:51
Thread-1:52
Thread-0:53
Thread-1:54
Thread-0:55
Thread-1:56
Thread-0:57
Thread-1:58
Thread-0:59
Thread-1:60
Thread-0:61
Thread-1:62
Thread-0:63
Thread-1:64
Thread-0:65
Thread-1:66
Thread-0:67
Thread-1:68
Thread-0:69
Thread-1:70
Thread-0:71
Thread-1:72
Thread-0:73
Thread-1:74
Thread-0:75
Thread-1:76
Thread-0:77
Thread-1:78
Thread-0:79
Thread-1:80
Thread-0:81
Thread-1:82
Thread-0:83
Thread-1:84
Thread-0:85
Thread-1:86
Thread-0:87
Thread-1:88
Thread-0:89
Thread-1:90
Thread-0:91
Thread-1:92
Thread-0:93
Thread-1:94
Thread-0:95
Thread-1:96
Thread-0:97
Thread-1:98
Thread-0:99
Thread-1:100
对比:sleep()和wait()异同
相同点:一旦执行方法,都可以使当前线程进入阻塞状态
不同点:1.两个方法声明的位置不同,Thread类中声明sleep(),Object类中声明wait().
2.调用要求不同,sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块中。
3.关于是否释放同步监视器:如果两个方法都使用在同步代码块或者同步方法中,sleep()不释放同步监视器,wait()释放同步监视器。
例子:生产者/消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
分析:
- 是否是多线程问题?是,生产者线程,消费者线程
- 是否有共享数据?是,店员(或者产品)
- 如何解决线程安全问题?同步机制
- 是否涉及线程的通信?是
package com.chen;
class Clerk{
private int productCoiunt = 0;
//生产产品
public synchronized void produceProduct() {
if(productCoiunt < 20){
productCoiunt++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCoiunt + "个产品");
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if(productCoiunt > 0){
System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCoiunt + "个产品");
productCoiunt--;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{//生产者
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始生产产品......");
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread{//消费者
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始消费产品......");
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者");
Consumer c1 = new Consumer(clerk);
c1.setName("消费者");
p1.start();
c1.start();
}
}
创建线程的方式三:实现Callable接口
视频地址
实现Callable接口的方式创建多线程比实现Runnable接口创建多线程的方式强大,原因如下:
- call()可以有返回值
- call()可以抛出异常,被外界的操作捕获,获取异常信息
- Callable是支持泛型的
package com.chen;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//遍历0-100的偶数,并返回和
//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;
}
}
return sum;
}
}
public class ThreadNew {
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()
new Thread(futureTask).start();
try{
//6.获取Callable中的call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写call()的返回值
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
}catch (Exception e){
e.printStackTrace();
}
}
输出:
2
4
6
...(此处省略)
98
100
总和为:2550
Process finished with exit code 0
使用线程池的好处
**背景:**经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响大。
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
- 提高相应速度(减少创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
package com.chen;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0; i <=100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0; i <=100; i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//2.执行指定线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());
service.execute(new NumberThread1());//适用于Runnable
// service.submit(Callable callable);//适用于Callable
//关闭连接池
service.shutdown();
}
}