多线程
1.相关概念
-
程序(program):为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
-
进程(process):程序的一次执行过程,或是正在内存中运行的应用程序。如:运行中的QQ,运行中的网易音乐播放器。
-
线程(thread):进程可进一步细化为线程,是程序内部的一条执行路径。一个进程中至少有一个线程。
- 进程同一时间若并行执行多个线程,就是支持多线程的。
2.创建和启动线程
2.1 方式1:继承Thread类
Java通过继承Thread类来创建并启动多线程的步骤如下:
- 定义Thread 类的子类,并重写该类的 run() 方法,该 run() 方法的方法体就代表了线程需要完成的任务
- 创建 Thread 子类的实例,即创建了线程对象
- 调用线程对象的 start() 方法来启动该线程
代码如下:
package Thread;
public class ThreadByThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
测试类:
package Thread;
public class ThreadTestByThread {
public static void main(String[] args) {
ThreadByThread t1 = new ThreadByThread();
ThreadByThread t2 = new ThreadByThread();
t1.start();
t2.start();
}
}
注意:
-
如果自己手动调用 run() 方法,那么就只是普通方法,没有启动多线程模式。
-
run() 方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
-
想要启动多线程,必须调用 start 方法。
-
一个线程对象只能调用一次 start() 方法启动,如果重复调用了,则将抛出以上的异常 “ IllegalThreadStateException ” 。
2.2 方式2:实现Runnable接口
步骤如下:
- 定义 Runnable 接口的实现类,并重写该接口的 run() 方法,该 run() 方法的方法体同样是该线程的线程执行体。
- 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 参数来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
- 调用线程对象的 start() 方法,启动线程。调用 Runnable 接口实现类的 run 方法。
代码如下:
package Thread;
public class ThreadByRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
测试类:
package Thread;
public class ThreadTestByRunnable {
public static void main(String[] args) {
ThreadByRunnable r = new ThreadByRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
说明:
Runnable 对象仅仅作为 Thread 对象的 target, Runnable 实现类里包含的
run() 方法仅作为线程执行体。 而实际的线程对象依然是 Thread 实例,只是该
Thread 线程负责执行其 target 的 run() 方法。
2.3 使用匿名内部类对象来实现线程的创建和启动
new Thread("新的线程!"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}.start();
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":" + i);
}
}
}).start();
2.4 对比继承 Thread 类和实现 Runnable 接口两种方式
联系
Thread类实际上也是实现了Runnable接口的类。即:public class Thread extends Object implements Runnable
区别
-
继承Thread:线程代码存放Thread子类run方法中。
-
实现Runnable:线程代码存在接口的子类的run方法。
实现Runnable接口比继承Thread类所具有的优势
-
避免了单继承的局限性。
-
多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
-
增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
3. Thread类的常用结构
3.1 构造器
-
public Thread() :分配一个新的线程对象。
-
public Thread(String name) :分配一个指定名字的新的线程对象。
-
public Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法。
-
public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
3.2 常用方法
-
public void run() :此线程要执行的任务在此处定义代码。
-
public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
-
public String getName() :获取当前线程名称。
-
public void setName(String name):设置该线程名称。
-
public static Thread currentThread() :返回对当前正在执行的线程对象的引用。在Thread子类中就是this,通常用于主线程和Runnable实现类
-
public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
-
public static void yield():yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。
-
public final boolean isAlive():测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。
-
void join() :等待该线程终止。
- void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。
- void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
每个线程都有一定的优先级,同优先级线程组成先进先出队列(先到先服
务),使用分时调度策略。优先级高的线程采用抢占式策略,获得较多的执
行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。
Thread类的三个优先级常量:
MAX_PRIORITY(10):最高优先级
MIN _PRIORITY (1):最低优先级
NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。
-
public final int getPriority() :返回线程优先级
-
public final void setPriority(int newPriority) :改变线程的优先级,范围在[1,10]之间。
4. 多线程的生命周期
4.1 jdk 1.5之前
线程的生命周期有五种状态:新建(New)、就绪(Runnable)、运行
(Running)、阻塞(Blocked)、死亡(Dead)。CPU需要在多条线程之间切
换,于是线程状态会多次在运行、阻塞、就绪之间切换。
4.2 jdk 1.5之后
在java.lang.Thread.State的枚举类中这样定义:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
-
NEW(新建):线程刚被创建,但是并未启动。还没调用start方法。
-
RUNNABLE(可运行):这里没有区分就绪和运行状态。
-
Teminated(被终止):表明此线程已经结束生命周期,终止运行。
-
重点说明,根据Thread.State的定义,阻塞状态分为三种:BLOCKED、WAITING、TIMED_WAITING。
-
BLOCKED(锁阻塞):一个正在阻塞、等待一个监视器锁(锁对象)的线程处于这一状态。只有获得锁对象的线程才能有执行机会。
-
TIMED_WAITING(计时等待):一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。
-
WAITING(无限等待):一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
-
通过Object类的wait进入WAITING状态的要有Object的notify/notifyAll唤醒;
-
通过Condition的await进入WAITING状态的要有Condition的signal方法唤醒;
-
通过LockSupport类的park方法进入WAITING状态的要有LockSupport类的unpark方法唤醒
-
通过Thread类的join进入WAITING状态,只有调用join方法的线程对象结束才能让当前线程恢复;
-
-
说明:当从 WAITING 或 TIMED_WAITING 恢复到 Runnable 状态时,如果
发现当前线程没有得到监视器锁,那么会立刻转入 BLOCKED 状态。
5. 线程安全问题以及解决
当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一
条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题。
但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。
案例
火车站要卖票,我们模拟火车站的卖票过程。因为疫情期间,本次列车的座位
共100个(即,只能出售100张火车票)。我们来模拟车站的售票窗口,实现多
个窗口同时售票的过程。注意:不能出现错票、重票
1. 继承 Thread 方式 ( 局部变量以及不同对象的实例变量不共享,使用静态变量共享)
package unsafe;
class TicketSaleThread extends Thread {
private static int ticket = 100;
public void run() {
while (ticket > 0) {
try {
Thread.sleep(10);//加入这个,使得问题暴露的更明显
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "卖出一张票,票号:" + ticket);
ticket--;
}
}
}
public class SaleTicket {
public static void main(String[] args) {
TicketSaleThread t1 = new TicketSaleThread();
TicketSaleThread t2 = new TicketSaleThread();
TicketSaleThread t3 = new TicketSaleThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
问题1:但是有重复票或负数票问题。
原因:线程安全问题
问题2:如果要考虑有两场电影,各卖100张票等
原因:TicketThread类的静态变量,是所有TicketThread类的对象共享
2. 使用实现 Runnable 接口的方式
package safe;
class TicketSaleRunnable implements Runnable {
private int ticket = 100;
public void run() {
while (ticket > 0) {
try {
Thread.sleep(10);//加入这个,使得问题暴露的更明显
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出一张票,票号:" + ticket);
ticket--;
}
}
}
public class SaleTicket {
public static void main(String[] args) {
TicketSaleRunnable tr = new TicketSaleRunnable();
Thread t1 = new Thread(tr, "窗口一");
Thread t2 = new Thread(tr, "窗口二");
Thread t3 = new Thread(tr, "窗口三");
t1.start();
t2.start();
t3.start();
}
}
结果:发现卖出近100张票。
问题:但是有重复票或负数票问题。
原因:线程安全问题
解决方法:同步机制
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在
票问题,Java中提供了同步机制 (synchronized)来解决。
根据案例简述:
窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结
束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改
共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢
夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现
象。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。注意:在任
何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能
在外等着 (BLOCKED) 。
同步机制的原理,其实就相当于给某段代码加“锁”,任何线程想要执行这
段代码,都要先获得“锁”,我们称它为同步锁。因为Java对象在堆中的数
据分为分为对象头、实例变量、空白的填充。而对象头中包含:
- Mark Word:记录了和当前对象有关的GC、锁标记等信息。
- 指向类的指针:每一个对象需要记录它是由哪个类创建出来的。
- 数组长度(只有数组对象才有)
哪个线程获得了“同步锁”对象之后,”同步锁“对象就会记录这个线程的
ID,这样其他线程就只能等待了,除非这个线程”释放“了锁对象,其他
线程才能重新获得/占"同步锁"对象。
同步代码块:synchronized 关键字可以用于某个区块前面,表示只对这个区块
的资源实行互斥访问。 格式:
synchronized(同步锁){
需要同步操作的代码
}
同步方法:synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能
进入这个方法,其他线程在外面等着。
public synchronized void method(){
可能会产生线程安全问题的代码
}
静态方法加锁代码:
package safe;
class TicketSaleThread extends Thread{
private static int ticket = 100;
public void run(){//直接锁这里,肯定不行,会导致,只有一个窗口卖票
while (ticket > 0) {
saleOneTicket();
}
}
public synchronized static void saleOneTicket(){//锁对象是TicketSaleThread类的Class对象,而一个类的Class对象在内存中肯定只有一个
if(ticket > 0) {//不加条件,相当于条件判断没有进入锁管控,线程安全问题就没有解决
System.out.println(Thread.currentThread().getName() + "卖出一张票,票号:" + ticket);
ticket--;
}
}
}
public class SaleTicket {
public static void main(String[] args) {
TicketSaleThread t1 = new TicketSaleThread();
TicketSaleThread t2 = new TicketSaleThread();
TicketSaleThread t3 = new TicketSaleThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
非静态方法加锁:
package safe;
public class SaleTicket {
public static void main(String[] args) {
TicketSaleRunnable tr = new TicketSaleRunnable();
Thread t1 = new Thread(tr, "窗口一");
Thread t2 = new Thread(tr, "窗口二");
Thread t3 = new Thread(tr, "窗口三");
t1.start();
t2.start();
t3.start();
}
}
class TicketSaleRunnable implements Runnable {
private int ticket = 100;
public void run() {//直接锁这里,肯定不行,会导致,只有一个窗口卖票
while (ticket > 0) {
saleOneTicket();
}
}
public synchronized void saleOneTicket() {//锁对象是this,这里就是TicketSaleRunnable对象,因为上面3个线程使用同一个TicketSaleRunnable对象,所以可以
if (ticket > 0) {//不加条件,相当于条件判断没有进入锁管控,线程安全问题就没有解决
System.out.println(Thread.currentThread().getName() + "卖出一张票,票号:" + ticket);
ticket--;
}
}
}
6. 再谈同步
6.1 死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要
的同步资源,就形成了线程的死锁。
诱发死锁的原因:
- 互斥条件
- 占用且等待
- 不可抢夺(或不可抢占)
- 循环等待
以上4个条件,同时出现就会触发死锁。
解决死锁:
死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件。
针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。
针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。
针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。
针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。
6.2 JDK5.0 新特性:LOCK(锁)
-
JDK5.0的新增功能,保证线程的安全。与采用synchronized相比,Lock可
提供多种锁方案,更灵活、更强大。Lock通过显式定义同步锁对象来实现同
步。同步锁使用Lock对象充当。 -
java. util. concurrent. locks. Lock 接口是控制多个线程对共享资源进行访
问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对
象加锁,线程开始访问共享资源之前应先获得Lock对象。 -
实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
- ReentrantLock类实现了 Lock 接口,它拥有与 synchronized 相同的
并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等
候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。
- ReentrantLock类实现了 Lock 接口,它拥有与 synchronized 相同的
-
Lock锁也称同步锁,加锁与释放锁方法,如下:
-
public void lock() :加同步锁。
-
public void unlock() :释放同步锁。
-
代码如下:
import java.util.concurrent.locks.ReentrantLock;
class Window implements Runnable{
int ticket = 100;
//1. 创建Lock的实例,必须确保多个线程共享同一个Lock实例
private final ReentrantLock lock = new ReentrantLock();
public void run(){
while(true){
try{
//2. 调动lock(),实现需共享的代码的锁定
lock.lock();
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticket--);
}else{
break;
}
}finally{
//3. 调用unlock(),释放共享代码的锁定
lock.unlock();
}
}
}
}
public class ThreadLock {
public static void main(String[] args) {
Window t = new Window();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
synchronized 与 Lock 的对比
- Lock 是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域、遇到异常等自动解锁
- Lock 只有代码块锁,synchronized 有代码块锁和方法锁
- 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类),更体现面向对象。
- (了解)Lock 锁可以对读不加锁,对写加锁,synchronized 不可以
- (了解)Lock 锁可以有多种获取锁的方式,可以从 sleep 的线程中抢到锁,synchronized 不可以
说明:开发建议中处理线程安全问题优先使用顺序为:
Lock ----> 同步代码块 ----> 同步方法
7. 线程的通信
生产者与消费者问题
等待唤醒机制可以解决经典的“生产者与消费者”的问题。生产者与消费者问
题,也称有限缓冲问题,是一个多线程同步问题的经典案例。该问题描述了两个
(多个)共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——
在实际运行时会发生的问题。
生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此
同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在
缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
举例:
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员
处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试
图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通
知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果
店中有产品了再通知消费者来取走产品。
类似的场景,比如厨师和服务员等。
生产者与消费者问题中其实隐含了两个问题:
-
线程安全问题:
因为生产者与消费者共享数据缓冲区,产生安全问题。不过这个问题可以使
用同步解决。 -
线程的协调工作问题:
要解决该问题,就必须让生产者线程在缓冲区满时等待(wait),暂停进入阻
塞状态,等到下次消费者消耗了缓冲区中的数据的时候,通知(notify)正在
等待的线程恢复到就绪状态,重新开始往缓冲区添加数据。同样,也可以让
消费者线程在缓冲区空时进入等待(wait),暂停进入阻塞状态,等到生产者
往缓冲区添加数据之后,再通知(notify)正在等待的线程恢复到就绪状态。
通过这样的通信机制来解决此类问题。
代码实现:
public class ConsumerProducerTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
Consumer c1 = new Consumer(clerk);
Consumer c2 = new Consumer(clerk);
p1.setName("生产者1");
c1.setName("消费者1");
c2.setName("消费者2");
p1.start();
c1.start();
c2.start();
}
}
//生产者
class Producer extends Thread{
private Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("=========生产者开始生产产品========");
while(true){
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
//要求clerk去增加产品
clerk.addProduct();
}
}
}
//消费者
class Consumer extends Thread{
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
System.out.println("=========消费者开始消费产品========");
while(true){
try {
Thread.sleep(90);
} catch (InterruptedException e) {
e.printStackTrace();
}
//要求clerk去减少产品
clerk.minusProduct();
}
}
}
//资源类
class Clerk {
private int productNum = 0;//产品数量
private static final int MAX_PRODUCT = 20;
private static final int MIN_PRODUCT = 1;
//增加产品
public synchronized void addProduct() {
if(productNum < MAX_PRODUCT){
productNum++;
System.out.println(Thread.currentThread().getName() +
"生产了第" + productNum + "个产品");
//唤醒消费者
this.notifyAll();
}else{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//减少产品
public synchronized void minusProduct() {
if(productNum >= MIN_PRODUCT){
System.out.println(Thread.currentThread().getName() +
"消费了第" + productNum + "个产品");
productNum--;
//唤醒生产者
this.notifyAll();
}else{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
8. JDK5.0 新增线程创建方式
8.1 新增方式一:实现 Callable接口
- 与使用Runnable相比, Callable功能更强大些
– 相比run()方法,可以有返回值
– 方法可以抛出异常
– 支持泛型的返回值(需要借助FutureTask类,获取返回结果)
- Future接口(了解)
– 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
– FutureTask是Futrue接口的唯一的实现类
– FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
- 缺点:在获取分线程执行结果的时候,当前线程(或是主线程)受阻塞,效率较低。
代码举例:
/*
* 创建多线程的方式三:实现Callable (jdk5.0新增的)
*/
//1.创建一个实现Callable的实现类
class NumThread implements Callable {
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
sum += i;
}
}
return sum;
}
}
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方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
8.2 新增方式二:使用线程池
现有问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束
了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程
需要时间。
那么有没有一种办法使得线程可以复用,即执行完一个任务,并不被销毁,而是
可以继续执行其他的任务?
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
-
提高响应速度(减少了创建新线程的时间)
-
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
-
便于线程管理
– corePoolSize:核心池的大小
– maximumPoolSize:最大线程数
– keepAliveTime:线程没有任务时最多保持多长时间后会终止
– …
线程池相关API
- JDK5.0之前,我们必须手动自定义线程池。从JDK5.0开始,Java内置线程池相关的API。在java.util.concurrent包下提供了线程池相关API:ExecutorService 和 Executors。
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
– void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
–Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
– void shutdown() :关闭连接池
- Executors:一个线程池的工厂类,通过此类的静态工厂方法可以创建多种类型的线程池对象。
– Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
– Executors.newFixedThreadPool(int nThreads); 创建一个可重用固定线程数的线程池
– Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
– Executors.newScheduledThreadPool(int corePoolSize):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
代码举例:
class NumberThread 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 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 Callable {
@Override
public Object call() throws Exception {
int evenSum = 0;//记录偶数的和
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
evenSum += i;
}
}
return evenSum;
}
}
public class ThreadPoolTest {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// //设置线程池的属性
// System.out.println(service.getClass());//ThreadPoolExecutor
service1.setMaximumPoolSize(50); //设置线程池中线程数的上限
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
try {
Future future = service.submit(new NumberThread2());//适合使用于Callable
System.out.println("总和为:" + future.get());
} catch (Exception e) {
e.printStackTrace();
}
//3.关闭连接池
service.shutdown();
}
}