多线程(十八)
进程 :进程是程序的基本执行实体。
线程 :一个进程可以包括多个线程,线程是操作系统能够进行运算调度的最小单位,它是进程中实际运作单位,简单理解为在应用软件中互相独立,可以同时运行的功能。
有了多线程 ,可让程序同时做多件事情,多线程 可以提高效率,若想让多个事情同时运行就需要用到多线程,比如:软件中的耗时操作,所有的聊天软件,所有的服务器。
并发 :在同一时刻,有多个指令在单个CPU上交替执行。
并行 :在同一时刻,有多个指令在单个CPU上同时执行。
线程的调度
- 抢占式调度:指优先让可运行池中优先级高的线程占用CPU,优先级并不绝对,只不过优先级越高,占用CPU的概率越高。
- 分时调度:又称非抢占式调度,指让所有线程轮流获得cpu的使用权。
多线程的实现方式
线程是程序中的执行线程,java虚拟机允许应用程序并发地运行多个执行线程。
- 继承Thread类的方式进行实现
- 实现Runnable接口的方式进行实现
- 利用Callable接口和Future接口方式实现
继承Thread类
步骤:
- 定义一个类继承Thread
- 重写run方法
- 创建子类对象,并启动线程(利用start方法启动,run方法会自动调用)
范例:
java
//Test.java
public class Test {
public static void main(String[] args) {
Function f1=new Function();
Function f2=new Function();
f1.setName("线程1");
f2.setName("线程2");
f1.start();
f2.start();
}
}
//Function.java
public class Function extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"执行");
}
}
}
/*
线程2执行
线程2执行
线程2执行
线程2执行
线程1执行
线程2执行
线程1执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
*/
实现Runnable接口
步骤:
- 定义一个类实现Runnable接口
- 重写里面的run方法
- 创建自己类的对象
- 创建一个Thread类的对象,并开启线程
范例:
java
//Test.java
public class Test {
public static void main(String[] args) {
//创建function对象,表示多线程要执行的任务
Function f=new Function();
//创建线程对象
Thread t1=new Thread(f);
Thread t2=new Thread(f);
//给线程设置名字
t1.setName("线程1");
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
//Function.java
public class Function implements Runnable {
//线程要执行的代码
@Override
public void run() {
//获取当前线程的对象
Thread t=Thread.currentThread();
for (int i=0;i<10;i++) {
System.out.println(t.getName()+"执行");
}
}
}
/*
线程1执行
线程1执行
线程2执行
线程1执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程2执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
线程1执行
*/
Callable接口和Future接口方式实现
特点:可获取多线程运行的结果
步骤:
- 创建一个类MyCallable类实现Callable接口,其中泛型便是多线程运行的结果类型
- 重写Call方法(具有返回值,表示多线程运行的结果)
- 创建MyCallable的对象(表示多线程要执行的任务)
- 创建FutureTask的对象(作用为管理多线程运行的结果)
- 创建Thread对象,并启动(表示线程)
范例:
java
//MyCallable.java
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum=0;
for(int i=1;i<=100;i++){
sum+=i;
}
return sum;
}
}
//
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个类MyCallable实现Callable接口
MyCallable mc=new MyCallable();
//创建FutureTask的对象(作用为管理多线程运行的结果)
FutureTask<Integer> ft=new FutureTask<>(mc);
//创建线程的对象
Thread t=new Thread(ft);
//启动线程
t.start();
//获取多线程运行的结果
Integer integer = ft.get();
System.out.println(integer);
}
}
/*
5050
*/
三种方式对比
-
继承Thread类
- 优点:编程比较简单,可直接使用Thread类的方法
- 缺点:可扩展性差,不能再继承其他类
-
实现Runnable接口
- 优点:扩展性强,实现该接口的同时还可以继承其他类
- 缺点:编程相对复杂,不能直接使用Thread类中的方法
-
实现Callable接口
- 优点:扩展性强,实现该接口的同时还可以继承其他类,可获取多线程运行的结果
- 缺点:编程相对复杂,不能直接使用Thread类中的方法
常见成员方法
getName
String getName()
说明:返回此线程的名称,若没有给线程设置名字,线程也是有默认名字的,默认名字格式:Thread-X(X序号,从0开始)
setName
void setName(String name)
说明:设置线程名字(构造方法也可以设置名字)
currentThread
static Thread currentThread()
说明:获取当前线程的对象,当虚拟机启动后,会自动启用多条线程,其中一条叫做main线程,它的作用便是调用main方法,并执行其中代码。
sleep
static void sleep(long time)
说明:让线程休眠指定的时间,单位为毫秒(1秒等于1000毫秒),哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间。
setPriority
setPriority(int newPriority)
说明:设置线程的优先级,优先级最高位10,最低为1,默认为5
getPriority
final int getPriority()
说明:获取线程的优先级
setDaemon
final void setDaemon(boolean on)
说明:设置守护线程,当其他非守护线程执行完毕后,守护线程会陆续结束。
yield
public static void yield()
说明:出让线程/礼让线程
join
public final void join()
说明:插入线程/插队线程。表示把调用该方法的线程插入到当前线程之前
范例
java
//MyThread.java
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName());
}
}
//Test.java
public class Test {
public static void main(String[] args){
//getName默认
MyThread mt1=new MyThread();
mt1.start();
//构造方法设置
MyThread mt2=new MyThread("线程2");
mt2.start();
//通过setName设置后的getName
MyThread mt3=new MyThread();
mt3.setName("线程3");
mt3.start();
//currentThread
Thread t=Thread.currentThread();
System.out.println(t.getName());
}
}
/*
main
线程2
Thread-0
线程3
*/
java
//MyThread.java
public class MyThread implements Runnable {
@Override
public void run() {
for (int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
//Test.java
public class Test {
public static void main(String[] args){
MyThread mt=new MyThread();
Thread t1=new Thread(mt,"线程1");
Thread t2=new Thread(mt,"线程2");
System.out.println("默认");
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
System.out.println("修该后");
t1.setPriority(1);
t2.setPriority(10);
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
t1.start();
t2.start();
}
}
/*
默认
5
5
修该后
1
10
线程2:0
线程2:1
线程1:0
线程2:2
线程1:1
线程1:2
线程2:3
线程1:3
线程2:4
线程1:4
线程2:5
线程2:6
线程2:7
线程2:8
线程2:9
线程1:5
线程1:6
线程1:7
线程1:8
线程1:9
*/
java
//Test.java
public class Test {
public static void main(String[] args){
MyThread1 mt1=new MyThread1();
MyThread2 mt2=new MyThread2();
mt1.setName("守护线程");
mt2.setName("非守护线程");
mt1.setDaemon(true);
mt1.start();
mt2.start();
}
}
//MyThread1.java
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+i);
}
}
}
//MyThread2.java
public class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+i);
}
}
}
/*
守护线程0
非守护线程0
非守护线程1
守护线程1
非守护线程2
守护线程2
非守护线程3
非守护线程4
非守护线程5
非守护线程6
非守护线程7
非守护线程8
非守护线程9
守护线程3
守护线程4
守护线程5
守护线程6
守护线程7
*/
java
//Test.java
public class Test {
public static void main(String[] args){
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();
mt1.setName("线程一");
mt2.setName("线程二");
mt1.start();
mt2.start();
}
}
//MyThread.java
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+i);
Thread.yield();
}
}
}
/*
线程一0
线程二0
线程二1
线程一1
线程一2
线程一3
线程二2
线程一4
线程一5
线程二3
线程二4
线程二5
线程一6
线程二6
线程一7
线程二7
线程一8
线程一9
线程二8
线程二9
*/
java
//Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread mt=new MyThread();
mt.setName("线程一");
mt.start();
mt.join();
for (int i = 0; i < 10; i++) {
System.out.println("main线程"+i);
}
}
}
//MyThread.java
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+i);
Thread.yield();
}
}
}
/*
线程一0
线程一1
线程一2
线程一3
线程一4
线程一5
线程一6
线程一7
线程一8
线程一9
main线程0
main线程1
main线程2
main线程3
main线程4
main线程5
main线程6
main线程7
main线程8
main线程9
*/
线程的生命周期
新建状态NEW:创建线程对象。调用start方法后变成就绪状态
就绪状态RUNNABLE:有执行资格,没执行权。不停的抢CPU
阻塞状态BLOCKED:无法获得锁对象
等待状态WAITING:wait方法
计时状态TIMED_WAITING:sleep方法
死亡状态TERMINATED:若run执行完毕,线程死亡,变成垃圾
同步代码块
把操作共享数据的代码锁起来。
特点:
- 锁默认打开,有一个线程进去,锁自动关闭
- 里面的代码全部执行完毕,线程出来,锁自动打开
格式:
java
synchronized(锁){
操作共享数据的代码
}
//锁对象一定要是唯一的,一般写当前类的字节码文件。
范例:
java
//某电影院目前上映的甲电影,共有10张票,有3个窗口售卖,设计一个程序模拟该电影院买票
//Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread mt1=new MyThread();
mt1.setName("窗口1");
MyThread mt2=new MyThread();
mt2.setName("窗口2");
MyThread mt3=new MyThread();
mt3.setName("窗口3");
mt1.start();
mt2.start();
mt3.start();
}
}
//MyThread.java
public class MyThread extends Thread{
static int ticketNum=1;
@Override
public void run() {
while (true) {
synchronized (MyThread.class) {
if (ticketNum <=10) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "卖出第" + ticketNum + "张票");
ticketNum++;
} else {
break;
}
}
}
}
}
/*
窗口1卖出第1张票
窗口1卖出第2张票
窗口3卖出第3张票
窗口3卖出第4张票
窗口3卖出第5张票
窗口3卖出第6张票
窗口3卖出第7张票
窗口3卖出第8张票
窗口2卖出第9张票
窗口2卖出第10张票
*/
同步方法
就是把synchronized关键字加到方法上。
格式:
修饰符 synchronized 返回值类型 方法名(方法参数){...}
特点:
- 同步方法是锁住方法里面所有的代码
- 锁对象不能自己指定。
- 非静态方法:this
- 静态方法:当前类的字节码文件对象
范例:
java
//某电影院目前上映的甲电影,共有10张票,有3个窗口售卖,设计一个程序模拟该电影院买票,同步方法实现
//Test.java
1public class Test {
public static void main(String[] args){
MyThread myThread=new MyThread();
Thread t1=new Thread(myThread);
Thread t2=new Thread(myThread);
Thread t3=new Thread(myThread);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
//MyThread.java
public class MyThread implements Runnable{
int ticketNum=0;
@Override
public void run() {
while (true){
if (method()){
break;
}
}
}
private synchronized boolean method(){
if(ticketNum==10){
return true;
}else {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticketNum++;
System.out.println(Thread.currentThread().getName()+ "卖出第" + ticketNum + "张票");
}
return false;
}
}
/*
窗口1卖出第1张票
窗口1卖出第2张票
窗口1卖出第3张票
窗口1卖出第4张票
窗口1卖出第5张票
窗口1卖出第6张票
窗口1卖出第7张票
窗口1卖出第8张票
窗口3卖出第9张票
窗口2卖出第10张票
*/
lock锁
Lock中提供获得锁 和释放锁的方法。
void lock(); 获得锁
void unlock(); 释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。
ReentrantLock的构造方法:
ReentrantLock(); 创建一个ReentrantLock的实例。
范例:
java
//某电影院目前上映的甲电影,共有10张票,有3个窗口售卖,设计一个程序模拟该电影院买票,Lock实现
//Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread mt1=new MyThread();
mt1.setName("窗口1");
MyThread mt2=new MyThread();
mt2.setName("窗口2");
MyThread mt3=new MyThread();
mt3.setName("窗口3");
mt1.start();
mt2.start();
mt3.start();
}
}
//MyThread.java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread{
static int ticketNum=1;
static Lock lock=new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
if (ticketNum <=100) {
Thread.sleep(10);
System.out.println(getName() + "卖出第" + ticketNum + "张票");
ticketNum++;
} else {
break;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
注:写锁的时候不要让锁嵌套,否则可能会死锁。
等待唤醒机制
方式一
等待唤醒机制(生产者消费者模式)是多个线程之间的一种协作机制。
思路:
此处将消费者比喻成吃货,生产者比喻成厨师。
- 消费者 :消费数据
- 判断桌子上是否有食物
- 若无,等待
- 若有,开吃
- 吃完后,唤醒厨师继续做
- 生产者 :生产数据
- 判断桌子上是否有食物
- 若有,等待
- 若无,做饭
- 把食物放到桌子上
- 唤醒等待的消费者开吃
常见方法
wait
void wait()
说明:当前线程等待,直到被其他线程唤醒
notify
void notify()
说明:随机唤醒单个线程
notifyAll
void notifyAll()
说明:唤醒所有线程
范例
java
//Desk.java
public class Desk {
public static int count=10;//吃货能吃的饭的数量
public static Object lock=new Object();//锁
public static int state=0;//桌子的状态,若无食物为0,若有食物为1
}
//Cookie.java
public class Cookie extends Thread {
/*
1. 循环
2.同步代码块
3.判断共享数据是否到了尽头(到了尽头)
4.判断共享数据是否到了尽头(没到尽头,执行核心逻辑)
*/
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count==0){
break;
}else {//判断桌子上是否有食物
if(Desk.state==0){//无食物,厨师做饭
System.out.println("厨师做饭");
//桌子上有了食物,状态设为1
Desk.state=1;
//唤醒吃货
Desk.lock.notifyAll();
}else{//若有,则等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
//Foodie.java
public class Foodie extends Thread{
/*
1. 循环
2.同步代码块
3.判断共享数据是否到了尽头(到了尽头)
4.判断共享数据是否到了尽头(没到尽头,执行核心逻辑)
*/
@Override
public void run() {
while (true){
synchronized (Desk.lock){
if (Desk.count==0){
break;
}else {
//判断桌子是否有食物
if(Desk.state==0){
try {//若无,等待
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{//若有,开吃
//先将饭量-1
Desk.count--;
System.out.println("吃货还能吃"+Desk.count+"顿饭");
//吃饭后,桌子上没有事物,状态设为0
Desk.state=0;
//唤醒厨师
Desk.lock.notifyAll();
}
}
}
}
}
}
//Test.java
public class Test {
public static void main(String[] args) {
Cookie cookie=new Cookie();
Foodie foodie=new Foodie();
cookie.start();
foodie.start();
}
}
/*
厨师做饭
吃货还能吃9顿饭
厨师做饭
吃货还能吃8顿饭
厨师做饭
吃货还能吃7顿饭
厨师做饭
吃货还能吃6顿饭
厨师做饭
吃货还能吃5顿饭
厨师做饭
吃货还能吃4顿饭
厨师做饭
吃货还能吃3顿饭
厨师做饭
吃货还能吃2顿饭
厨师做饭
吃货还能吃1顿饭
厨师做饭
吃货还能吃0顿饭
*/
方式二
阻塞队列实现等待唤醒机制。
阻塞队列继承结构
阻塞队列实现Iterable,Collection,Queue,BlockingQueue接口,有两个实现类:
- ArrayBlockingQueue:底层是数组,有界
- LinkedBlockingQueue:底层是链表,无界,但不是真的无界,最大为int最大值
思路:
此处将消费者比喻成吃货,生产者比喻成厨师。
- 厨师
- 构造方法中接收一个阻塞队列对象
- run方法中循环向阻塞队列添加食物
- 打印添加结果
- 吃货
- 构造方法中接收一个阻塞队列对象
- run方法中循环获取阻塞队列的食物
- 打印获取结果
核心方法
put(anObject)
将参数放入队列,若放不进去会堵塞。
take()
取出第一个数据,取不到会堵塞。
注:put,take方法里本身就有锁
范例
java
//Test.java
import java.util.concurrent.ArrayBlockingQueue;
public class Test {
public static void main(String[] args){
ArrayBlockingQueue<String> abq=new ArrayBlockingQueue<>(1);//创建阻塞对象,并设置容量
Cookie cookie=new Cookie(abq);
Foodie foodie=new Foodie(abq);
cookie.start();
foodie.start();
}
}
//Cookie.java
import java.util.concurrent.ArrayBlockingQueue;
public class Cookie extends Thread{
ArrayBlockingQueue<String> abq;
public Cookie(ArrayBlockingQueue<String> abq){
this.abq=abq;
}
@Override
public void run() {
while (true){
try {
abq.put("食物");
System.out.println("厨师放食物");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//Foodie.java
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{
ArrayBlockingQueue<String> abq;
public Foodie(ArrayBlockingQueue<String> abq){
this.abq=abq;
}
@Override
public void run() {
while (true){
try {
String s=abq.take();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
练习
例1
java
/*
假设:100元分成了三个红包(均为整数),有5人去抢
其中红包是共享数据。
5个人5条线程
打印结果如下:
XXX抢到了XX元
XXX抢到了XX元
XXX抢到了XX元
XXX没抢到
XXX没抢到
*/
//Test.java
public class Test {
public static void main(String[] args) {
RedPackage rp1=new RedPackage();
RedPackage rp2=new RedPackage();
RedPackage rp3=new RedPackage();
RedPackage rp4=new RedPackage();
RedPackage rp5=new RedPackage();
rp1.setName("线程1");
rp2.setName("线程2");
rp3.setName("线程3");
rp4.setName("线程4");
rp5.setName("线程5");
rp1.start();
rp2.start();
rp3.start();
rp4.start();
rp5.start();
}
}
//RedPackage.java
import java.util.Random;
public class RedPackage extends Thread{
static int sum=100;
static int count=3;
@Override
public void run() {
synchronized (RedPackage.class){
if(count==0){
System.out.println(getName()+"没抢到");
}else{
if(count==1){
int money=sum;
System.out.println(getName()+"抢到了"+money+"元");
}else{
Random r=new Random();
int money=r.nextInt(sum-(count-1))+1;
System.out.println(getName()+"抢到了"+money+"元");
sum=sum-money;
}
count--;
}
}
}
}
/*
线程1抢到了3元
线程5抢到了40元
线程4抢到了57元
线程3没抢到
线程2没抢到
*/
例2
java
/*
有一个抽奖池,该抽奖池中存放了奖励的金额,该抽奖池中的奖项为10,5,20,50,100,200,500,800,2,80,300,700
创建两个抽奖箱(线程)设置线程名分别为"抽奖箱1"和"抽奖箱2"
随机从抽奖池中获取奖项元素并打印在控制台上,格式如下:
在此次抽奖过程中,抽奖箱2共产生6个奖项
分别为:800,10,80,500,700,2最高奖项为800元,总计额为2092元
在此次抽奖过程中,抽奖箱1共产生6个奖项
分别为:5,100,50,300,20,200最高奖项为300元,总计额为675元
在此次抽奖过程中,抽奖箱2中产生最大奖项,该奖项金额为800元
*/
//Lottery.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.Callable;
public class Lottery implements Callable<Integer> {
ArrayList<Integer> moneyBox;
public Lottery(ArrayList<Integer> moneyBox){
this.moneyBox=moneyBox;
}
@Override
public Integer call() throws Exception {
ArrayList<Integer> list=new ArrayList<>();
int sum=0;
while (true){
synchronized (Lottery.class) {
if (moneyBox.size() == 0) {
print(list,Collections.max(list),sum);
break;
} else {
Collections.shuffle(moneyBox);
int money = moneyBox.remove(0);
list.add(money);
sum+=money;
}
}
Thread.sleep(10);
}
if(list.size()==0){
return null;
}
return Collections.max(list);
}
private static void print(ArrayList<Integer> list,int max,int sum){
System.out.println("在此次抽奖过程中,"+Thread.currentThread().getName()+"共产生"+list.size()+"个奖项");
System.out.print("分别为:");
for(int i=0;i<list.size();i++){
System.out.print(list.get(i));
if(i< list.size()-1){
System.out.print(",");
}
}
System.out.println("最高奖项为"+max+"元,总计额为"+sum+"元");
}
}
//Test.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ArrayList<Integer> moneyBox=new ArrayList<>();
Collections.addAll(moneyBox,10,5,20,50,100,200,500,800,2,80,300,700);
Lottery lottery=new Lottery(moneyBox);
FutureTask<Integer> ft1=new FutureTask<>(lottery);
FutureTask<Integer> ft2=new FutureTask<>(lottery);
Thread t1=new Thread(ft1);
Thread t2=new Thread(ft2);
t1.setName("抽奖箱1");
t2.setName("抽奖箱2");
t1.start();
t2.start();
int money;
String name;
if(ft1.get()>ft2.get()){
money=ft1.get();
name=t1.getName();
}else{
money=ft2.get();
name=t2.getName();
}
System.out.println("在此次抽奖过程中,"+name+"产生最大值,最大值金额为"+money);
}
}
/*
在此次抽奖过程中,抽奖箱1共产生6个奖项
分别为:80,100,50,5,10,700最高奖项为700元,总计额为945元
在此次抽奖过程中,抽奖箱2共产生6个奖项
分别为:500,200,20,300,2,800最高奖项为800元,总计额为1822元
在此次抽奖过程中,抽奖箱2产生最大值,最大值金额为800
*/
线程池
核心原理
- 创建一个池子,池子中是空的
- 提交任务时,池子会创建新的线程对象,执行完毕后,线程归还给池子,下次再提交任务时,不需创建新线程,直接复用已有的线程即可
- 若提交任务时,池子中没有空闲线程,也无法创建新的线程,任务便会排队等待
代码实现步骤:
- 创建线程池
- 提交任务
- 所有的任务全部执行完毕,关闭线程池
Executors
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
newCachedThreadPool
格式:
public static ExecutorsService newCachedThreadPool()
说明:创建一个几乎没有上限的线程池
newFixedThreadPool
格式:
public static ExecutorsService newFixedThreadPool(int nThreads)
说明:创建一个有上限的线程池
范例
java
//Test.java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) throws InterruptedException {
//1.获取线程池对象
ExecutorService pool= Executors.newCachedThreadPool();
//2.提交任务
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//3.销毁线程池
pool.shutdown();
}
}
//MyRunnable.java
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
/*
pool-1-thread-1
pool-1-thread-3
pool-1-thread-5
pool-1-thread-2
pool-1-thread-4
*/
java
//MyRunnable.java
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
//1.获取线程池对象
ExecutorService pool= Executors.newFixedThreadPool(3);
//2.提交任务
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//3.销毁线程池
pool.shutdown();
}
}
/*
pool-1-thread-2
pool-1-thread-1
pool-1-thread-3
pool-1-thread-1
pool-1-thread-2
*/
自定义线程
步骤
-
创建一个空的池子
-
有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程。
注:不断提交任务,会有以下三个临界点
- 当核心线程满时,在提交任务就会排队
- 当核心线程满,队满时,会创建临时线程
- 当核心线程满,队满时,临时线程满时,会触发任务拒绝策略
核心元素(参数)
- 核心线程数量(不少于0)
- 线程池中最大线程数量(最大数量>=核心线程数量)
- 空闲时间(值)(不能小于0)
- 空闲时间(单位)(用TimeUnit绑定)
- 阻塞队列(不能为null)
- 创建线程工厂(不能为null)
- 要执行的任务过多时的解决方案(不能为null)
范例
java
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args){
ThreadPoolExecutor pool=new ThreadPoolExecutor(
3,//核心线程数量(不少于0)
6,//线程池中最大线程数量(最大数量>=核心线程数量)
60,//空闲时间(值)(不能小于0)
TimeUnit.SECONDS,//空闲时间(单位)(用TimeUnit绑定)
new ArrayBlockingQueue<>(3),//阻塞队列(不能为null)
Executors.defaultThreadFactory(),//创建线程工厂(不能为null)
new ThreadPoolExecutor.AbortPolicy()//要执行的任务过多时的解决方案(不能为null)
);
}
}
线程池多大合适
最大并行数:可用Runtime.getRuntime().availableProcessors()返回当前电脑的最大并行数
CPU密集型运算:即项目中CPU计算居多,线程池大小=最大并行数+1
I/O密集型运算:即项目中文件操作居多,线程池大小=最大并行数*期望CPU利用率*(总时间(CPU计算时间+等待时间)/CPU计算时间)