·一、线程的通信
1、例子:用两个线程,交替打印1-100的数字
public class Main {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
class Number implements Runnable{
private int number = 1;
private Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){
obj.notify();//唤醒
if(number <= 100){
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
}else{
break;
}
try {
obj.wait();//阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
涉及到的三个方法:
wait():一旦执行到此处,当前线程就进入阻塞状态,并且释放同步监视器。
notify():一旦执行到此处,就会唤醒被wait的一个线程,如果有多个,释放优先级高的。
notifyAll():一旦执行到此处,就会唤醒被wait的所有线程
注意:
这个三个方法必须要使用在同步方法或者同步代码块中。
这个三个方法的调用者,是同步监视器。
这个三个方法是定义在Object中的,因为同步监视器可以是任何一个类的对象。
2、sleep()和wait()的异同
相同:
一旦执行方法当前线程就会被阻塞
不同:
两个方法声明的位置不同:Thread类中声明sleep,Object类中声明wait
调用的要求不同:sleep可以在任何需要的条件下调用,wait必须要使用在同步方法或者同步代码块中
是否释放同步监视器:如果都在同步方法或者同步代码块中,wait会释放锁,sleep不会
3、线程通信的应用:生产者/消费者问题
生产者(productor)将产品交给店员(clerk),而消费者(customer)从店员处,取走产品,店员一次只能持有固定数量的产品,如果生产者试图生产更多的产品,店员会叫停。如果店员没用足够的产品,会让消费者等一下。
class Clerk{
private int produceCount = 0;
//生产产品
public synchronized void produceProduct() {
if(produceCount < 20){
produceCount++;
notify();//只要生产了一个产品就可以唤醒消费者
System.out.println(Thread.currentThread().getName()+":开始生产第"+produceCount+"个产品");
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if(produceCount > 0){
System.out.println(Thread.currentThread().getName()+":开始消费第"+produceCount+"个产品");
produceCount--;
notify();//只要消费了一个产品,就可以把生产者唤醒
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Productor extends Thread{
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 开始生产...");
while (true){
clerk.produceProduct();
}
}
}
class Customer extends Thread{
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 开始消费...");
while (true){
clerk.consumeProduct();
}
}
}
public class Main {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor p1 = new Productor(clerk);
p1.setName("生产者");
Customer c1 = new Customer(clerk);
c1.setName("消费者");
p1.start();
c1.start();
}
}
二、JDK5.0新增的两种线程创建方式:(了解)
1、实现Callable接口
与Runnable相比,更强大。
相比run()方法有可以有返回值。
可以抛出异常,
支持泛型的返回值
需要借助FutureTask类(Future接口可以对Runnable、Callable任务执行的结果进行取消、查询是否完成、获取结果等。FutureTask是Future接口的唯一实现类,FutureTask同时实现了Runnable、Future接口。)
//创建一个Callable接口的实现类
class NumThread implements Callable {
//将此线程需要的操作写在call方法中
@Override
public Object call() throws Exception {
//遍历100内的整数,并且返回总和
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i%2 == 0) {
System.out.println(i);
sum+=i;
}
}
return sum;
}
}
public class Main {
public static void main(String[] args) {
//创建Callable接口的实现类的对象
NumThread numThread = new NumThread();
//将此对象作为参数传递到FutureTask的构造器中,创建FutureTask对象
FutureTask futureTask = new FutureTask(numThread);
//将FutureTask对象传递到Thread类的构造器中,创建Thread对象,并调用start
new Thread(futureTask).start();
try {
Object sum = futureTask.get();//返回值即为Callable实现类重写的Call的返回值
System.out.println("总和为: "+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
2、使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前建立好多个线程,放入线程池中,使用直接获取,使用完放回池中。类似生活中的交通工具
public class Main {
public static void main(String[] args) {
//提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//执行指定的线程操作需要提供实现Runnable接口或者Callable接口的实现类的对象
service.execute(对象);//适合于Runnable
//service.submit(对象); 适合于Callable
//关闭连接池
service.shutdown();
}
}
缓存线程池:(长度无限制)
1、判断线程池是否存在空闲的线程
2、存在则使用
4、不存在,则创建线程,放入线程池中,然后使用
public class Main {
public static void main(String[] args) throws InterruptedException {
//提供线程池
ExecutorService service = Executors.newCachedThreadPool();
//指挥线程池执行新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"呜呼");//pool-1-thread-1呜呼
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"呜呼");//pool-1-thread-2呜呼
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"呜呼");//pool-1-thread-3呜呼
}
});
//这时缓冲池有是三个线程
Thread.sleep(1000);
//会使用上面三个线程池中的一个来执行任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"呜呼");//pool-1-thread-1呜呼
}
});
}
}
定长线程池:(长度是指定的)
1、判断线程池是否存在空闲的线程
2、存在则使用
4、不存在,且线程池未满的情况下,创建,否则就会等待。
public class Main {
public static void main(String[] args) throws InterruptedException {
//提供线程池
ExecutorService service = Executors.newFixedThreadPool(2);
//指挥线程池执行新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"呜呼");//pool-1-thread-1呜呼
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"呜呼");//pool-1-thread-2呜呼
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//过了三秒才会执行
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"呜呼");//pool-1-thread-1呜呼
}
});
}
}
单线程线程池:
1、判断线程池是否存在空闲的线程
2、存在则使用
4、不存在则等待,等那个单线程空闲后使用
public class Main {
public static void main(String[] args) throws InterruptedException {
//提供线程池
ExecutorService service = Executors.newSingleThreadExecutor();
//指挥线程池执行新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"呜呼");//pool-1-thread-1呜呼
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"呜呼");//pool-1-thread-1呜呼
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"呜呼");//pool-1-thread-13呜呼
}
});
}
}
周期任务 定长线程池:
在定长线程池的基础上,定时执行,当某个时机触发时,自动执行某任务
public class Main {
public static void main(String[] args) throws InterruptedException {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 定时执行依次
* 参数1:定时执行的任务
* 参数2:时长数字
* 参数3:时长数字的时间单位(TimeUnit的常量指定)
*/
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}, 3, TimeUnit.SECONDS);
/**
* 周期执行任务
* 参数1:任务
* 参数2:延迟时长数字(第一次执行在什么时间以后)
* 参数2:周期时长数字(每隔多久执行)
* 参数3:时长数字的时间单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("乌拉");
}
}, 3, 1, TimeUnit.SECONDS);
}
}