多线程
多线程
一、线程的定义和创建
1.进程和线程
-
程序(Program)
程序是一段静态的代码,每个.java文件就是java中的一个程序,是应用程序执行的蓝本
-
进程(Process)
1.进程是指正在运行的程序,有自己的地址空间
2.进程的特点:
-
动态性:正在运行的程序
-
并发性:可同时打开world,idea等软件
-
独立性:world,idea等软件之间是没有关系的
-
并发和并行的区别:
并发(concurrency):(一个CPU(采用时间片)同时执行多个任务。)在同一时刻只能有一条指令执行,多个进程被快速的轮换交替执行。从宏观来看是多个进程同时执行。但从微观上来看,是把时间分成了若干片段,使多个进程快速交替执行。
并行(parallel):(多个CPU同时执行多个任务)在同一时刻,有多条指令在多个处理器上同时执行,从宏观和微观上来看,都是同时执行的。
-
线程(Thread)
-
进程内部的一个执行单元
-
被称为轻量级进程
-
如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称为多线程
-
线程的特点:
- 轻量级进程
- 独立调度的基本单位(一个cpu上同时运行了两个进程时,是进程里的线程在排队)
- 共享进程资源(进程申请了资源后,线程可以直接用)
- 可并发执行
如下图:
(1)公路中的隔离带将公路隔离成了两条马路(即两条进程)。每条马路上有多个车道(即进程中的多个线程),每个车道是可以同时走车的(即可并发执行)
(2)迅雷相当于一个进程,里面的两个下载任务相当于线程。
-
-
线程和进程的区别
- 根本区别:进程是资源分配的单位,线程是调度和执行的单位(找CPU排队)
- 开销:进程间的切换开销大,线程间的切换开销小
- 分配内存:进程可以分配到资源,线程直接使用进程的资源
- 包含关系:线程是进程的一部分
2.线程的定义和创建
2.1方法一:继承Thread类
- Thread类是线程顶级类。继承Thread类可以快速定义线程
- run() 是线程体,线程要完成的任务
- start() 线程启动,线程进入就绪队列,等待获取CPU并执行
package com.zhang.thread.thread1;
/**
* 自定义一个线程类,继承Thread类
*
* run() 是线程体
* start() 线程启动
* setName() 设置线程名
* Thread.currentThread().getName() 线程名
* setPriority() 设置线程优先级
* Thread.currentThread().getPriority() 线程优先级
*/
public class MyThread extends Thread {
public void run() {
// this.setName("乌龟线程");//设置线程名
// this.setPriority(3);//设置线程的优先级
while (true) {
/* System.out.println("乌龟领先了。。。。 线程名:" + Thread.currentThread().getName()
+ " 线程优先级:" + Thread.currentThread().getPriority());*/
System.out.println("乌龟领先了。。。。 线程名:" + this.getName()
+ " 线程优先级:" + this.getPriority());
}
}
}
package com.zhang.thread.thread1;
public class Test {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.setName("乌龟线程1");
thread.start();//启动线程
Thread thread2 = new MyThread();
thread2.setName("乌龟线程2");
thread2.start();//启动线程
Thread.currentThread().setName("兔子线程");
Thread.currentThread().setPriority(6);//最大优先级是10,优先级越高,执行的越多
while (true) {
System.out.println("兔子领先了。。。。 线程名:" + Thread.currentThread().getName() + " 线程优先级:" + Thread.currentThread().getPriority());
}
}
}
运行结果:
乌龟领先了。。。。 线程名:乌龟线程1 线程优先级:5
乌龟领先了。。。。 线程名:乌龟线程1 线程优先级:5
乌龟领先了。。。。 线程名:乌龟线程1 线程优先级:5
兔子领先了。。。。 线程名:兔子线程 线程优先级:6
兔子领先了。。。。 线程名:兔子线程 线程优先级:6
兔子领先了。。。。 线程名:兔子线程 线程优先级:6
兔子领先了。。。。 线程名:兔子线程 线程优先级:6
兔子领先了。。。。 线程名:兔子线程 线程优先级:6
兔子领先了。。。。 线程名:兔子线程 线程优先级:6
乌龟领先了。。。。 线程名:乌龟线程2 线程优先级:5
乌龟领先了。。。。 线程名:乌龟线程2 线程优先级:5
乌龟领先了。。。。 线程名:乌龟线程2 线程优先级:5
乌龟领先了。。。。 线程名:乌龟线程2 线程优先级:5
兔子领先了。。。。 线程名:兔子线程 线程优先级:6
兔子领先了。。。。 线程名:兔子线程 线程优先级:6
总结:
- 优点:编码简单
- 缺点:单继承,继承了Thread类无法继承其他类
2.2方法二:实现了Runnable接口
package com.zhang.thread.runnable;
public class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("乌龟领先了。。。。 线程名:" + Thread.currentThread().getName()
+ " 线程优先级:" + Thread.currentThread().getPriority());
}
}
}
package com.zhang.thread.runnable;
public class Test {
public static void main(String[] args) {
//方法一:自定义MyRunnable类
//Runnable runnable = new MyRunnable();
//方法二:匿名内部类
/*Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("乌龟领先了。。。。 线程名:" + Thread.currentThread().getName()
+ " 线程优先级:" + Thread.currentThread().getPriority());
}
}
};*/
//方法三:lambda表达式
Runnable runnable = () -> {
while (true) {
System.out.println("乌龟领先了。。。。 线程名:" + Thread.currentThread().getName()
+ " 线程优先级:" + Thread.currentThread().getPriority());
}
};
Thread thread = new Thread(runnable);
thread.start();//启动乌龟线程
Thread thread2 = new Thread(runnable);
thread2.start();//启动乌龟线程
while (true) {
System.out.println("兔子领先了。。。。 线程名:" + Thread.currentThread().getName() + " 线程优先级:" + Thread.currentThread().getPriority());
}
}
}
运行结果:
兔子领先了。。。。 线程名:main 线程优先级:5
兔子领先了。。。。 线程名:main 线程优先级:5
兔子领先了。。。。 线程名:main 线程优先级:5
兔子领先了。。。。 线程名:main 线程优先级:5
兔子领先了。。。。 线程名:main 线程优先级:5
乌龟领先了。。。。 线程名:Thread-1 线程优先级:5
乌龟领先了。。。。 线程名:Thread-1 线程优先级:5
乌龟领先了。。。。 线程名:Thread-1 线程优先级:5
乌龟领先了。。。。 线程名:Thread-1 线程优先级:5
乌龟领先了。。。。 线程名:Thread-1 线程优先级:5
乌龟领先了。。。。 线程名:Thread-0 线程优先级:5
乌龟领先了。。。。 线程名:Thread-0 线程优先级:5
乌龟领先了。。。。 线程名:Thread-0 线程优先级:5
乌龟领先了。。。。 线程名:Thread-0 线程优先级:5
乌龟领先了。。。。 线程名:Thread-0 线程优先级:5
总结:
- 优点:可以继承其他类 ;便于多个线程共享同一个任务(只创建了一个Runnable对象,多个线程共用一个资源;而继承Thread会创建多个对象,资源重复)
- 缺点:编码复杂
- 使用这种方式更多一点
2.3实现了Callable接口
-
与Runnable相比,Callable的功能更强大,可以有返回值,可以抛异常
@FunctionalInterface public interface Callable<V> { V call() throws Exception; }
-
需要借助FutureTask获取返回结果
public class FutureTask<V> implements RunnableFuture<V> { } public interface RunnableFuture<V> extends Runnable, Future<V> { }
-
Future接口
- FutureTask是Future接口接口的唯一实现类
- FutureTask同时实现了 Runnable, Future两个接口。既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回结果
- 可以对具体Runnable,Callable任务的执行结果进行取消,查询是否完成,获取结果
package com.zhang.thread.callable;
import java.util.Random;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("==================1===========");
Thread.sleep(5000);//休息5秒
System.out.println("==================2===========");
return new Random().nextInt(10);
}
}
package com.zhang.thread.callable;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* futureTask.isDone();是否执行完成
* futureTask.get();//获取结果
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//方法一:自定义MyCallable类
// Callable callable = new MyCallable();
//方法二:匿名内部类
/*Callable callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return new Random().nextInt(10);
}
};*/
//方法三:lambda
Callable callable = () -> new Random().nextInt(10);
FutureTask<Integer> futureTask = new FutureTask(callable);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.isDone());//是否执行完成
Integer integer = futureTask.get();//获取结果,得不到返回值就会在此阻塞,后面代码不会执行
System.out.println("获取结果:::" + integer);
System.out.println(futureTask.isDone());
}
}
运行结果:
false
获取结果:::4
true
Process finished with exit code 0
二、线程控制
1.线程的生命周期
-
新建:new()了线程对象后
-
就绪:调用start()后处于就绪状态,等待系统分配CPU
当系统选定等待的线程开始执行时,(就绪状态->执行状态,称为CPU调度)
-
运行:执行run()中的代码
-
阻塞:sleep()
-
死亡:(1)线程完成了全部工作(2)抛出异常(3)强制终止(stop(),不推荐)
-
运行状态的出口:死亡,就绪,阻塞
-
就绪的入口:新建,运行,阻塞
-
了解线程同步,线程通信后,会知道更多的线程状态
2.线程控制
用来对线程的生命周期进行干预
(1)join():阻塞其他线程,当自己的线程执行完后才能继续执行
package com.zhang.thread.ctrl1;
public class MyThread extends Thread {
public void run() {
int i = 1;
while (i <= 10) {
System.out.println("乌龟领先了。。。。 "+i);
i++;
}
}
}
package com.zhang.thread.ctrl1;
public class Test {
public static void main(String[] args) throws InterruptedException {
int i = 1;
while (i <= 10) {
System.out.println("兔子领先了。。。。 " + i);
//实现,兔子先跑,跑到5后,乌龟再跑,等乌龟跑完兔子才能接着跑
if (i == 5) {
Thread thread = new MyThread();
thread.start();//启动线程
thread.join();//让别人的线程阻塞,当自己的线程跑完后,别的线程才能开始跑
}
i++;
}
}
}
执行结果:
兔子领先了。。。。 1
兔子领先了。。。。 2
兔子领先了。。。。 3
兔子领先了。。。。 4
兔子领先了。。。。 5
乌龟领先了。。。。 1
乌龟领先了。。。。 2
乌龟领先了。。。。 3
乌龟领先了。。。。 4
乌龟领先了。。。。 5
乌龟领先了。。。。 6
乌龟领先了。。。。 7
乌龟领先了。。。。 8
乌龟领先了。。。。 9
乌龟领先了。。。。 10
兔子领先了。。。。 6
兔子领先了。。。。 7
兔子领先了。。。。 8
兔子领先了。。。。 9
兔子领先了。。。。 10
Process finished with exit code 0
(2)sleep():让自己的线程停止运行一段时间,让出CPU
- 实际中用来模拟线程切换,暴露线程安全问题
package com.zhang.thread.ctrl2;
public class MyThread extends Thread {
public void run() {
int i = 1;
while (i <= 10) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("乌龟领先了。。。。 ");
i++;
}
}
}
package com.zhang.thread.ctrl2;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread = new MyThread();
thread.start();//启动线程
int i = 1;
while (i <= 10) {
//Thread.sleep(100);
TimeUnit.MILLISECONDS.sleep(100);
System.out.println("兔子领先了。。。。 ");
i++;
}
}
}
运行结果:
乌龟领先了。。。。
兔子领先了。。。。
兔子领先了。。。。
乌龟领先了。。。。
乌龟领先了。。。。
兔子领先了。。。。
乌龟领先了。。。。
兔子领先了。。。。
乌龟领先了。。。。
兔子领先了。。。。
乌龟领先了。。。。
兔子领先了。。。。
兔子领先了。。。。
乌龟领先了。。。。
兔子领先了。。。。
乌龟领先了。。。。
兔子领先了。。。。
乌龟领先了。。。。
乌龟领先了。。。。
兔子领先了。。。。
Process finished with exit code 0
(3)yield():让正在执行的线程暂停,进入就绪状态,如果没有其他等待就绪的线程,就会马上恢复执行
package com.zhang.thread.ctrl3;
public class MyThread extends Thread {
public void run() {
int i = 1;
while (i <= 10) {
Thread.yield();
System.out.println("乌龟领先了。。。。 ");
i++;
}
}
}
package com.zhang.thread.ctrl3;
public class Test {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();//启动线程
int i = 1;
while (i <= 10) {
Thread.yield();
System.out.println("兔子领先了。。。。 ");
i++;
}
}
}
sleep()和yield()的区别
- 执行了sleep会进入阻塞状态,而执行了yield会进入就绪状态
- sleep需要指定休息时间,yield无需指定
- sleep有异常,可以中断休息,而yield不涉及这些
(4)setDaemon():将线程设置为寄生线程(后台线程)
- 只能在线程启动前设置为后台线程
- 前台线程结束时,后台线程要跟着结束
中断后台线程方法一:
thread.setDaemon(true);//设为寄生线程(后台线程),主线程跑完后这个线程也停止
package com.zhang.thread.ctrl4;
public class Test {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.setDaemon(true);//设为寄生线程(后台线程),主线程跑完后这个线程也停止
thread.start();//启动线程
int i = 1;
while (i <= 100) {
System.out.println("兔子领先了。。。。 " + i);
i++;
}
}
}
package com.zhang.thread.ctrl4;
public class MyThread extends Thread {
public void run() {
while (true) {
System.out.println("乌龟领先了。。。。 ");
}
}
}
运行结果:
乌龟领先了。。。。
兔子领先了。。。。 92
兔子领先了。。。。 93
兔子领先了。。。。 94
兔子领先了。。。。 95
兔子领先了。。。。 96
兔子领先了。。。。 97
兔子领先了。。。。 98
兔子领先了。。。。 99
兔子领先了。。。。 100
乌龟领先了。。。。
乌龟领先了。。。。
乌龟领先了。。。。
Process finished with exit code 0
(5)interrupt():并没有直接中断线程,而是需要被中断线程自己处理
中断后台线程方法二:
package com.zhang.thread.ctrl5;
public class MyThread extends Thread {
public void run() {
while (!this.isInterrupted()) {//isInterrupted是true,说明被打断了
System.out.println("乌龟领先了。。。。 ");
}
}
}
package com.zhang.thread.ctrl5;
public class Test {
public static void main(String[] args) {
Thread thread = new MyThread();
//thread.setDaemon(true);//设为寄生线程,后台线程,守护线程
thread.start();//启动线程
int i = 1;
while (i <= 100) {
System.out.println("兔子领先了。。。。 " + i);
i++;
}
thread.interrupt();//中断乌龟线程,并没有真正的中断乌龟线程,只是修改了乌龟线程的一个相关符号位
}
}
运行结果:
兔子领先了。。。。 99
兔子领先了。。。。 100
乌龟领先了。。。。
乌龟领先了。。。。
Process finished with exit code 0
总结:
- thread.interrupt();//中断乌龟线程,并没有真正的中断乌龟线程,只是修改了乌龟线程的一个相关符号位
- this.isInterrupted()判断 和 thread.interrupt()结合实现
- 补充:this.isInterrupted()被打断后,底层会自动还原为未打断状态,等待下一次被打断
(6) stop():结束线程,不推荐使用
三、线程的同步(synchronized)
3.1问题的提出
场景:多个用户同时操作一个银行账户,每次取400,取款前检查余额是否足够,不够则取款失败
分析:使用多线程解决;开发一个取款线程类,每个用户对应一个线程对象;多个线程共享一个银行账户,使用Runnable解决
思路:创建一个银行账户Account;创建取款线程AccountRunnable;创建测试类Test,让两个人同时取款
package com.zhang.thread.syn0;
/**
* 银行账户
*/
public class Account {
private double balance = 600.0;//余额
public double getBalance() {
return this.balance;
}
public void withDraw(double money) {//取款
this.balance -= money;
}
}
package com.zhang.thread.syn0;
/**
* 取款
*/
public class AccountRunnable implements Runnable {
Account account = new Account();
@Override
public void run() {
if (account.getBalance() >= 400) {
try {
Thread.sleep(1);//模拟线程中的问题
} catch (InterruptedException e) {
e.printStackTrace();
}
account.withDraw(400.0);
System.out.println("取款成功,现在的余额是" + account.getBalance());
} else {
System.out.println("余额不足,取款失败,现在的余额是" + account.getBalance());
}
}
}
package com.zhang.thread.syn0;
public class Test {
public static void main(String[] args) {
Runnable runnable = new AccountRunnable();
//创建两个线程
Thread thread1 = new Thread(runnable, "小明");
Thread thread2 = new Thread(runnable, "小明妻子");
//启动线程(开始取款)
thread1.start();
thread2.start();
}
}
运行结果:(从运行结果可以看出,两次都取款成功,是错误的)
取款成功,现在的余额是-200.0
取款成功,现在的余额是-200.0
Process finished with exit code 0
总结:
- 当多个线程访问同一个数据时,容易出现线程安全问题,需要让线程同步,保证线程安全
- 线程同步:当多个线程访问同一个资源时,需要确保资源在某一时刻只被一个线程访问
- 线程同步的实现方案:
- (1)同步代码块锁:synchronized (obj) {}
- (2)同步方法锁: public synchronized void withDraw() {}
- (3)Lock锁:ReentrantLock ,ReentrantReadWriteLock
- (4)volatile+CAS无锁化方案
解决方法:对取款代码加锁synchronized(){}
package com.zhang.thread.syn0;
/**
* 取款
*/
public class AccountRunnable implements Runnable {
Account account = new Account();
@Override
public void run() {
synchronized (account) {
if (account.getBalance() >= 400) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.withDraw(400.0);
System.out.println("取款成功,现在的余额是" + account.getBalance());
} else {
System.out.println("余额不足,取款失败,现在的余额是" + account.getBalance());
}
}
}
}
重新运行:
取款成功,现在的余额是200.0
余额不足,取款失败,现在的余额是200.0
Process finished with exit code 0
3.2同步代码块:synchronized (锁对象) {}
package com.zhang.thread.syn0;
/**
* 取款
*/
/**
* 总结1(认识锁对象):synchronized (锁对象) {}
* (1)必须是引用数据类型,不能是基本数据类型
* (2)在同步代码块中可以改变锁对象的值,不能改变其引用
* (3)尽量不用String,和包装类Integer做锁对象,如果使用了,只要保证在代码块中没有任何操作也可以
* (4)一般使用共享资源做锁对象即可
* (5)也可以创建一个专门的锁对象,没有任何业务意义
* (6)建议使用final修饰锁对象
*
* 总结2:同步代码块的执行过程:
* (1)第一个线程来到同步代码块,发现锁对象(其实是:和锁对象关联的监视器对象中)的锁是open状态,将锁close,然后执行同步代码块中的代码
* (2)第一个线程执行过程中,发生了线程切换(阻塞,就绪),让出CPU,并未开锁
* (3)第二个线程获取CPU,来到同步代码块,发现锁是close状态,无法执行其中的代码,也进入阻塞状态
* (4)第一个线程再次获取CPU,执行完代码,释放锁open
* (5)第二个线程再次获取CPU,发现锁对象(其实是:和锁对象关联的监视器对象中)的锁是open状态,重复第一线程的处理过程
*
* 注意:同步代码块中能发生CPU切换吗? 可以,但后续执行的线程无法执行线程同步(锁还是close)
*
* 总结3:线程同步的优缺点:
* 优点:线程安全
* 缺点:效率低,可能出现死锁(A被锁,锁中调了B;B被锁,锁中调了A)
*
* 总结4:
* 多个代码块使用了同一个锁对象时,锁住了一个代码块,所有使用该锁的代码块都被锁住,其他线程无法访问其中的任何一个
*
*/
public class AccountRunnable implements Runnable {
Account account = new Account();
@Override
public void run() {
synchronized (account) {
if (account.getBalance() >= 400) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.withDraw(400.0);
System.out.println("取款成功,现在的余额是" + account.getBalance());
} else {
System.out.println("余额不足,取款失败,现在的余额是" + account.getBalance());
}
}
}
}
3.3同步方法:public synchronized void 方法名()
package com.zhang.thread.syn1;
/**
* 取款
*/
/**
* 总结:
* 1.不要将run()定义为同步方法
* 2.同步方法锁的对象是this,静态同步方法锁的监视器是类名.class
* 3.同步代码块的效率高于同步方法锁
* 同步方法锁住了整个方法;同步代码块只锁住了方法中的一部分代码
* 同步方法的锁对象是this,一旦锁住一个方法,会锁住所有的同步方法;同步代码块只会锁住同一个锁对象的代码块
*/
public class AccountRunnable implements Runnable {
Account account = new Account();
@Override
public void run() {
this.withDraw();
}
public synchronized void withDraw() {
if (account.getBalance() >= 400) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.withDraw(400.0);
System.out.println("取款成功,现在的余额是" + account.getBalance());
} else {
System.out.println("余额不足,取款失败,现在的余额是" + account.getBalance());
}
}
}
synchronized总结:
- 对于synchronized锁(同步方法,同步代码块),如果正常执行完毕,会释放锁。如果线程执行异常,JVM也会让线程自动释放锁
- synchronized的缺点:
- 如果获取锁的线程由于IO等原因让线程阻塞了,此时一直没有释放锁,其他线程只能一直等着,影响效率
- synchronized会将读写操作都上锁。无法实现多个读操作同时运行,一个写操作运行
3.4Lock锁
public interface Lock {
/**
*lock();最常用的锁,如果锁被其他线程获取,则等待;
*要主动通过 unlock()释放锁,且要放到finally里
*/
void lock();
/**
*tryLock();尝试获取锁,成功则返回true;失败(被其他线程获取),返回false;不会一直等待
*/
boolean tryLock();
/**
*tryLock(long time, TimeUnit unit);尝试在某段时间获取锁,成功则返回true;失败,返回false; *不再等待
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
*lockInterruptibly();通过这个方法获取锁时,如果线程正在等待获取锁,可以通过 *Thread.interrupt()中断等待过程
*/
void lockInterruptibly() throws InterruptedException;
/**
* unlock()释放锁
*/
void unlock();
}
代码示例:
package com.zhang.thread.syn2;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 取款
*/
public class AccountRunnable implements Runnable {
Account account = new Account();
Lock lock = new ReentrantLock();//定义锁
@Override
public void run() {
lock.lock();//关锁
try {
if (account.getBalance() >= 400) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.withDraw(400.0);
System.out.println("取款成功,现在的余额是" + account.getBalance());
} else {
System.out.println("余额不足,取款失败,现在的余额是" + account.getBalance());
}
} finally {
lock.unlock();//解锁,必须放到finally里
}
}
}
ReentrantLock:可重入锁
- ReentrantLock是唯一实现了Lock接口的非内部类
- ReentrantLock可重入的意思是可被单个线程多次获取(发现被线程自己锁了后,还可继续获取)
- 分为公平锁(需排队)和非公平锁,默认非公平锁,非公平锁效率高
- ReentrantLock是通过队列来管理线程的
总结:
Lock锁的底层核心是AbstractQueuedSynchronizer(AQS抽象的队列式同步器)
3.5ReadWriteLock锁
- ReadWriteLock接口有两个方法(readLock()读锁,writeLock()写锁),将读写分成两个锁,让多个线程可以进行读操作
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
- ReentrantReadWriteLock是接口ReadWriteLock的实现类,该类中包括两个内部类ReadLock,WriteLock,这两个内部类实现了Lock接口
代码示例:
package com.zhang.thread.syn3;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class MyReadWriteLock {
public static void main(String[] args) {
ReadWriteLock rwl = new ReentrantReadWriteLock();//非公平,可重入,分为读锁和写锁
Lock readLock = rwl.readLock();
Lock readLock1 = rwl.readLock();
Lock writeLock = rwl.writeLock();
Lock writeLock1 = rwl.writeLock();
System.out.println(readLock == readLock1);//true 同一把锁
System.out.println(writeLock == writeLock1);//true 同一把锁
readLock.lock();//对读锁上锁
readLock.unlock();//对读锁解锁
writeLock.lock();//对写锁上锁
writeLock.unlock();//对写锁解锁
//结论:读读共享,读写互斥,写写互斥
}
}
结论:从ReadWriteLock中多次获取的readLock和writeLock是同一把读锁,同一把写锁
代码示例2:
package com.zhang.thread.syn3;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestReadWriteLock {
ReadWriteLock lock = new ReentrantReadWriteLock();
public void read() {
lock.readLock().lock();
System.out.println("==========读锁开始=========" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("==========读锁结束=========" + Thread.currentThread().getName());
lock.readLock().unlock();
}
public void write() {
lock.writeLock().lock();
System.out.println("==========写锁开始=========" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("==========写锁结束=========" + Thread.currentThread().getName());
lock.writeLock().unlock();
}
public static void main(String[] args) {
TestReadWriteLock rw = new TestReadWriteLock();
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
rw.read();
}
}).start();
}
for (int i = 0; i < 5; i++) {
new Thread(() -> rw.write()).start();
}
}
}
运行结果:
==========读锁开始=========Thread-2
==========读锁开始=========Thread-4
==========读锁开始=========Thread-3
==========读锁开始=========Thread-1
==========读锁开始=========Thread-0
==========读锁结束=========Thread-0
==========读锁结束=========Thread-2
==========读锁结束=========Thread-3
==========读锁结束=========Thread-1
==========读锁结束=========Thread-4
==========写锁开始=========Thread-5
==========写锁结束=========Thread-5
==========写锁开始=========Thread-6
==========写锁结束=========Thread-6
==========写锁开始=========Thread-7
==========写锁结束=========Thread-7
==========写锁开始=========Thread-8
==========写锁结束=========Thread-8
==========写锁开始=========Thread-9
==========写锁结束=========Thread-9
Process finished with exit code 0
3.6Lock锁和同步锁的区别
- synchronized 在jdk1.6之前是重量级锁,1.6的时候引入了:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。状态会随竞争情况逐渐升级
3.7volatile:保证多个线程之间对一个变量值的可见性
-
volatile boolean flag=true;//volatile:保证多个线程之间对一个变量值的可见性,作为类的成员变量定义 System.out.println();//底层被synchronized修饰,可以确保线程间的可见性 public void println(boolean x) { synchronized (this) { print(x); newLine(); } }
-
线程安全三要素:
- 可见性(多个线程之间对一个变量值的可见性)
- 原子性(不可分割)
- 有序性(禁止指令重排)
-
synchronized:可见性,原子性,有序性,可以实现线程安全
-
volatile:可见性,有序性
-
CAS:保证了原子性,和volatile相结合保证线程安全(Lock底层就是这种方式)
3.8CAS和ABA问题
CAS:Compare And Swap/Set,比较并交换,比较并修改。实现原子性要靠底层的操作,有个类:UnSafe
ABA问题:I=5,A线程想修改为6,修改前需判断I是否改变,即使再次读的I是5,也不代表值没变过,可能是B线程将5改为7,C线程又将7该为5
四、线程通信
线程通信建立在线程同步的基础上。
生产者和消费者问题:仅靠线程同步无法解决
- 上面的方法都是Object中的
- 都只能在同步方法或同步代码块中使用,否则会抛异常
3.1线程通信准备工作
(一)
商品类:
package com.zhang.thread.comm0;
public class Product {
private String name;
private String color;
public Product(String name, String color) {
this.name = name;
this.color = color;
}
public Product() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
生产者:
package com.zhang.thread.comm0;
public class ProductRunnable implements Runnable {
Product product = new Product();
@Override
public void run() {
int i = 0;
while (true) {
if (i % 2 == 0) {
product.setName("馒头");
product.setColor("白色");
} else {
product.setName("玉米饼");
product.setColor("黄色");
}
System.out.println("生产者生产商品:" + product.getName() + " " + product.getColor());
i++;
}
}
}
消费者:
package com.zhang.thread.comm0;
public class ConsumeRunnable implements Runnable {
Product product = new Product();
@Override
public void run() {
while (true) {
System.out.println("消费者购买商品:" + product.getName() + " " + product.getColor());
}
}
}
测试类:
package com.zhang.thread.comm0;
public class Test {
public static void main(String[] args) {
//创建生产者和消费者线程并启动
Runnable runnable1 = new ProductRunnable();
Thread thread1 = new Thread(runnable1);
thread1.start();
Runnable runnable2 = new ConsumeRunnable();
Thread thread2 = new Thread(runnable2);
thread2.start();
}
}
运行结果发现问题1:(发现即使生产者生产了商品,消费者也购买null,因为生产者new了一个对象,消费者new了另外一个对象)
生产者生产商品:玉米饼 黄色
生产者生产商品:馒头 白色
生产者生产商品:玉米饼 黄色
生产者生产商品:馒头 白色
生产者生产商品:玉米饼 黄色
消费者购买商品:null null
消费者购买商品:null null
消费者购买商品:null null
消费者购买商品:null null
(二)解决问题1:
消费者:
package com.zhang.thread.comm1;
public class ConsumeRunnable implements Runnable {
Product product;
public ConsumeRunnable() {
}
public ConsumeRunnable(Product product) {
this.product = product;
}
public void setProduct(Product product) {
this.product = product;
}
@Override
public void run() {
while (true) {
System.out.println("消费者购买商品:" + product.getName() + " " + product.getColor());
}
}
}
生产者:
package com.zhang.thread.comm1;
public class ProductRunnable implements Runnable {
Product product;
public ProductRunnable() {
}
public ProductRunnable(Product product) {
this.product = product;
}
public void setProduct(Product product) {
this.product = product;
}
@Override
public void run() {
int i = 0;
while (true) {
if (i % 2 == 0) {
product.setName("馒头");
product.setColor("白色");
} else {
product.setName("玉米饼");
product.setColor("黄色");
}
System.out.println("生产者生产商品:" + product.getName() + " " + product.getColor());
i++;
}
}
}
测试类:
package com.zhang.thread.comm1;
public class Test {
public static void main(String[] args) {
Product product = new Product();
//创建生产者和消费者线程并启动
Runnable runnable1 = new ProductRunnable(product);
Thread thread1 = new Thread(runnable1);
thread1.start();
Runnable runnable2 = new ConsumeRunnable(product);
Thread thread2 = new Thread(runnable2);
thread2.start();
}
}
运行结果正确。
(三)在生产过程中加 Thread.sleep()
package com.zhang.thread.comm1;
public class ProductRunnable implements Runnable {
Product product;
public ProductRunnable() {
}
public ProductRunnable(Product product) {
this.product = product;
}
public void setProduct(Product product) {
this.product = product;
}
@Override
public void run() {
int i = 0;
while (true) {
if (i % 2 == 0) {
product.setName("馒头");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setColor("白色");
} else {
product.setName("玉米饼");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setColor("黄色");
}
System.out.println("生产者生产商品:" + product.getName() + " " + product.getColor());
i++;
}
}
}
运行结果发现问题2(错误,颜色错误,原因:没有线程同步,生产商品没有生产完,消费者开始消费):
运行结果发现问题3(供不应求,功过于求,原因:生产者和消费者没有进行线程通信):
消费者购买商品:玉米饼 白色
消费者购买商品:玉米饼 白色
消费者购买商品:馒头 黄色
消费者购买商品:馒头 黄色
消费者购买商品:馒头 黄色
消费者购买商品:馒头 黄色
(四)同步代码块中实现线程通信
解决问题2:对生产者和消费者加同把锁synchronized
必须对生产者和消费者同时加锁且加同把锁,否则还会出现黄色的馒头和白色的玉米饼
生产者:
package com.zhang.thread.comm1;
public class ProductRunnable implements Runnable {
Product product;
public ProductRunnable() {
}
public ProductRunnable(Product product) {
this.product = product;
}
public void setProduct(Product product) {
this.product = product;
}
@Override
public void run() {
int i = 0;
while (true) {
synchronized (product) {
if (i % 2 == 0) {
product.setName("馒头");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setColor("白色");
} else {
product.setName("玉米饼");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setColor("黄色");
}
System.out.println("生产者生产商品:" + product.getName() + " " + product.getColor());
i++;
}
}
}
}
消费者:
package com.zhang.thread.comm1;
public class ConsumeRunnable implements Runnable {
Product product;
public ConsumeRunnable() {
}
public ConsumeRunnable(Product product) {
this.product = product;
}
public void setProduct(Product product) {
this.product = product;
}
@Override
public void run() {
while (true) {
synchronized (product) {
System.out.println("消费者购买商品:" + product.getName() + " " + product.getColor());
}
}
}
}
运行结果正常。
(五)解决问题3(供不应求,功过于求,原因:生产者和消费者没有进行线程通信):
商品:( private boolean flag = false;//仓库不满)
package com.zhang.thread.comm2;
public class Product {
private String name;
private String color;
private boolean flag = false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public Product(String name, String color) {
this.name = name;
this.color = color;
}
public Product() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
}
消费者:
package com.zhang.thread.comm2;
public class ConsumeRunnable implements Runnable {
Product product;
public ConsumeRunnable() {
}
public ConsumeRunnable(Product product) {
this.product = product;
}
public void setProduct(Product product) {
this.product = product;
}
@Override
public void run() {
while (true) {
synchronized (product) {
//仓库是否已满,不满则等待
while (!product.isFlag()) {
try {
product.wait();//wait会释放锁和CPU
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者购买商品:" + product.getName() + " " + product.getColor());
product.setFlag(false);//更新仓库为空
product.notifyAll();//通知生产者生产
}
}
}
}
生产者
package com.zhang.thread.comm2;
public class ProductRunnable implements Runnable {
Product product;
public ProductRunnable() {
}
public ProductRunnable(Product product) {
this.product = product;
}
public void setProduct(Product product) {
this.product = product;
}
@Override
public void run() {
int i = 0;
while (true) {
synchronized (product) {
//仓库是否满,满则等待
while (product.isFlag()) {
try {
product.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (i % 2 == 0) {
product.setName("馒头");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setColor("白色");
} else {
product.setName("玉米饼");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setColor("黄色");
}
System.out.println("生产者生产商品:" + product.getName() + " " + product.getColor());
i++;
product.setFlag(true);//更新仓库已满
product.notifyAll();//通知消费者仓库已满
}
}
}
}
总结:
-
进行线程通信的多个线程要使用同一个锁对象,还必须调用锁对象的wait(),notify(),notifyAll()
-
线程通信的三个方法:
- wait():在调用notify(),notifyAll()之前,一直等待
- wait(time):在调用notify(),notifyAll()之前,或在指定时间之前,一直等待
- notify():唤醒在锁对象上等待的单个线程,唤醒的线程是随机的
- notifyAll():唤醒在锁对象上等待的所有线程,线程将进行竞争得到CPU
-
完整的线程生命周期
- 阻塞有三种状态:
- 普通阻塞:sleep(),join(),等待键盘输入
- 同步阻塞:(锁池队列,锁池状态),没有获取到锁对象的线程的队列
- 等待阻塞:(等待队列),调用wait()后释放锁,进入该队列
锁池状态:拿不到对象锁的在锁池状态等待
等待队列:调用wait()后在等待队列等待,直到调用了notify(),notifyAll(),wait时间到后,到锁池状态等待
只有拿到了锁,才能进入运行状态
-
sleep()和wait()的区别?
- sleep()会让出CPU,进入普通阻塞状态,不释放锁;wait()会让出CPU进入等待阻塞队列,释放锁
- wait()只能在同步方法和同步代码块中使用,sleep()可以在任何地方使用
3.2 同步方法中实现线程通信
商品:
package com.zhang.thread.comm3;
public class Product {
private String name;
private String color;
private boolean flag = false;//不满
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public Product(String name, String color) {
this.name = name;
this.color = color;
}
public Product() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
public synchronized void product(String name, String color) {
//仓库是否满,满则等待
while (this.isFlag()) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.color = color;
// System.out.println("生产者生产商品:" + this.getName() + " " + this.getColor());
System.out.println("生产者生产商品:" + name + " " + color);
this.setFlag(true);//更新仓库已满
this.notifyAll();//通知消费者仓库已满
}
public synchronized void consume() {
//仓库是否已满,不满则等待
while (!this.isFlag()) {
try {
this.wait();//wait会释放锁和CPU
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者购买商品:" + name + " " + color);
this.setFlag(false);//更新仓库为空
this.notifyAll();//通知生产者生产
}
}
生产者:
package com.zhang.thread.comm3;
public class ProductRunnable implements Runnable {
Product product;
public ProductRunnable() {
}
public ProductRunnable(Product product) {
this.product = product;
}
public void setProduct(Product product) {
this.product = product;
}
@Override
public void run() {
int i = 0;
while (true) {
if (i % 2 == 0) {
product.product("馒头", "白色");
} else {
product.product("玉米饼", "黄色");
}
i++;
}
}
}
消费者:
package com.zhang.thread.comm3;
public class ConsumeRunnable implements Runnable {
Product product;
public ConsumeRunnable() {
}
public ConsumeRunnable(Product product) {
this.product = product;
}
public void setProduct(Product product) {
this.product = product;
}
@Override
public void run() {
while (true) {
product.consume();
}
}
}
测试类:
package com.zhang.thread.comm3;
public class Test {
public static void main(String[] args) {
Product product = new Product();
//创建生产者和消费者线程并启动
Runnable runnable1 = new ProductRunnable(product);
Thread thread1 = new Thread(runnable1);
thread1.start();
Runnable runnable2 = new ConsumeRunnable(product);
Thread thread2 = new Thread(runnable2);
thread2.start();
}
}
运行结果:
生产者生产商品:馒头 白色
消费者购买商品:馒头 白色
生产者生产商品:玉米饼 黄色
消费者购买商品:玉米饼 黄色
生产者生产商品:馒头 白色
消费者购买商品:馒头 白色
生产者生产商品:玉米饼 黄色
消费者购买商品:玉米饼 黄色
Process finished with exit code -1
总结:
- 同步方法的同步监视器都是this,所以需要将consume()和product()放入同一个类Product中,确保是同一把锁
- 必须调用this的wait(),notify(),notifyAll(),this可省略
3.3Lock锁实现线程通信
通过synchronized通信时,生产者和消费者在一个等待队列,无法精确的唤醒生产者或消费者,而Lock锁可以实现
package com.zhang.thread.comm4;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Product {
private String name;
private String color;
private boolean flag = false;//不满
Lock lock = new ReentrantLock();//lock锁
Condition productCondition = lock.newCondition();//生产者队列
Condition consumeCondition = lock.newCondition();//消费者队列
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public Product(String name, String color) {
this.name = name;
this.color = color;
}
public Product() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Product{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
'}';
}
public void product(String name, String color) {
lock.lock();
try {
//仓库是否满,满则等待
while (this.isFlag()) {
try {
productCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.color = color;
// System.out.println("生产者生产商品:" + this.getName() + " " + this.getColor());
System.out.println("生产者生产商品:" + name + " " + color);
this.setFlag(true);//更新仓库已满
consumeCondition.signalAll();//通知消费者仓库已满
} finally {
lock.unlock();
}
}
public void consume() {
lock.lock();
try {
//仓库是否已满,不满则等待
while (!this.isFlag()) {
try {
consumeCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者购买商品:" + name + " " + color);
this.setFlag(false);//更新仓库为空
productCondition.signalAll();//通知生产者生产
} finally {
lock.unlock();
}
}
}
3.4Condition
- Condition是在1.5中出现了,用来代替Object中的wait(),notify(),notifyAll()
- Condition中的await(),signalAll(),signal()比wait(),notify(),notifyAll()能更安全高效的实现线程间的通信
- 同一个锁可以创建多个Condition。一个Condition包含一个等待队列,一个Lock可以创建多个Condition,所以可以有多个等待队列
- 在Object监视器上可以有一个同步队列和一个等待队列,而Lock拥有一个同步队列和多个等待队列
- 调用的Condition中的await(),signalAll(),signal()必须在lock.lock()和lock.unlock()之间
- Condition中的await(),对应Object中的wait()
- Condition中的signal()对应Object中的notify()
- Condition中的signalAll()对应Object中的notifyAll()
Condition productCondition = lock.newCondition();//生产者队列
Condition consumeCondition = lock.newCondition();//消费者队列
五、线程池
5.1什么是线程池
为什么要用线程池:创建对象(需分配内存)和销毁对象是非常消耗时间的,在并发情况下的线程,对性能影响大。所以可以创建多个线程放到线程池中,使用时直接获取引用,不用时放回池子,实现重复利用。
线程池的好处:
-
(1)减少了创建线程的时间,提高了响应速度;
-
(2)重复利用线程池中的线程,降低了资源消耗;
-
(3)提高线程的可管理性,避免无限制的创建线程
线程池的应用场合:
- 需要大量线程,且完成任务时间短
- 对性能要求苛刻的
- 接受突发性的大量请求
创建对象(需分配内存)和销毁对象是非常消耗时间的,在并发情况下的线程,对性能影响大。所以可以创建多个线程放到线程池中,使用时直接获取引用,不用时放回池子,实现重复利用。
jdk1.5提供了内置的线程池:
使用者提交任务后,要看核心线程池是否已满,没满则创建,满了则看等待的队列是否已满,队列没满将任务存在队列里排队,队列也满了看整个线程池是否已满,不满则创建线程执行任务,满了按照策略处理无法执行的任务
5.2使用线程池执行大量的Runnable命令
package com.zhang.thread.pool.pool1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestRunnablePool {
public static void main(String[] args) {
//创建线程
//线程池只有一个线程,保证一定有一个
ExecutorService pool = Executors.newSingleThreadExecutor();
//线程池有固定数量的线程
//ExecutorService pool = Executors.newFixedThreadPool(10);
//线程池有可变数量的线程
//ExecutorService pool=Executors.newCachedThreadPool();
//用来间隔执行(半小时执行一次)或延迟执行(12:00执行)
//ExecutorService pool = Executors.newScheduledThreadPool(10);
//使用线程池执行大量的Runnable命令
for (int i = 0; i < 100; i++) {
final int n = i;
pool.execute(() -> {
System.out.println("线程开始----------" + n);
System.out.println("线程结束----------" + n);
});
}
//关闭线程
pool.shutdown();
}
}
5.3使用线程池执行大量的Callable命令(有返回值)
注意:必须先将任务全部添加到队列,然后再获取结果
package com.zhang.thread.pool.pool1;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
public class TestRunnablePool2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建线程
//线程池只有一个线程,保证一定有一个
//ExecutorService pool = Executors.newSingleThreadExecutor();
//线程池有固定数量的线程
ExecutorService pool = Executors.newFixedThreadPool(10);
//线程池有可变数量的线程
//ExecutorService pool=Executors.newCachedThreadPool();
//用来间隔执行(半小时执行一次)或延迟执行(12:00执行)
//ExecutorService pool = Executors.newScheduledThreadPool(10);
//使用线程池执行大量的Callable命令
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(2000);
return new Random().nextInt(10);
}
};
List<Future> list = new ArrayList<>();
for (int i = 0; i < 200; i++) {
Future<Integer> submit = pool.submit(callable);
list.add(submit);//必须先将任务添加到队列里
}
for (int i = 0; i < 200; i++) {
System.out.println(list.get(i).get());
}
//关闭线程
pool.shutdown();
}
}
5.4线程池的API
-
Executor:线程池顶级接口,只有一个方法
public interface Executor { void execute(Runnable command); }
-
ExecutorService:真正的线程池接口
public interface ExecutorService extends Executor { void execute(Runnable command);//执行任务,没有返回值,一般用来执行Runnable Future<?> submit(Runnable task);//执行任务,有返回值,一般用来执行Callable void shutdown();//关闭线程池 }
-
AbstractExecutorService:基本实现了ExecutorService的所有方法
public abstract class AbstractExecutorService implements ExecutorService {}
-
ThreadPoolExecutor:默认的线程池实现类
public class ThreadPoolExecutor extends AbstractExecutorService {}
-
ThreadPoolExecutor的七个参数
int corePoolSize//核心池的大小 int maximumPoolSize//最大线程数 long keepAliveTime//线程没有任务时存活的时间(非核心池线程) TimeUnit unit,//keepAliveTime存活单位 BlockingQueue<Runnable> workQueue//存储等待执行任务的阻塞队列(可是顺序队列,链式队列等) ThreadFactory threadFactory,//线程工厂,Executors的静态内部类 RejectedExecutionHandler handler//拒绝处理任务的四种策略 //AbortPolicy:默认抛出异常 //DiscardPolicy:什么也不做 //DiscardOldestPolicy:将在线程池等待队列中的第一个抛弃,当前的放进去 //CallerRunsPolicy:如果线程还在运行,直接运行这个线程
-
-
ScheduledThreadPoolExecutor:实现周期性任务调度的线程池
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService {} public interface ScheduledExecutorService extends ExecutorService { }
-
Executors:线程池的工具类,线程池的工厂类
//线程池只有一个线程,保证一定有一个 Executors.newSingleThreadExecutor(); //线程池有固定数量的线程 Executors.newFixedThreadPool(10); //线程池有可变数量的线程 Executors.newCachedThreadPool(); //用来间隔执行(半小时执行一次)或延迟执行(12:00执行) Executors.newScheduledThreadPool(10);
5.5ForkJoin框架
java7提供的框架,把一个大任务分解为若干小任务,最终汇总每个小任务的结果,得到大任务的结果的框架
ForkJoinPool和ThreadPoolExecutor是兄弟关系,都继承了AbstractExecutorService
public class ForkJoinPool extends AbstractExecutorService {
}
-
ThreadPoolExecutor多个线程共用一个队列;而ForkJoinPool每个线程有自己的队列,当自己队列中的任务完成后,从其他线程的任务队列(尾部)偷任务执行,充分利用资源(工作窃取算法)
-
工作窃取算法:
优点:利用线程进行并行计算,减少了线程间的竞争
缺点:双端队列只有一个任务时,线程间会竞争;会消耗更多资源,如会创建多个线程和多个双端队列
六、用多线程实现火车站售票
1.方法一:synchronized()同步块
package com.zhang.thread.home;
/**
* 火车站四个窗口,共卖100张票
* 分析:四个线程共享100张票,实现Runnable接口
*/
public class TestTicket {
public static void main(String[] args) {
Runnable myRunnable1 = new MyRunnable1();
new Thread(myRunnable1, "窗口1").start();
new Thread(myRunnable1, "窗口2").start();
new Thread(myRunnable1, "窗口3").start();
new Thread(myRunnable1, "窗口4").start();
}
}
class MyRunnable1 implements Runnable {
int ticketNum = 100;
@Override
public void run() {
while (true) {
synchronized (this) {
if (ticketNum <= 0) {
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticketNum + "张票");
ticketNum--;
}
}
}
}
2.方法二:synchronized 同步方法
package com.zhang.thread.home;
/**
* 火车站四个窗口,共卖100张票
* 分析:四个线程共享100张票,实现Runnable接口
*/
public class TestTicket2 {
public static void main(String[] args) {
Runnable myRunnable1 = new MyRunnable2();
new Thread(myRunnable1, "窗口1").start();
new Thread(myRunnable1, "窗口2").start();
new Thread(myRunnable1, "窗口3").start();
new Thread(myRunnable1, "窗口4").start();
}
}
class MyRunnable2 implements Runnable {
int ticketNum = 100;
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
break;
}
sellTicket();
}
}
public synchronized void sellTicket() {
if (ticketNum <= 0) {
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticketNum + "张票");
ticketNum--;
}
}
3.方法三:Lock锁
package com.zhang.thread.home;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 火车站四个窗口,共卖100张票
* 分析:四个线程共享100张票,实现Runnable接口
*/
public class TestTicket3 {
public static void main(String[] args) {
Runnable myRunnable1 = new MyRunnable3();
new Thread(myRunnable1, "窗口1").start();
new Thread(myRunnable1, "窗口2").start();
new Thread(myRunnable1, "窗口3").start();
new Thread(myRunnable1, "窗口4").start();
}
}
class MyRunnable3 implements Runnable {
int ticketNum = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (ticketNum <= 0) {
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticketNum + "张票");
ticketNum--;
} finally {
lock.unlock();
}
}
}
}