文章目录
Java多线程
一、基本概念
1、并发与并行
并发:两个或多个事件在同一时间段内发生
并行:两个或多个事件在同一时间发生
2、进程
进程是指一个在内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进行;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
3、线程
线程是进程的一个执行单元,负责当前进程中程序的执行。一个程序运行后至少有一个进程,一个进程中可以包含多个线程
4、线程调度
-
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
-
抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
二、多线程
1、创建多线程的两种方式
(1)创建Thread的子类
-
创建一个Thread类的子类
-
在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么)
-
创建Thread类的子类对象
-
调用Thread类中的start方法,开启新的线程,执行run方法
void start()
使线程开始执行;Java虚拟机调用该线程的run方法,该线程和主线程并发运行,该线程执行run方法。
注意:
- 多次启动一个线程是非法的,特别适当线程已经结束,不能再重新启动
- Java程序属于抢占式调度,哪个线程的优先级搞,哪个线程优先执行,同一个优先级,随机执行
代码实现:
//1、创建一个Thread类的子类
public class MyThread extends Thread{
//2、在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么)
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run:" + i);
}
}
}
// 测试类
public class Demo01Thread {
public static void main(String[] args) {
//3、创建Thread类的子类对象
MyThread myThread = new MyThread();
//4、调用Thread类中的方法start,开启新的线程,执行run方法
myThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("main:" + i);
}
}
}
(2)实现Runnable接口
java.lang.Runnable
Runnable接口应该由那些打算通过某一线程执行其实例来实现,类必须定义一个称为run的无参数方法
java.lang.Thread类
的构造方法
Thread(Runnable target)
分配新的Thread 对象
Thread(Runnable target,String name)
分配的Thread对象
实现步骤
- 创建一个Runnable接口的实现类
- 在实现类中重写Runnable接口的run方法,设置线程任务
- 创建一个Runnable接口的实现类对象
- 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
- 调用Thread类中start方法,开启新的线程执行run方法
代码实现
// 1、创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable{
// 2、在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "---->" + i);
}
}
}
public class Demo01Runnable {
public static void main(String[] args) {
// 3、创建一个Runnable接口的实现类对象
RunnableImpl ri = new RunnableImpl();
// 4、创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t = new Thread(ri);
// 5、调用Thread类中的start方法,开启新的线程执行run方法
t.start();
System.out.println(Thread.currentThread().getName());
}
}
实现Runnable接口比继承Thread类所具有的的优势
- 避免了单继承的局限性
- 一个类只能继承一个类,如果继承了Thread类就不能继承其他类,实现Runnable接口,还可以继承其他的类,实现其他的接口
- 增强了程序的扩展性,降低了程序的耦合性(解耦)
- 实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
- 实现类类中,重写了run方法:用来设置线程任务
- 创建Thread类对象,调用start方法:用来开启新线程
2、多线程原理及内存图解
继承Thread类的线程类,每创建一次实例并调用start方法就在栈内存开辟新的空间来执行run方法,CPU可以选择执行main方法或者run方法。
3、Thread类的常用方法
(1)获取线程
String getName()
返回该线程的名称
static Thread currentThread()
返回对当前正在执行的线程对象的引用
代码实现
public class MyThread extends Thread {
// 重写Thread类中的run方法,设置线程任务
@Override
public void run() {
// 获取线程名称
Thread t = Thread.currentThread();
System.out.println(t);
String name = t.getName();
System.out.println(name);
}
}
public class Demo01GetThreadName {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
// 链式编程
System.out.println(Thread.currentThread().getName());// main
}
}
(2)设置线程名称
void setName(String name)
改变线程名称,使之与参数name名称相同
Thread(String name)
分配新的Thread对象
代码实现
public class MyThread extends Thread {
public MyThread(){}
public MyThread(String name) {
super(name); // 把线程名称传递给父类,让父类(Thread)给子线程起一个名字
}
@Override
public void run() {
// 获取线程的名称
System.out.println(Thread.currentThread().getName());
setName("赵晓");
System.out.println(Thread.currentThread().getName());
}
}
public class Demo01SetThreadName {
public static void main(String[] args) {
// 使用构造方法创建自定义线程类实例
MyThread mt = new MyThread("xiaozhao");
System.out.println(mt.getName());// xiaozhao
System.out.println(Thread.currentThread().getName());// main
}
}
(3)sleep
public static void sleep(long millis)
使当前正在执行的线程以指定的毫秒暂停(暂时停止执行)
代码实现
public class Demo01Sleep {
public static void main(String[] args) {
// 模拟秒表
for (int i = 1; i <= 60; i++) {
System.out.println(i);
// 使用Thead类的sleep方法让程序睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4、匿名内部类创建线程
匿名内部类的作用:简化代码(相当于新建了一个子类对象/接口实现类对象)
- 把子类继承父类、重写父类的方法,创建子类对象合成一步完成
- 把实现实现类接口、重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类对象/实现类对象,这个类没有名字
代码实现
public class Demo01InnerClassThread {
public static void main(String[] args) {
// 线程的父类是Thread
// new MyThread().start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "---->" + "赵晓");
}
}
}.start();
// 线程的接口Runnable
// Runnable r = new RunnableImpl(); // 多态
Runnable r = new Runnable() {
// 重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "---->" + "xiaozhao");
}
}
};
Thread t = new Thread(r);
t.start();
// 链式编程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "---->" + "zhao");
}
}
}).start();
}
}
5、线程安全及同步
(1)概述
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
(2)代码实现
模拟卖票案例的代码实现
public class RunnableImpl implements Runnable {
private int ticket = 100;
// 设置线程任务:卖票
@Override
public void run() {
// 使用死循环,让卖票操作重复执行
while(true) {
// 先判断票是否存在
if(ticket > 0) {
System.out.println(Thread.currentThread().getName()+ "-->正在卖" + ticket + "张票");
ticket -- ;
}
}
}
}
/*
模拟卖票案例
创建3个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
// 创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
// 创建3个Thread对象
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
// 调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
(3)线程安全问题产生的原理
以卖票程序为例,三个线程同时执行到了“正在卖第“+ticket+”张票“,会出现卖同一张票和不存在的票等问题。原因就是三个线程在抢夺cpu的执行权时,还未等到当前线程卖完票,其他线程就又进行了卖票操作。
(4)解决线程安全问题的方法
-
同步代码块
synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。注意:
- 通过代码块中的锁对象,可以使用任意的对象
- 必须要保证多个线程使用的锁对象是同一个
- 锁对象作用:把同步的代码块锁住,只让一个线程在同步代码块中执行
代码实现
public class RunnableImpl implements Runnable { // 定义一个多线程共享的票源 private int ticket = 100; // 创建一个锁对象 Object obj = new Object(); // 设置线程任务:卖票 @Override public void run() { // 使用死循环,让卖票操作重复执行 while (true) { // 同步代码块 synchronized (obj) { // 先判断是否存在 if(ticket > 0) { // 提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票"); ticket -- ; } } } } }
public class Demo01Ticket { public static void main(String[] args) { // 创建Runnable接口的实现类对象 RunnableImpl run = new RunnableImpl(); // 创建3个Thread对象 Thread t0 = new Thread(run); Thread t1 = new Thread(run); Thread t2 = new Thread(run); // 调用start方法开启多线程 t0.start(); t1.start(); t2.start(); } }
同步技术的原理
上述程序使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器。3个线程一起抢夺CPU的执行权,谁抢到了谁执行run方法进行卖票。t0抢到了cpu的执行权,执行run方法,遇到synchronized代码块。这是t0会检查synchronized
代码块是否有锁对象,发现有,就会获取到锁对象,进入到同步中执行。t1抢到了cpu的执行权,执行run方法,遇到synchronnized
代码块,这时t1会检查synchronized
代码块是否有锁对象,发现没有,t1就会进入到阻塞状态,会一直等待t0线程归还锁对象。直到t0线程执行完同步中的代码,才会把锁对象归还给同步代码块,t1才能获取到锁对象进入到同步中执行。同步技术的优缺点
同步保证了只有一个线程在同步中执行共享数据,保证了安全。但是程序频繁的判断锁,获取锁,释放锁,程序的效率会降低。
总结
同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步代码
-
同步方法
使用
synchronized
修饰的方法,就叫做同步方法,保证A线程执行改方法的时候,其他线程只能在方法外等着。public synchronized void method(){可能会产生线程安全问题的代码}
使用同步方法时的同步锁是谁?
-
对于非静态方法,同步锁就是this
-
对于静态方法,我们使用当前方法所在类的字节码对象(类名.class)作为同步锁(反射机制)
代码实现
/* 卖票案例出现了线程安全问题 卖出了不存在的票和重复的票 解决线程安全问题的第二种方案,使用同步方法 使用步骤: 1、把访问了共享数据的代码抽取出来,放到一个方法中 2、在方法上添加synchronized修饰符 格式: 定义方法的格式 修饰符 synchronized 返回值类型 方法名(参数列表) { 可能会出现线程安全问题的代码(访问了共享数据的代码) } */ public class RunnableImpl implements Runnable{ // 定义一个多线程共享的票源 private static int ticket = 1000; // 设置线程任务:卖票 @Override public void run() { // 打印调用run方法的锁对象 System.out.println("this:"+ this); while (true){ payTicketStatic(); } } /* 静态的同步方法 锁对象是谁? 不能是this this是创建对象之后产生的,静态方法有限于对象 静态方法的锁对象是本类的class属性 --> class文件对象(反射) */ public static /*synchronized*/ void payTicketStatic(){ synchronized (RunnableImpl.class) { // 先判断是否存在 if (ticket > 0) { // 提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票"); ticket--; } } } /* 定义一个同步方法 同步方法也会把方法内部的代码锁住 只让一个线程执行 同步方法的锁对象是谁 就是实现类对象 new RunnableImpl() 也就是this */ public /*synchronized*/ void payTicket(){ synchronized (this) { // 先判断是否存在 if (ticket > 0) { // 提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票"); ticket--; } } } }
-
-
Lock锁
java.util.concurrent.locks.Lock
接口提供了比使用synchronized
方法和语句可获得的更广泛的锁定操作。Lock接口中的方法:
void lock()
获取锁void unlock()
释放锁java.util.concurrent.locks.ReentrantLock implements Lock
Lock接口的实现类,使用方法及步骤:- 在成员位置创建一个ReentrantLock对象
- 在可能出现安全问题的代码前调用Lock接口中的方法lock获取锁
- 在可能出现安全问题的代码后调用Lock接口中的方法unlock释放锁
代码实现
public class Demo01Ticket { public static void main(String[] args) { // 创建Runnable接口的实现类对象 RunnableImpl run = new RunnableImpl(); // 打印对象run System.out.println("run:" + run); // 创建3个Thread对象 Thread t0 = new Thread(run); Thread t1 = new Thread(run); Thread t2 = new Thread(run); // 调用start方法开启多线程 t0.start(); t1.start(); t2.start(); } }
public class RunnableImpl implements Runnable { // 定义一个多线程共享的票源 private int ticket = 1000; // 1、在成员位置创建一个ReentrantLock对象 Lock l = new ReentrantLock(); // 设置线程任务:卖票 @Override public void run() { // 让卖票操作重复执行 while (ticket > 1) { // 2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁 l.lock(); // 提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(10); // 票存在,卖票 System.out.println(Thread.currentThread().getName() + "-->正在卖" + ticket + "张票"); ticket -- ; } catch (InterruptedException e) { e.printStackTrace(); } finally { //3.在可能会出现安全问题的代码后调用Locke接口中的方法unlock释放锁 l.unlock(); // 无论程序是否异常,都会把锁释放 } } }
6、等待唤醒机制
(1)线程状态
新建状态 new:至今尚未启动的线程处于这种状态
阻塞状态 Blocked:具有cpu的执行资格,等待cpu控线时执行
运行状态 Runnable:正在java虚拟机中执行的线程处于这种状态
休眠(睡眠)状态 Timed_Waiting:放弃cpu的执行资格,cpu空闲,也不执行
无限(永久)等待状态 Waiting:无限期的等待另一个线程来执行某一特定操作
死亡状态 Terminated :已退出的线程处于这种状态
进入到TimeWaiting状态有两种方式
- 使用
sleep(long m)
方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态 - 使用
wait(long m)
方法,wait方法如果在毫秒值结束之后,还没有被notify方法唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
唤醒的方法:
void notify()
唤醒在此对象监视器上等待的单个线程
void notifyAll()
唤醒在此对象监视器上等待的所有线程
(2)线程间通信
**概念:**多个线程在处理同一个资源,但处理的动作(线程的任务)却不相同。比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。
(3)Object类中wait带参方法和notify方法
void wait()
在其他线程调用此对象的notify()
方法或notifyAll()
方法前,当前线程处于等待状态。
void notify()
唤醒在此对象监视器上的单个线程,会继续执行wait方法之后的代码
(4)代码实现
public class Demo01WaitAndNotify {
public static void main(String[] args) {
// 创建锁对象,保证唯一
Object obj = new Object();
// 创建一个顾客线程(消费者)
new Thread() {
@Override
public void run() {
// 一直等着卖包子
while (true) {
// 保证等待和唤醒的线程只有一个在执行,需要用到同步技术
synchronized (obj) {
System.out.println("顾客1告知老板要的包子的种类和数量");
// 调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒之后执行的代码
System.out.println("包子已经做好了,顾客1开吃");
}
}
}
}.start();
// 创建一个老板线程(生产者)
new Thread() {
@Override
public void run() {
// 一直在做包子
while(true) {
// 花了5秒做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 保证等待和唤醒的线程只有一个执行,需要使用同步技术
synchronized (obj) {
System.out.println("老板5秒钟之后做好包子,告知顾客1,可以吃包子了");
// 做好包子后,调用notify方法,唤醒顾客吃包子
obj.notify();
}
}
}
}.start();
}
}
(5)等待唤醒机制综合案例
包子铺(生产者)、吃货(消费者)共同操作包子。
/*
资源类:包子类
设置包子的属性
皮
线
包子的状态:有true,没有false
*/
public class BaoZi {
// 皮
String pi;
// 馅
String xian;
// 包子的状态,有true,没有false,设置初始值为false没有包子
Boolean flag = false;
}
/*
生产者(包子铺)类是一个线程类,可以继承Thread
设置线程任务(run):生产包子
对包子的状态进行判断
true:
有包子
包子铺调用wait()方法进入等待状态
false:没有包子
包子铺生产包子
增加一些趣味性:交替生产两种包子
有两种状态(i%2== 0)
包子铺生产好了包子
修改包子的状态为true 有
唤醒吃货线程,让吃货线程吃包子
注意:
包子铺线程和包子线程关系--> 通信(互斥)
必须同时同步技术保证两个线程只能有一个再执行
锁对象必须保证唯一,可以把包子对象作为锁对象
包子铺类和吃货的类就需要把包子对象作为参数传递进来
1、需要在成员位置创建一个包子变量
2、使用带参数构造方法,为这个包子变量赋值
*/
public class BaoZiPu extends Thread{
// 1、在成员变量位置创建一个包子变量
private BaoZi bz;
// 2、使用带参数的构造方法为这个包子赋值
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
// 设置线程任务(run):生产包子
@Override
public void run() {
// 定义一个变量
int count = 0;
while (true){
// 必须使用同步技术保证两个线程只有一个在执行
synchronized (bz) {
// 对包子的状态进行判断
if( bz.flag == true ) {
try {
// 调用wait方法进入等待状态
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 被唤醒之后执行,包子铺生产包子
// 增加一些趣味性:交替生产两种包子
if( count % 2 == 0) {
// 生产 薄皮三鲜馅的包子
bz.pi = "薄皮";
bz.xian = "三鲜馅";
} else {
// 生产 冰皮猪肉大葱馅的包子
bz.pi = "冰皮";
bz.xian = "猪肉大葱";
}
count ++;
System.out.println("包子铺正在生产:" + bz.pi + bz.xian+"的包子!");
// 生产包子需要三秒钟
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 包子铺生产好了包子之后
// 修改包子的状态为true
bz.flag = true;
// 唤醒吃货线程,让吃货线程吃包子
bz.notify();//这个唤醒是不是唤醒的包子铺线程自己
System.out.println("包子铺已经生产好了"+ bz.pi + bz.xian +"的包子,吃货可以来吃了");
}
}
}
}
/*
消费者(吃货)类:是一个线程类,可以继承Thread
设置线程任务(run):吃包子
对包子的状态进行判断
false:没有包子
吃货调用wait方法进入等待状态
true:有包子
吃货吃包子
吃货吃完包子
修改包子的状态为false没有
吃货唤醒包子铺线程,生产包子
*/
public class ChiHuo extends Thread {
//1、需要在成员位置创建一个包子变量
private BaoZi bz;
//2、使用带参数的构造方法,为这个包子变量赋值
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
//3、设置线程任务(run):吃包子
@Override
public void run() {
//使用死循环,让吃货一直吃包子
while (true) {
// 必须使用同步技术保证两个线程只能有一个再执行
synchronized (bz) {
if (bz.flag == false) {
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 被唤醒之后执行的代码,吃包子
System.out.println("吃货正在吃:" + bz.pi + bz.xian + "的包子");
//吃货吃完包子,修改包子的状态为false
bz.flag = false;
// 吃货唤醒包子铺线程,生产包子
bz.notify();
System.out.println("吃货已经把:" + bz.pi + bz.xian + "的包子吃完了,包子铺开始生产包子");
System.out.println("-----------------------------------------");
}
}
}
}
/*
测试类:
包含main方法,程序执行的入口,启动程序
创建包子对象
创建包子铺线程,开启,生产包子
创建吃货线程,开启,吃包子
*/
public class Demo {
public static void main(String[] args) {
//创建包子对象;
BaoZi bz = new BaoZi();
// 创建包子铺线程,开启,生产包子
new BaoZiPu(bz).start();
// 创建吃货线程,开启,吃包子
new ChiHuo(bz).start();
}
}
7、线程池
(1)概念和原理
线程池:一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
java.util.concurrent.Executors
线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads)
创建一个可复用固定线程数的线程池
参数:int nThreads
创建线程池中包含的线程数量
返回值:ExecutorService
接口,返回的是ExecutorService
接口的实现类对象,我们可以使用ExecutorService
接口接收(面向接口编程)
java.util.concurrent.ExecutorService
线程池接口
submit(Runnable task)
提交了一个Runnable任务用于执行,用来从线程池汇总获取线程,调用run方法,执行线程任务。void shutdown()
关闭/销毁线程池的方法
线程池的使用步骤:
- 使用线程池的工厂类
Executors
里边提供的静态方法newFixedThreadPool
生产一个指定线程数量的线程池 - 创建一个类,实现
Runnable
接口,重写run
方法,设置线程任务 - 调用
ExecutorService
中的方法submit
,传递线程任务(实现开启线程类),执行run方法 - 调用
ExecutorService
中的方法shutdown
销毁线程池(不建议执行)
(2)代码实现
public class Demo01ThreadPool {
public static void main(String[] args) {
// 1、使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
// 3、调用ExecutorService中的方法submit,传递线程任务(实现开启线类),程,执行run方法
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
// 线程池会一直开启,试用完了之后会归还给线程池,可以拿出来继续使用
es.shutdown();
es.submit(new RunnableImpl());// 线程池销毁了,就不能获取线程使用了
}
}
public class RunnableImpl implements Runnable{
//2、创建一个类,实现Runnable接口,重写run方法,设置线程任务
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"创建了一个新线程");
}
}
方法,设置线程任务
- 调用
ExecutorService
中的方法submit
,传递线程任务(实现开启线程类),执行run方法 - 调用
ExecutorService
中的方法shutdown
销毁线程池(不建议执行)
(2)代码实现
public class Demo01ThreadPool {
public static void main(String[] args) {
// 1、使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
// 3、调用ExecutorService中的方法submit,传递线程任务(实现开启线类),程,执行run方法
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
// 线程池会一直开启,试用完了之后会归还给线程池,可以拿出来继续使用
es.shutdown();
es.submit(new RunnableImpl());// 线程池销毁了,就不能获取线程使用了
}
}
public class RunnableImpl implements Runnable{
//2、创建一个类,实现Runnable接口,重写run方法,设置线程任务
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"创建了一个新线程");
}
}