一、基本概念
1.1 程序、进程、线程
- 程序(programm):一组指令的集合、一段静态的代码
- 进程(process):正在运行的一个程序
- 进程作为资源分配的单位
- 系统在运行时会为每个进程分配不同的内存区域
- 线程(thread):一个程序内部的一条执行路径
- 线程作为调度和执行的单位
- 每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间(方法区、堆),它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效
- 多个线程操作共享的系统资源可能就会带来安全的隐患
1.2 多线程
- 一个Java应用程序java.exe,其实至少三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。如果发生异常,会影响主线程。
-
- 单核CPU:假的多线程,一个时间单元内,执行一个线程的任务
- 多核CPU:更好的发挥多线程的效率
-
- 并行:多个CPU同时执行多个任务
- 并发:一个CPU同一时间段执行多个任务
- 多线程程序的优点:
- 提高应用程序的响应,对图形化界面更有意义,可增强用户体验
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
- 何时需要多线程:
- 程序需要同时执行两个或多个任务
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等
- 需要一些后台运行的程序时
1.3 线程的分类
- 守护线程
- 守护线程是用来服务用户线程的
- 通过在start()方法前调用
thread.setDaemon(true)
可以把一个用户线程变成一个守护线程 - Java垃圾回收就是一个典型的守护线程
- 若JVM中都是守护线程,当前JVM将退出
- 用户线程
- 当存在任何一个用户线程未离开,JVM是不会退出的
二、线程的创建和使用
2.1 Thread类
2.1.1 构造器
- Java语言的JVM允许程序运行多个线程,它通过
java.lang.Thread
类来体现 Thread
类构造器:Thread()
:创建新的Thread对象Thread(String threadname)
:创建线程并指定线程实例名Thread(Runnable target)
:指定创建线程的目标对象,它实现了Runnable接口中的run方法Thread(Runnable target, String name)
:创建新的Thread对象
2.1.2 方法
Thread
类方法:void start()
: 启动线程,并执行对象的run()
方法run()
: 线程在被调度时执行的操作String getName()
: 返回线程的名称void setName(String name)
:设置该线程名称static Thread.currentThread()
: 返回当前线程。在Thread
子类中就是this
,通常用于主线程和Runnable
实现类static void yield()
:线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法join()
:当线程a调用线程b的join()
,a进入阻塞态,直到b完全执行完,a才结束阻塞态static void sleep(long millis)
:(指定时间:毫秒)令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队stop()
:已过时,强制线程生命期结束,不推荐使用boolean isAlive()
:判断线程是否还活着
2.1.3 线程调度、优先级
- 调度策略:
- 时间片
- 抢占式
- Java的调度方法:
- 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
- 对高优先级,使用优先调度的抢占式策略
- 线程的优先级等级:
MAX_PRIORITY
:10MIN _PRIORITY
:1NORM_PRIORITY
:5(默认)
- 涉及的方法
getPriority()
:返回线程优先值setPriority(int newPriority)
:改变线程的优先级
- 说明
- 线程创建时继承父线程的优先级
- 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
2.2 方式一:继承Thread类的方式
-
步骤:
- 1.创建一个继承于
Thread
类的子类 - 2.重写
Thread
类的run()
:将此线程执行的操作声明在run()
中,run()
方法的主体称为线程体 - 3.创建
Thread
类的子类的对象 - 4.通过此对象调用
start()
而非直接调用run()
:①启动当前线程 ② 调用当前线程的run()
- 5.如果再启动一个线程,必须重新创建一个
Thread
子类的对象,调用此对象的start()
package com.lsy.test; /** * 两个线程,一个遍历100以内的偶数, * 另一个遍历100以内的奇数 * * @author lsssy * @create 2020-04-15 19:46 */ public class ThreadTest { public static void main(String[] args) { EvenThread t1 = new EvenThread(); OddThread t2 = new OddThread(); t1.start(); t2.start(); } } class EvenThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(i + "*************"); } } } } class OddThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 != 0) { System.out.println(i + "##############"); } } } }
- 1.创建一个继承于
-
Thread的匿名子类
package com.lsy.test; /** * 打印100以内的偶数 * @author lsssy * @create 2020-04-15 20:21 */ public class ThreadTest { public static void main(String[] args) { // 创建Thread的匿名子类 new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(i + "*************"); } } } }.start(); } }
2.3 方式二:实现Runnable接口的方式
-
步骤:
- 1.创建一个实现了
Runnable
接口的类 - 2.实现类去实现
Runnable
中的抽象方法:run()
- 3.创建实现类的对象
- 4.将此对象作为参数传递到
Thread
类的构造器中,创建Thread
类的对象
- 5.通过
Thread
类的对象调用start()
/** * 遍历100以内奇数 * @author lsssy * @create 2020-04-15 21:47 */ public class ThreadTest { public static void main(String[] args) { OddThread2 o1 = new OddThread2(); Thread t1 = new Thread(o1); Thread t2 = new Thread(o1); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start(); } } class OddThread2 implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i%2!=0){ System.out.println(Thread.currentThread().getName()+": "+i); } } } }
- 1.创建一个实现了
-
匿名实现runnable接口
/** * 打印100以内的偶数 * @author lsssy * @create 2020-04-15 20:21 */ public class ThreadTest { public static void main(String[] args) { // 创建Thread的匿名子类 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(i + "*************"); } } } }).start(); } }
2.4 方式一、二的比较
- 开发中:优先选择实现
Runnable
接口的方式 - 原因:
- 实现的方式没有类的单继承性的局限性
- 实现的方式更适合来处理多个线程共享数据的情况
- 区别
- 继承
Thread
:线程代码存放Thread子类的run方法中 - 实现
Runnable
:线程代码存在接口的子类的run方法中
- 继承
三、线程的生命周期
3.1 Thread.State类(线程的五状态)
- 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU 并临时中止自己的执行,进入阻塞状态
- 死亡(终止):线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
四、线程的同步
4.1 同步机制
- 例子:三个卖票窗口,共卖100张
- 1.线程安全问题:出现重票、错票(0、-1)
- 2.原因:卖票操作(对共享数据的操作)未完成,其他线程参与进来,导致共享数据出错
- 3.解决:线程a卖完票(操作完共享数据),其他线程才可以操作ticket,即使线程a阻塞,也如此
- 4.解决方式:同步机制
4.1.1 同步代码块
synchronized(同步监视器){
// 需要被同步的代码
}
- 说明:
- 需要被同步的代码:操作共享数据的代码,不能包多也不能包少
- 共享数据:多个线程共同操作的变量
- 同步监视器:锁,任何一个类的对象;要求多个线程共用一把锁(注意:继承Thread类方式中锁(类对象)要声明为
static
)
4.1.1.1 实现Runnable接口方式
package com.lsy.java;
/**
* 例子:三个卖票窗口,共卖100张,用Runnable接口实现多线程
* @author lsssy
* @create 2020-04-15 22:05
*/
public class WindowTest2 {
public static void main(String[] args) {
Window2 w1 = new Window2();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window2 implements Runnable {
private int ticket = 100; // 共享数据
//Object obj = new Object(); // 锁
@Override
public void run() {
while (true) {
synchronized(this){ // this: 唯一的Window2对象w1
//synchronized (obj) { // 设置同步代码块
if (ticket > 0) {
try {
Thread.sleep(100); // 测试用,增加出现问题的可能
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
4.1.1.2 继承Thread类方式
package com.lsy.java;
/**
* 三个窗口卖票,一共100张
*
* @author lsssy
* @create 2020-04-15 21:32
*/
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread {
private static int ticket = 100; // static让三个窗口共享一个静态变量
// private static Object obj = new Object(); // static让三个窗口共享一个obj
@Override
public void run() {
while (true) {
synchronized (Window.class){ // Class clazz = Window.class 类可作为对象存在,且只加载一次
//synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ": 买票,票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
4.1.2 同步方法
public synchronized void show (String name){
// 需要被同步的代码
}
- 操作共享数据的代码完整的声明在一个方法中,将该方法声明为同步的
- 需要同步监视器,不需显示声明
- 非静态的同步方法的锁:this
- 静态的同步方法的锁:当前类本身
4.1.2.1 实现Runnable接口方式
package com.lsy.java;
/**
* 例子:三个卖票窗口,共卖100张,用Runnable接口实现多线程
* @author lsssy
* @create 2020-04-15 22:05
*/
public class WindowTest3 {
public static void main(String[] args) {
Window3 w1 = new Window3();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window3 implements Runnable {
private int ticket = 100;
private boolean flag = true;
@Override
public void run() {
while (flag) {
show();
}
}
private synchronized void show() { // 同步监视器:this
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 买票,票号为" + ticket);
ticket--;
}else{
flag = false;
}
}
}
4.1.2.2 继承Thread类方式
package com.lsy.java;
/**
* 例子:三个卖票窗口,共卖100张,用继承Thread类方式实现多线程
*
* @author lsssy
* @create 2020-04-15 21:32
*/
public class WindowTest4 {
public static void main(String[] args) {
Window4 w1 = new Window4();
Window4 w2 = new Window4();
Window4 w3 = new Window4();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
class Window4 extends Thread {
private static int ticket = 100; // 注意static让三个窗口共享一个静态变量
private static boolean flag = true;
@Override
public void run() {
while (flag) {
show();
}
}
private static synchronized void show() { // 方法为静态的,同步监视器:Windows4.class
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 买票,票号为" + ticket);
ticket--;
} else {
flag = false;
}
}
}
4.1.3 Lock锁(JDK5.0新增)
class A{
private final ReentrantLock lock = new ReenTrantLock(); // 加final保证lock对几个同步的线程是唯一的
public void m(){
lock.lock(); // 上锁
try{
//保证线程安全的代码;
}
finally{
lock.unlock(); // 解锁
}
}
}
- 通过显式定义同步锁对象来实现同步
- 同步锁使用
Lock
对象充当 java.util.concurrent.locks.Lock
接口是控制多个线程对共享资源进行访问的工具- 锁提供了对共享资源的独占访问,每次只能有一个线程对
Lock
对象加锁,线程开始访问共享资源之前应先获得Lock
对象 ReentrantLock
类实现了Lock
,它拥有与synchronized
相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock
,可以显式加锁、释放锁
package com.lsy.exer;
import java.util.concurrent.locks.ReentrantLock;
/**
* 例子:有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
* @author lsssy
* @create 2020-04-18 20:45
*/
public class AccountTest {
public static void main(String[] args) {
Account acct = new Account(0); // 实现共享
Customer c1 = new Customer(acct);
Customer c2 = new Customer(acct);
c1.setName("甲");
c2.setName("已");
c1.start();
c2.start();
}
}
class Account{
private double balance;
private final ReentrantLock lock = new ReentrantLock();
public Account(double balance) {
this.balance = balance;
}
public void deposit(double amt){
lock.lock(); // 解决线程安全问题
try {
if(amt>0){
balance += amt;
try {
Thread.sleep(1000); // 用于测试,提供出问题的概率
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"存钱成功,余额:"+balance);
}
}
finally {
lock.unlock(); // 如果同步代码有异常,要将unlock()写入finally语句块
}
}
}
class Customer extends Thread{ // 多线程,继承Thread类
private Account acct;
public Customer(Account acct) { // 实现共享
this.acct = acct;
}
@Override
public void run() { // 线程主体
for (int i=0;i<3;i++){
acct.deposit(1000);
}
}
}
4.1.3.1 synchronized 与 Lock 的对比
synchronized | Lock |
---|---|
Lock 是 显式锁,手动开启和关闭锁 | synchronized 是 隐式锁,出了作用域自动释放 |
有代码块锁和方法锁 | 只有代码块锁 |
4.1.4 优先使用顺序
- 1.Lock
- 优先使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)
- 2.同步代码块(已经进入了方法体,分配了相应资源)
- 3.同步方法(在方法体之外)
4.1.5 同步的范围
- 如何找问题,即代码是否存在线程安全:
- 1.明确哪些代码是多线程运行的代码
- 2.明确多个线程是否有共享数据
- 3.明确多线程运行代码中是否有多条语句操作共享数据
- 如何解决:
- 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行
- 即所有操作共享数据的这些语句都要放在同步范围中
- 切忌:
- 范围太小:没锁住所有有安全问题的代码
- 范围太大:没发挥多线程的功能
4.2 线程安全的单例模式(懒汉式)
-
方式一:效率较差
class Bank{ private Bank(){} private static Bank instance = null; public static Bank getInstance(){ //public static synchronized Bank getInstance(){ // 或者直接加synchronized synchronized (Bank.class) { if (instance==null){ instance = new Bank(); } } return instance; } }
-
方式二:效率高,没抢到单例的不用傻等着
class Bank { private Bank() { } private static Bank instance = null; public static Bank getInstance() { if (instance == null) { // 优化! synchronized (Bank.class) { if (instance == null) { instance = new Bank(); } } } return instance; } }
4.3 死锁
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所的线程都处于阻塞状态,无法继续
- 使用同步时,要避免出现死锁:
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
public class lock1 {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread() {
@Override
public void run() {
synchronized (s1) {
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
五、线程的通信
5.1 wait、notify、notifyAll
wait()
:令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()
或notifyAll()
方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行notify()
:唤醒正在排队等待同步资源的线程中优先级最高者结束等待notifyAll()
:唤醒正在排队等待资源的所有线程结束等待- 说明:
- 三个方法必须使用在同步代码块或同步方法中,否则会报
java.lang.IllegalMonitorStateException
异常 - 三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现
IllegalMonitorStateException
异常 - 三个方法是定义在
java.lang.Object类
中
- 三个方法必须使用在同步代码块或同步方法中,否则会报
5.2 sleep()和wait()的异同
特点 | sleep() | wait() |
---|---|---|
一旦执行方法,都可以使得当前的线程进入阻塞状态 | ||
两个方法声明的位置不同 | Thread类中声明sleep() | Object类中声明wait() |
调用的要求不同 | sleep()可以在任何需要的场景下调用 | wait()必须使用在同步代码块或同步方法中 |
是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中 | sleep()不会释放锁 | wait()会释放锁 |
5.3 释放锁
- 释放锁的操作:
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到
break
、return
终止了该代码块、 该方法的继续执行 - 当前线程在同步代码块、同步方法中出现了未处理的
Error
或Exception
,导致异常结束 - 当前线程在同步代码块、同步方法中执行了线程对象的
wait()
方法,当前线程暂停,并释放锁
- 不会释放锁的操作:
- 线程执行同步代码块或同步方法时,程序调用
Thread.sleep()
、Thread.yield()
方法暂停当前线程的执行 - 线程执行同步代码块时,其他线程调用了该线程的
suspend()
方法将该线程挂起,该线程不会释放锁- 应尽量避免使用
suspend()
和resume()
来控制线程
- 应尽量避免使用
- 线程执行同步代码块或同步方法时,程序调用
5.4 生产者/消费者问题(线程通信的应用)
package com.lsy.exer;
/**
* 例子:
* 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
* 店员一次只能持有固定数量的产品(比如:20),
* 如果生产者试图生产更多的产品,店员会叫生产者停一下,
* 如果店中有空位放产品了再通知生产者继续生产;
* 如果店中没有产品了,店员会告诉消费者等一下,
* 如果店中有产品了再通知消费者来取走产品。
* 分析:
* 1.多线程问题:生产者、消费者
* 2.共享数据:店员/产品
* 3.如何解决:三个方法
* 4.线程通信:涉及
*
* @author lsssy
* @create 2020-04-18 23:31
*/
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("消费者1");
Consumer c2 = new Consumer(clerk);
c2.setName("消费者2");
p1.start();
c1.start();
c2.start();
}
}
class Clerk{
private int productCount;
public synchronized void produceProduct() { // 同步监视器this,即clerk
if (productCount<20){
productCount++;
System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consumeProduct() { // 同步监视器this,即clerk
if (productCount>0){
System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
productCount--;
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){
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){
clerk.consumeProduct();
}
}
}
六、JDK5.0新增线程创建方式
6.1 实现Callable接口
- 与使用
Runnable
相比,Callable
功能更强大些- 相比
run()
方法,可以有返回值 - 方法可以抛出异常
- 支持泛型的返回值
- 需要借助
FutureTask
类,比如获取返回结果
- 相比
Future
接口- 可以对具体
Runnable
、Callable
任务的执行结果进行取消、查询是否完成、获取结果等 FutrueTask
是Futrue
接口的唯一的实现类FutureTask
同时实现了Runnable
,Future
接口。它既可以作为Runnable
被线程执行,又可以作为Future
得到Callable
的返回值
- 可以对具体
package com.lsy.exer;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author lsssy
* @create 2020-04-19 0:08
*/
public class CallableTest {
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方法的返回值
Object sum = futureTask.get(); // get()返回FutureTask构造器参数Callable实现类重写的call()的返回值
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中,call()有返回值,可 return null;
@Override
public Object call() throws Exception {
int sum=0;
for (int i = 0; i <= 100; i++) {
if (i%2==0){
System.out.println(i);
sum+=i;
}
}
return sum; // int转为Integer,多态
}
}
6.2 使用线程池
- 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
corePoolSize
:核心池的大小maximumPoolSize
:最大线程数keepAliveTime
:线程没任务时最多保持多长时间后会终止
- JDK 5.0起提供了线程池相关API:
ExecutorService
和Executors
ExecutorService
:真正的线程池接口,常见子类ThreadPoolExecutor
void execute(Runnable command)
:执行任务/命令,没有返回值,一般用来执行Runnable
<T> Future<T> submit(Callable<T> task)
:执行任务,有返回值,一般又来执行Callable
void shutdown()
:关闭连接池
Executors
:工具类、线程池的工厂类,用于创建并返回不同类型的线程池Executors.newCachedThreadPool()
:创建一个可根据需要创建新线程的线程池Executors.newFixedThreadPool(n)
; 创建一个可重用固定线程数的线程池Executors.newSingleThreadExecutor()
:创建一个只有一个线程的线程池Executors.newScheduledThreadPool(n)
:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
package com.lsy.exer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author lsssy
* @create 2020-04-19 0:44
*/
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10); // service是接口
//设置线程池的属性
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; // 强转成ThreadPoolExecutor类
// System.out.println(service.getClass()); // class java.util.concurrent.ThreadPoolExecutor
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread1()); // 适合Runnable
service.execute(new NumberThread2()); // 适合Runnable
// service.submit(); // 适合Callable
// 关闭线程池
service.shutdown();
}
}
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);
}
}
}
}
class NumberThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}