Day23-多线程
1.多线程
1.1 守护线程
1.1.1 概述
守护线程 又叫兜底线程
每个程序运行当中,都会默认开启一个守护线程,用于监听我们正常的程序
简单来说,就是没有任何一个线程的时候,JVM就需要退出了,这个时候守护线程也会退出,主要完成垃圾回收等功能
但是 我们可以使用Thread.setDameon() 方法 把某个线程设置为守护线程
但是必须在启动 static之前,否则报错
1.1.2 使用
package com.demo._Tread;
/**
* 守护线程 又叫兜底线程
*
* 每个程序运行当中,都会默认开启一个守护线程,用于监听我们正常的程序
*
* 简单来说,就是没有任何一个线程的时候,JVM就需要退出了,这个时候守护线程也会退出,主要完成垃圾回收等功能
*
* 但是 我们可以使用Thread.setDameon() 方法 把某个线程设置为守护线程
*
* 但是必须在启动 start()之前设置,否则报错
*
*/
public class Thread_01_Daemon {
public static void main(String[] args) {
Thread t1 = new Processor_01();
Thread t2 = new Processor_01();
t1.setName("t1");
t2.setName("t2");
// 设置为守护线程
t1.setDaemon(true);
t1.start();
// /*Exception in thread "main" java.lang.IllegalThreadStateException*/
// t1.setDaemon(true);
t2.start();
for (int i = 0; i < 10; i++) {
System.out.println("main-->" + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Processor_01 extends Thread {
@Override
public void run() {
int i = 0;
// 死循环
while (true) {
System.out.println(getName() + " = " + i++);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.2 Timer
1.2.1 概述
定时器 计划任务,只要有一个任务监听 就会是一个线程
1 执行任务的类 , 2 执行任务起始时间 3 执行任务间隔时间
1.2.2 使用
package com.demo._Tread;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* 定时器 计划任务,只要有一个任务监听 就会是一个线程
*
* 1 执行任务的类 , 2 执行任务起始时间 3 执行任务间隔时间
*
*/
public class Thread_02_Timer {
public static void main(String[] args) throws ParseException {
// 1 创建定时器
Timer t = new Timer();
// 执行任务对象
LogTimerTask logTimerTask = new LogTimerTask();
// 指定起始时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
// 1 任务 , 2 起始时间 , 3 间隔时间 (毫秒)
t.schedule(logTimerTask, sdf.parse("2021-01-30 11:04:00 000"), 1000 * 3);
}
}
// 任务类
class LogTimerTask extends TimerTask {
@Override
public void run() {
// 日期格式化对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
// 当前系统时间
Date date = new Date();
// 格式化输出
System.out.println(sdf.format(date));
/**
* 2021-01-30 11:04:00 000
2021-01-30 11:04:03 001
2021-01-30 11:04:06 001
*/
}
}
1.3 死锁
1.3.1 锁相关知识
如果访问了一个对象中加锁的成员方法,那么该对象中所有的加锁的成员方法全部锁定,都不能被其他线程访问
但是和静态无关,和其他对象也无关
如果访问了一个类加锁的静态方法,那么该类中所有的加锁的静态方法都被锁定,不能访问
但是和成员无关
package com.demo._Tread;
/**
* 如果访问了一个对象中加锁的成员方法,那么该对象中所有的加锁的成员方法全部锁定,都不能被其他线程访问
*
* 但是和静态无关,和其他对象也无关
*
* 如果访问了一个类加锁的静态方法,那么该类中所有的加锁的静态方法都被锁定,不能访问
*
* 但是和成员无关
*
*/
public class Thread_03_Synchronized {
public static void main(String[] args) {
MyClass_01 my = new MyClass_01();
Thread t1 = new Thread(new Processor_03(my));
Thread t2 = new Thread(new Processor_03(my));
Thread t3 = new Thread(new Processor_03(my));
t1.setName("t1");
t2.setName("t2");
t3.setName("t3");
t1.start();
// 睡眠一小会,保证让t1先执行,先进入加锁m1方法,并睡眠5秒
try {
Thread.sleep(500);//main线程先睡0.5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 因为t1进入m1后就加锁,并睡眠,查看t2和t3是否可以进入m2和m3,其中m2加锁,m3未加锁
t2.start();
t3.start();
}
}
class Processor_03 implements Runnable {
MyClass_01 my;
public Processor_03(MyClass_01 my) {
super();
this.my = my;
}
@Override
public void run() {
if ("t1".equals(Thread.currentThread().getName())) {
my.m1();
} else if ("t2".equals(Thread.currentThread().getName())) {
my.m2();
} else if ("t3".equals(Thread.currentThread().getName())) {
my.m3();
}
}
}
// 业务类
class MyClass_01 {
public synchronized void m1() {
System.out.println("加锁成员方法m1,即将进入睡眠");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void m2() {
System.out.println("加锁成员方法m2");
}
public void m3() {
System.out.println("未加锁成员方法m3");
}
}
1.3.2 概述
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃
自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续
解决方法
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
死锁 : 就是大家在执行过程中,都遇到对方进入加锁方法中,导致大家都访问不了
原理 :
1 某个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象1
2 另一个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象2
3 在第一个线程中,要去访问对象2的时候,发现被锁定 了,只能等待
3 在第二个线程中,要去访问对象1的时候,发现被锁定了,只能等待
代码块锁
synchronized(xxx){} 代码块锁,可以锁类,也可以锁对象
如果锁对象,当访问该代码块锁的时候,该对象中所有的代码块锁和加锁的成员方法都被锁定
同理 访问对象中加锁的成员方法的时候,代码块锁也会被锁定
如果是锁类,当访问该代码块的时候,该类中所有的代码块锁和加锁的静态方法都被锁定
同理 访问类中加锁的静态方法的时候,代码块锁也会被锁定
1.3.3 代码实现
package com.demo._Tread;
/**
* 死锁 : 就是大家在执行过程中,都遇到对方进入加锁方法中,导致大家都访问不了
*
* 原理 :
* 1 某个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象1
* 2 另一个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象2
* 3 在第一个线程中,要去访问对象2的时候,发现被锁定 了,只能等待
* 3 在第二个线程中,要去访问对象1的时候,发现被锁定了,只能等待
*
* 代码块锁
* synchronized(xxx){} 代码块锁,可以锁类,也可以锁对象
* 如果锁对象,当访问该代码块锁的时候,该对象中所有的代码块锁和加锁的成员方法都被锁定
* 同理 访问对象中加锁的成员方法的时候,代码块锁也会被锁定
*
* 如果是锁类,当访问该代码块的时候,该类中所有的代码块锁和加锁的静态方法都被锁定
* 同理 访问类中加锁的静态方法的时候,代码块锁也会被锁定
*
*
* 实现思路 :
* 1 两个线程
* 2 两个对象
* 3 两个线程中 保存的两个对象是相同的
* 4 线程1 先访问对象1 再嵌套访问对象2
* 5 线程2 先访问对象2 再嵌套访问对象1
*/
public class Thread_04_DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new Thread(new T1(o1,o2));
Thread t2 = new Thread(new T2(o1,o2));
t1.start();
t2.start();
}
}
class T1 extends Thread{
Object o1 ;
Object o2;
public T1(Object o1, Object o2) {
super();
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1) {
try {
// 加入睡眠 保证一定会死锁,因为到这里先确保让t2把o2锁定
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("T1执行完成");
}
}
}
}
class T2 extends Thread{
Object o1 ;
Object o2;
public T2(Object o1, Object o2) {
super();
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2) {
try {
// 加入睡眠 保证一定会死锁,因为到这里先确保让t1把o1锁定
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("T2执行完成");
}
}
}
}
1.4 线程通信
1.4.1 概述
wait() 与 notify() 和 notifyAll()
a)wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
b)notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
c)notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
注意:这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报
java.lang.IllegalMonitorStateException异常
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声
Object 中的方法
wait() : 该线程进入等待状态,功能还在,只是没有运行,当被唤醒之后进入就绪状态,当执行的时候接着当前等待的状态继续执行
notify() : 唤醒该对象上等待中的某一个线程(优先级)
notifyAll() : 唤醒该对象上所有等待的线程
必须用在成员方法中,因为是对象相关,并且该成员方法必须加锁(synchronized)
wait : 无参 或者是传入 0 ,说明不会自动醒,只能被notifyAll/notify唤醒
也可以传入long类型的值,单位是毫秒数,过了指定时间之后自动醒
注意 sleep是Thread类中的静态方法,睡眠指定时间,不会交出锁(其他线程依旧不能交出该方法)
而 wait是Object中的成员方法,也就是每个对象都有的方法,挂起,会交出锁(其他线程就可以访问该方法了)
1.4.2 使用方式
package com.demo._Tread;
/**
* Object 中的方法
*
* wait() : 该线程进入等待状态,功能还在,只是没有运行,当被唤醒之后进入就绪状态,当执行的时候接着当前等待的状态继续执行
*
* notify() : 唤醒该对象上等待中的某一个线程(优先级)
*
* notifyAll() : 唤醒该对象上所有等待的线程
*
* 必须用在成员方法中,因为是对象相关,并且该成员方法必须加锁(synchronized)
*
* wait : 无参 或者是传入 0 ,说明不会自动醒,只能被notifyAll/notify唤醒
*
* 也可以传入long类型的值,单位是毫秒数,过了指定时间之后自动醒
*
* 注意 sleep是Thread类中的静态方法,睡眠指定时间,不会交出锁(其他线程依旧不能交出该方法)
*
* 而 wait是Object中的成员方法,也就是每个对象都有的方法,挂起,会交出锁(其他线程就可以访问该方法了)
*
* 以打印奇数偶数为例
* 1 有一个业务类 Num ,其中有一个成员变量 count
* 2 业务类提供打印奇数和偶数的方法
* 1 奇数 printOdd : 打印奇数, count++ , 变成偶数,唤醒其他线程,自身进入wait等待
* 2 偶数 printEven : 打印偶数 count++ , 变成奇数,唤醒其他线程,自身进入wait等待
* 3 两个线程,保存同一个Num对象,分别调用 printOdd和printEven
*
*/
public class Thread_05_Wait {
public static void main(String[] args) {
Num num = new Num(1);
Thread t1 = new PrintOdd(num);
Thread t2 = new Thread(new PrintEven(num));
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
// 打印奇数
class PrintOdd extends Thread{
Num num;
public PrintOdd(Num num) {
super();
this.num = num;
}
@Override
public void run() {
while (true) {
num.printOdd();
}
}
}
class PrintEven implements Runnable{
Num num;
public PrintEven(Num num) {
super();
this.num = num;
}
@Override
public void run() {
while (true) {
num.printEven();
}
}
}
// 打印偶数
// 业务类
class Num{
int count ;
public Num(int count) {
super();
this.count = count;
}
public synchronized void printOdd(){
System.out.println(Thread.currentThread().getName()+"--->"+count);
count++;
// 唤醒其他线程
this.notifyAll();
// 自己进入挂起等待
try {
// 加入睡眠,效果更直观
Thread.sleep(500);
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void printEven(){
System.out.println(Thread.currentThread().getName()+"--->"+count);
count++;
// 唤醒其他线程
this.notifyAll();
// 自己进入挂起等待
try {
// 加入睡眠,效果更直观
Thread.sleep(500);
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1.4.3 面试题之生产者与消费者
思路 :
1 有一个业务类,SynStack 其中有一个成员变量 cnt 用来保存生产的个数,还需要一个容器来存储生产的数据char[]
2 业务类需要有对应的生产方法和消费方法
1 生产 push : 向数组中添加元素,需要判断数组是否满了,如果满了 就不生产,唤醒消费者
2 消费 pop : 同上,判断使用没有数据,如果没有 就唤醒生产者
3 两个线程分别调用生产和消费
wait 和 notify
package com.demo._Tread;
/**
* 比较经典的面试题 : 生产者和消费者
*
* 思路 :
* 1 有一个业务类,SynStack 其中有一个成员变量 cnt 用来保存生产的个数,还需要一个容器来存储生产的数据char[]
* 2 业务类需要有对应的生产方法和消费方法
* 1 生产 push : 向数组中添加元素,需要判断数组是否满了,如果满了 就不生产,唤醒消费者
* 2 消费 pop : 同上,判断使用没有数据,如果没有 就唤醒生产者
* 3 两个线程分别调用生产和消费
*
* wait 和 notify
*/
public class Thread_06_ProducerConsumer {
public static void main(String[] args) {
SynStack s = new SynStack();
Thread t1 = new Thread(new Consumer(s));
Thread t2 = new Thread(new Producer(s));
t1.start();
t2.start();
}
}
// 消费线程类
class Consumer implements Runnable{
SynStack s;
public Consumer(SynStack s) {
super();
this.s = s;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
s.pop();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 生产线程类
class Producer implements Runnable{
SynStack s;
public Producer(SynStack s) {
super();
this.s = s;
}
@Override
public void run() {
char ch;
for (int i = 0; i < 20; i++) {
ch = (char)('a'+i);
s.push(ch);
}
}
}
// 业务类
class SynStack{
// 保存以生产的个数.同时也是下一个元素的下标
int cnt = 0;
char[] data = new char[6];
public synchronized void push(char ch){
// 生产个数如果和容器大小一致,说明生产满了,就不再生产
if (cnt == data.length) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 先唤醒消费者 提醒消费
notifyAll();
// 把元素保存到数组中
data[cnt] = ch;
// 个数+1
cnt++;
System.out.println("生产了 : "+ch+" , 容器中剩余 "+cnt+" 个字符");
}
public synchronized char pop() {
// 生产个数如果是0 就不进行消费
if (cnt == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 先唤醒生产者提醒生产
notifyAll();
// 先 -- 再取出,因为最后一个元素为个数-1 , 而 cnt是个数,所以要先--
cnt--;
char ch = data[cnt];
System.out.println("消费了 : " + ch + " , 容器中剩余 " + cnt + " 个字符");
return ch;
}
}
1.5 单例模式
1.5.1 概述
单例模式目的 : 让一个类只创建一个对象
根据对象的创建时机不同,分为两种
懒汉模式 : 第一次使用的时候创建对象
饿汉模式 : 类加载的时候创建对象
实现步骤 :
1 构造方法私有化
2 公共的静态方法用于获取对象
3 私有化静态变量存储创建后的对象
1.5.2 单例模式编码
package com.demo.Singleton;
public class SingLethon_01 {
//创建私有的构造方法
private SingLethon_01() {
}
//创建私有静态变量,储存静态方法
private static SingLethon_01 s = null;
//创建公共静态方法,并返回地址
public static SingLethon_01 getInstance() {
if (s == null) {
synchronized (SingLethon_01.class) {
if (s == null) {
s = new SingLethon_01();
}
}
}
return s;
}
}
package com.demo.Singleton;
/**
* 单例模式目的 : 让一个类只创建一个对象
*
* 根据对象的创建时机不同,分为两种
* 懒汉模式 : 第一次使用的时候创建对象
* 饿汉模式 : 类加载的时候创建对象
*
* 实现步骤 :
* 1 构造方法私有化
* 2 公共的静态方法用于获取对象
* 3 私有化静态变量存储创建后的对象
*
*/
public class Test {
public static void main(String[] args) {
// System.out.println(SingLethon_01.getInstance());
// System.out.println(SingLethon_01.getInstance());
// System.out.println(SingLethon_01.getInstance());
// System.out.println(SingLethon_01.getInstance());
Thread t1 = new AA();
Thread t2 = new AA();
Thread t3 = new AA();
Thread t4 = new AA();
Thread t5 = new AA();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class AA extends Thread{
@Override
public void run() {
System.out.println(SingLethon_01.getInstance());
// com.demo.Singleton.SingLethon_01@f5c02ee
// com.demo.Singleton.SingLethon_01@f5c02ee
// com.demo.Singleton.SingLethon_01@f5c02ee
// com.demo.Singleton.SingLethon_01@f5c02ee
// com.demo.Singleton.SingLethon_01@f5c02ee
}
}
1.5.3 问题-多线程环境下不行
1.5.3.1 分析原因
package com.demo.Singleton;
/**
* 单例模式目的 : 让一个类只创建一个对象
*
* 根据对象的创建时机不同,分为两种
* 懒汉模式 : 第一次使用的时候创建对象
* 饿汉模式 : 类加载的时候创建对象
*
* 实现步骤 :
* 1 构造方法私有化
* 2 公共的静态方法用于获取对象
* 3 私有化静态变量存储创建后的对象
*
*/
public class Test {
public static void main(String[] args) {
// System.out.println(SingLethon_01.getInstance());
// System.out.println(SingLethon_01.getInstance());
// System.out.println(SingLethon_01.getInstance());
// System.out.println(SingLethon_01.getInstance());
Thread t1 = new AA();
Thread t2 = new AA();
Thread t3 = new AA();
Thread t4 = new AA();
Thread t5 = new AA();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class AA extends Thread{
@Override
public void run() {
System.out.println(SingLethon_01.getInstance());
//com.demo.Singleton.SingLethon_01@118f2161
//com.demo.Singleton.SingLethon_01@118f2161
//com.demo.Singleton.SingLethon_01@118f2161
//com.demo.Singleton.SingLethon_01@f5c02ee
//com.demo.Singleton.SingLethon_01@1ec599cd
}
}
package com.demo.Singleton;
public class SingLethon_01 {
//创建私有的构造方法
private SingLethon_01() {
}
//创建私有静态变量,储存静态方法
private static SingLethon_01 s = null;
//创建公共静态方法,并返回地址
public static SingLethon_01 getInstance() {
if (s == null) {//可能有多个线程同时执行这个判断,判断s的时候为null,所以出现多次创建对象
s = new SingLethon_01();
}
return s;
}
}
1.5.3.2 解决方案1
package com.demo.Singleton;
public class SingLethon_01 {
//创建私有的构造方法
private SingLethon_01() {
}
//创建私有静态变量,储存静态方法
private static SingLethon_01 s = null;
//创建公共静态方法,并返回地址
public synchronized static SingLethon_01 getInstance() {
if (s == null) {
s = new SingLethon_01();
}
return s;
}
}
//com.demo.Singleton.SingLethon_01@f5c02ee
//com.demo.Singleton.SingLethon_01@f5c02ee
//com.demo.Singleton.SingLethon_01@f5c02ee
//com.demo.Singleton.SingLethon_01@f5c02ee
//com.demo.Singleton.SingLethon_01@f5c02ee
加锁 可以解决,一定不会出现问题
但是会有新的问题,
没有加锁前,出现问题,是因为,第一次执行的时候,多个线程并行执行到这个判断了
因为第一次执行,s是null,没有对象,所以导致创建多个对象
但是 一旦跳过第一次,后续不管多少个并发/并行 s都不再等于null,就不会再创建
而我们如果使用synchronized修饰方法的话,虽然结果一定没有问题,但是效率降低太多了
因为不仅仅是第一次,任何时候只要想来获取对象,都需要排队等待
而 我们这个程序中,其实只需要保证第一次排队等待即可,一旦创建了对象之后,则不需要排队,即使在这时候有很多并行线程同时执行,判断s==null的时候 也是false,因为有对象了
所以 以上编码 , 不是最佳选择
1.5.3.3 解决方案2
package com.demo.Singleton;
public class SingLethon_01 {
//创建私有的构造方法
private SingLethon_01() {
}
//创建私有静态变量,储存静态方法
private static SingLethon_01 s = null;
//创建公共静态方法,并返回地址
public static SingLethon_01 getInstance() {
if (s == null) {
synchronized (SingLethon_01.class) {
if (s == null) {
s = new SingLethon_01();
}
}
}
return s;
}
}
//com.demo.Singleton.SingLethon_01@586f4a16
//com.demo.Singleton.SingLethon_01@586f4a16
//com.demo.Singleton.SingLethon_01@586f4a16
//com.demo.Singleton.SingLethon_01@586f4a16
//com.demo.Singleton.SingLethon_01@586f4a16
这样的话,只有第一次请求需要排队,一会就算有很多线程同时执行这个方法,也不会排队等待,因为方法没有加锁,多个线程 可以同时进来执行该方法
另外s已经在第一次请求的时候赋值过了,所以判断s==null时 也是false
所以 这种写法才是多线程环境下的最佳选择
1.6 线程池
1.6.1 概述
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。
好处:
a)提高响应速度(减少了创建新线程的时间)
b)降低资源消耗(重复利用线程池中线程,不需要每次都创建)
c)便于线程管理
i.corePoolSize:核心池的大小
ii.maximumPoolSize:最大线程数
iii.keepAliveTime:线程没有任务时最多保持多长时间后会终止
线程池的作用 :
线程池作用就是限制系统中执行线程的数量
根据系统的环境情况,可以自动或者手动来设置线程数量,以达到运行的最佳效果
少了浪费系统资源,多了造成系统拥挤效率不高
用线程池控制线程数量,其他线程排队等候
一个任务 执行完成,再从队列中取最前面的任务开始执行
如果队列中没有等待进程,线程池的这个资源处于等待状态
当一个新任务需要运行时,如果此时线程池中还有等待中的工作线程时,可以直接开始运行
否则需要进入等待队列
为什么要使用线程池
1 减少了创建 和销毁线程的次数,因为每个工作线程都可以被重复使用,可执行多个任务
2 可以根据系统的承受能力,调整线程池中的线程数量,防止因为消耗过多的内存,导致服务器死机
(每个线程需要大概1MB内存,线程开的越多,消耗内存越大,最后导致死机)
1.6.2使用方式
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
1.6.2.1 NewCachedThreadPool
创建一个可根据需要创建新线程的线程池
// 创建一个可缓存线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程
// 若没有可以回收的,则新建线程,线程池规模不存在限制,数量不固定
package com.demo.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池的作用 :
*
* 线程池作用就是限制系统中执行线程的数量
* 根据系统的环境情况,可以自动或者手动来设置线程数量,以达到运行的最佳效果
* 少了浪费系统资源,多了造成系统拥挤效率不高
* 用线程池控制线程数量,其他线程排队等候
*
* 一个任务 执行完成,再从队列中取最前面的任务开始执行
* 如果队列中没有等待进程,线程池的这个资源处于等待状态
* 当一个新任务需要运行时,如果此时线程池中还有等待中的工作线程时,可以直接开始运行
* 否则需要进入等待队列
*
* 为什么要使用线程池
* 1 减少了创建 和销毁线程的次数,因为每个工作线程都可以被重复使用,可执行多个任务
* 2 可以根据系统的承受能力,调整线程池中的线程数量,防止因为消耗过多的内存,导致服务器死机
* (每个线程需要大概1MB内存,线程开的越多,消耗内存越大,最后导致死机)
*/
public class _01_NewCachedThreadPoolTest {
public static void main(String[] args) {
// 创建一个可缓存线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程
// 若没有可以回收的,则新建线程,线程池规模不存在限制,数量不固定
// 创建池子
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
// 执行任务,传入了Runnable的匿名内部类,也可以是实现类对象
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
});
}
System.out.println("----------");
// 关闭线程池
cachedThreadPool.shutdown();
}
}
1.6.2.2 NewFixedThreadPool
创建一个固定 长度线程池,可控制线程最大并发数
超出此数量的线程,会在队列中等待
package com.demo.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 创建一个固定 长度线程池,可控制线程最大并发数
*
* 超出此数量的线程,会在队列中等待
*/
public class _02_NewFixedThreadPoolTest {
public static void main(String[] args) {
// 创建池子,并指定最高 并发3个
//如果创建10线程池,就是并行了
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
// 执行任务,传入了Runnable的匿名内部类,也可以是实现类对象
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
});
}
// 关闭线程池
fixedThreadPool.shutdown();
}
}
1.6.2.3 NewScheduledThreadPool
创建一个固定长度线程池,支持定时及周期性执行任务
package com.demo.pool;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 创建一个固定长度线程池,支持定时及周期性执行任务
*
*/
public class _03_NewScheduledThreadPoolTest {
public static void main(String[] args) {
// test1();
test2();
}
/**
* 延迟执行
*/
public static void test1() {
// 创建池子,并指定数量5
ScheduledExecutorService scheduledThreadPool = Executors
.newScheduledThreadPool(5);
// 执行
// 1 执行的任务, 2 延迟时间 , 3 延迟时间的单位
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("延迟3秒再执行");
}
}, 3, TimeUnit.SECONDS);
// 关闭池子
scheduledThreadPool.shutdown();
}
/**
* 延迟并间隔执行
*/
public static void test2() {
// 创建池子,并指定数量5
ScheduledExecutorService scheduledThreadPool = Executors
.newScheduledThreadPool(5);
// 执行
// 1 执行的任务, 2 延迟时间 , 3, 时间间隔 , 4 延迟时间的单位
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("延迟3秒再执行,并且每2秒执行一次");
}
}, 3, 2, TimeUnit.SECONDS);
}
}
1.6.2.4 NewSingleThreadExcutor
单线程线程池,只创建一个线程,如果这个 线程因为异常结束,那么会有一个新的线程来替代他
该线程保证所有的任务的执行顺序,按照任务的提交顺序执行,谁先来谁先执行
适用于一个一个任务执行的情况
package com.demo.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 单线程线程池,只创建一个线程,如果这个 线程因为异常结束,那么会有一个新的线程来替代他
*
* 该线程保证所有的任务的执行顺序,按照任务的提交顺序执行,谁先来谁先执行
*
* 适用于一个一个任务执行的情况
*
*/
public class _04_NewSingletThreadExecutor {
public static void main(String[] args) {
// 创建池子
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 一次性创建10个请求
for (int i = 0; i < 10; i++) {
final int index = i;
System.out.println("创建 "+i+" 次");
// 只能依次执行
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" : "+index);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}