一、多线程
l 每个线程都有自己独立的一块栈空间
1.1 并发和并行
- 并发:指两个或多个事件在同一个时间段内发生。(交替执行)
- 并行:指两个或多个事件在同一时刻发生(同时发生)。
1.2 线程和进程
- 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
- 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
线程调度:
- 分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。 - 抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。- 设置线程的优先级
任务管理器->详细信息->右键->设置优先级 - 抢占式调度详解
大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。
实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。
其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
- 设置线程的优先级
1.3 创建线程类
创建Thread类的子类,重写run方法 -> 线程任务
//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 mt=new MyThread();
//4.调用Thread类中的start方法,开启新的线程
mt.start();
//主线程会继续执行主方法中的代码
for (int i = 0; i <20 ; i++) {
System.out.println("main:"+i);
}
}
}
输出结果:
run:0
main:0
main:1
run:1
main:2
run:2
main:3
run:3
.... //CPU随机选择 main线程和新线程
二、Thread方法和Runnable接口
2.1 Thread常用方法
获取线程名称
public String getName()
:获取当前线程名称。
public static Thread currentThread()
:返回对当前正在执行的线程对象的引用
public class MyThread extends Thread {
@Override
public void run() {
// String name=getName();
// System.out.println(name);
// Thread t=Thread.currentThread();
// System.out.println(t);
// String name=t.getname();
// System.out.println(name);
//链式编程
System.out.println(Thread.currentThread().getName());
}
}
/*
线程的名称:
主线程:main
新线程:Thread-0,Thread-1,Thread-2 ....
*/
public class Demo01GetThreadName {
public static void main(String[] args) {
//创建对象
MyThread mt=new MyThread();
//调用start方法开启线程
mt.start();//Thread-0
new MyThread().start();//Thread-1
//获取主线程名称(只能先获取当前线程 currentThread 才能调用getName)
System.out.println(Thread.currentThread().getName());//main
}
设置线程名称
public Thread(String name)
:分配一个指定名字的新的线程对象
public Thread(Runnable target,String name)
:分配一个带有指定目标新的线程对象并指定名字
public void setName(String name)
: 为线程对象设置名字.
public class MyThread extends Thread{
public MyThread(){};
public MyThread(String name){
super(name);//调用父类带参构造方法,使父类给其起名字 -> 007
}
@Override
public void run() {
//获取线程名称
System.out.println(Thread.currentThread().getName());
}
}
public class Demo01SetThreadName {
public static void main(String[] args) {
//开启多线程
MyThread mt=new MyThread();
mt.setName("001");//第一种方法
mt.start();//001
new MyThread("007").start();//007
}
}
Sleep方法
public static void sleep(long millis)
:使当前正在执行的线程以指定的毫秒暂停
// 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);
//使用sleep方法让程序睡眠一秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.2 Runnable
开启线程的第二种方法
2.3 Runnable和Thread的区别
实现Runnable接口创建多线程程序的好处:
1.避免了单继承的局限性
一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法: 用来设置线程任务
创建Thread类对象,调用start方法: 用来开启新线程
传递不同实现类,完成不同线程任务(接口)
2.4 匿名内部类实现
/*
匿名内部类方法实现线程创建
*/
public class Demo01InnerClassThread {
public static void main(String[] args) {
//线程的父类Thread
new Thread(){
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}.start(); //开启多线程
//线程的接口Runnable
//Runnable r =new RunnableImpl();//多态
Runnable r = new Runnable(){
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
};
new Thread(r).start();
//简化接口的方式
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}).start();
}
}
三、线程安全
3.1 售票问题
问题背景:一家电影院三个售票窗口同时售卖 1-100张票,会出现同时卖同样的票
public class RunnableImpl implements Runnable {
//定义一个多个线程共享的票源
private int ticket=100;
//设置线程任务 -> 卖票
@Override
public void run() {
//使用循环让卖票重复进行
while(true){
if (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在 -> 出售
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
测试类:
/*
模拟卖票案例:
创建3个线程,同时开启,对共享的票进行出售
*/
public class Demo01Ticket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run=new RunnableImpl();//只创建一个实现类实现共享票源
//创建Thread对象,构造方法中传递Runnable接口的实现类对象
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
//开启三个线程
t0.start();
t1.start();
t2.start();
}
}
结果:(将ticket改为2之后)
Thread-2-->正在卖第2张票
Thread-0-->正在卖第2张票
Thread-1-->正在卖第2张票
Thread-2-->正在卖第-1张票
分析结果:
三个线程抢夺CPU执行权,谁抢到就执行
出现卖同一张票的原因:
多个线程同时执行至 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
-> 此时还没有执行到ticket--;
所以输出的是同一张票
出现正在卖-1张票原因:
线程进入if语句判断后 然后进入睡眠 -> 例如0号线程和1号线程先结束睡眠进入执行卖票后执行
ticket--;
,此时ticket为1,两个线程都卖出第1张票后,ticket--;
执行了两次变成-1;而此时2号线程也进入了循环还在睡眠中,那么他就会执行正在卖-1张票的操作
在现实操作时,我们要保证始终只有一个线程在卖票
3.2 线程同步
同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据代码)
}
注意:
1.通过代码块的所对象,可以使用任意的对象
2.必须保证多个线程使用的锁对象是同一个
3.锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
改良卖票程序:
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(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在 -> 出售
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
同步锁原理
多个线程抢夺cpu执行权时,当某个线程抢夺到了执行权
执行遇到 synchronized
代码块,检查发现存在锁对象,则会获取锁对象,运行代码块内容
当其他CPU进入代码发现锁对象被占用,则会进入堵塞状态,直到其他线程执行完代码块归还锁对象
同步保证了只有一个线程在同步中执行共享数据保证了安全,程序频繁的判断锁,获取锁,释放锁,程序的效率会降低。
3.3 同步方法
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
使用同步方法:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized修饰符
格式:定义方法的格式修饰符 synchronized 返回值类型 方法名(参数列表)
public synchronized void method(){
可能会产生线程安全问题的代码
}
public class RunnableImpl implements Runnable {
//定义一个多个线程共享的票源
private int ticket=2;
//设置线程任务 -> 卖票
@Override
public void run() {
System.out.println("this:"+this);
//使用循环让卖票重复进行
while(true){
payTicket();//调用同步方法
}
}
/*
定义一个同步方法
同步方法也会把方法内部的代码锁住
只让一个线程执行
同步方法的锁对象是谁?
就是实现类对象 new RunnableImpl()
也就是 this (调用方法的参数即为同步锁对象)
*/
public synchronized void payTicket(){
if (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在 -> 出售
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
3.4 Lock锁
第三种方法:Lock锁:
方法:
1.void lock() 获取锁
2.void unlock() 释放锁ReentrantLock
implements Lock
接口
使用步骤:
1.在成员位置创建一个ReentrantLock对象
2.在可能出现安全问题的代码前调用Lock接口中的方法lock获取锁
3.在可能出现安全问题的代码后调用unlock释放锁
格式:
lock();
try{
//可能出现问题的代码
}catch(e){
...
}finally{
unlock();
}
public class RunnableImpl implements Runnable {
//定义一个多个线程共享的票源
private int ticket=2;
//创建ReentrantLock对象
Lock l = new ReentrantLock();
@Override
public void run() {
while(true){
//在 可能出现问题的代码前获取锁
l.lock();
if (ticket>0){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//在会出现安全问题的代码后释放锁
l.unlock();
}
}
}
}
// @Override
// public void run() {
//
// while(true){
// //在 可能出现问题的代码前获取锁
// l.lock();
//
// if (ticket>0){
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//
// System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
// ticket--;
// }
//
// //在会出现安全问题的代码后释放锁
// l.unlock();
// }
// }
}
四、线程状态
4.1 线程状态概述
当线程被创建并启动后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,APIjava.lang.Thread.State
中有6种状态:
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行代码,也可能没有,取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态; |
Wating(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Wating状态,进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
TimedWaiting(计时等待) | 同wating状态,有几个方法有超时参数,调用他们进入TimedWaiting状态,这一状态将一直保持到超时期满或者接受到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait |
Teminated(被死亡) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
4.2Timed Waiting(计时等待)
4.3 BLOCKED(锁阻塞)
4.4 Waiting(无限等待)
一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
买包子和卖包子案例
package code.day06Thread.Demo10Wating;
/*
* 等待唤醒案例:线程之间的通信
* 创建一个顾客线程(消费者): 告诉老板需要什么包子,调用wait方法,放弃cpu的执行,进入到WAITING状态
* 创建一个老板线程(生产者): 花了5秒制作包子,做好包子后,调用notify方法,唤醒顾客吃包子
*
* 注意:
* 顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只有一个在执行
* 同步使用的锁对象必须保证唯一
* 只有锁对象此啊能调用wait和notify方法
*
* Object类方法:
* void wait()
* 在其他线程调用此对象notify()方法或者notifyAll()方法前,导致当前线程等待
* void notify()
* 唤醒再次对象监视器上等待的单个线程
* 会继续执行wait方法之后的代码
* */
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("告知老板要的包子的种类和数量");
//调用wait方法,放弃cpu的执行,进入到WAITING状态
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("顾客吃到了包子");
System.out.println("----------------------");
}
}
}
}.start();
//创建老板线程
new Thread(){
@Override
public void run() {
//一直卖包子
while(true){
//花五秒钟做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//同步
synchronized (obj){
System.out.println("老板五秒做好包子,告知顾客");
//notify唤醒顾客吃包子
obj.notify();//obj对象调用的wait方法来等待就要用obj对象调用notify来唤醒
}
}
}
}.start();
}
}
结果:
告知老板要的包子的种类和数量
老板五秒做好包子,告知顾客
顾客吃到了包子
----------------------
告知老板要的包子的种类和数量
老板五秒做好包子,告知顾客
顾客吃到了包子
----------------------
/*
* 进入到TimeWaiting(计时等待)有两种方式:
* 1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
* 2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
*
* 唤醒到方法:
* void notify() 唤醒在此对象监视器上等待到单个线程
* void notifyAll() 唤醒在此对象监视器上等待到所有线程。
*/
【线程池】
一、等待唤醒机制
1.1 线程间通信
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)不同
比如:A线程生产包子,B线程吃包子,包子可以理解为一种资源
为什么要处理线程通信
多个线程并发执行时,在默认情况下,cpu是随机切换线程的,当我们需要多个线程共同来完成一个目标时,就需要一些协调通信,以此帮助多个线程共同操作一份数据
如何保证线程间通信有效利用资源
多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各线程能有效的利用资源。这种手段即 ——等待唤醒机制
1.2等待与唤醒机制
等待与唤醒机制:线程之间的通信
重点:有效的利用资源(生产一个包子,吃一个包子,再生产一个包子…..)
通信:对包子的状态进行判断
等待唤醒中的方法
1.wait:线程不再活动,不参与调度,进入wait set中,因此不会浪费cpu资源,也并不会去竞争锁,
这时该线程的状态时WAITING。它还要等着别的线程执行一个特别的动作,也就是通知(notify)在这个对象上等待的线程从wait set中释放出来,重新进入到调度队列(ready queue)中
2.notify:选取所通知对象的wait set中的一个线程释放;
3.notifyAll:释放所通知对象的wait set上的全部线程。
注意: 哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。 总结如下:
- 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
- 否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态
注意事项
- wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
- wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
- wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
1.3 生产者与消费者案例
等待唤醒机制其实就是经典的“生产者与消费者”的问题。
就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:
包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。
代码演示:
包子类
public class Baozi {
//皮
String pi;
//馅
String xian;
//包子的状态: true false 初始值 false
boolean flag= false;
}
包子铺线程类
/*
* 注意:
* 包子铺线程和吃货线程关系-->通信(互斥)
* 必须使用同步技术保证两个线程只有一个在执行
* 锁对象必须保证唯一,使用包子对象作为锁对象
* 包子铺类和吃货类需要把包子对象作为参数传递进来
* 1.成员位置创建包子变量
* 2.使用带参构造,为包子变量赋值
*/
public class BaoZiPu extends Thread{
//1.成员位置创建包子变量
private Baozi bz;
//2.使用带参构造,为包子变量赋值
public BaoZiPu(Baozi bz){
this.bz=bz;
}
@Override
public void run() {
int count=0;
while(true){
//同步代码块
synchronized (bz){
if (bz.flag==true)
{
//已有包子,则等待
try {
bz.wait(); //使用锁对象调用方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//被唤醒之后执行,包子铺生产包子
if (count%2==0)
{
//生产 A包子
bz.pi="A皮";
bz.xian="A馅";
}else {
//生产B包子
bz.pi="B皮";
bz.xian="B馅";
}
count++;
System.out.println("包子铺正在生产:"+bz.pi+bz.xian+"包子.");
//生产包子 等待3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改包子状态
bz.flag=true;
//唤醒吃货线程
bz.notify();
System.out.println("包子铺以生产:"+bz.pi+bz.xian+"包子,顾客可以吃了");
}
}
}
}
吃货线程类
public class ChiHuo extends Thread{
private Baozi bz;
public ChiHuo(Baozi bz){
this.bz=bz;
}
@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+"包子");
//修改包包子状态
bz.flag=false;
//唤醒包子铺线程
bz.notify();
System.out.println("吃货吃完了"+bz.pi+bz.xian+"包子,包子铺开始生产包子");
System.out.println("------------------------");
}
}
}
}
测试类
public class Demo {
public static void main(String[] args) {
Baozi bz=new Baozi();
new BaoZiPu(bz).start();
new ChiHuo(bz).start();
}
}
结果:
包子铺正在生产:A皮A馅包子.
包子铺以生产:A皮A馅包子,顾客可以吃了
吃货正在吃A皮A馅包子
吃货吃完了A皮A馅包子,包子铺开始生产包子
------------------------
包子铺正在生产:B皮B馅包子.
包子铺以生产:B皮B馅包子,顾客可以吃了
吃货正在吃B皮B馅包子
吃货吃完了B皮B馅包子,包子铺开始生产包子
------------------------
包子铺正在生产:A皮A馅包子.
包子铺以生产:A皮A馅包子,顾客可以吃了
吃货正在吃A皮A馅包子
吃货吃完了A皮A馅包子,包子铺开始生产包子
------------------------
二、线程池
我们使用线程的时候,就直接创建一个线程,但是会有一个问题:
如果并发的线程很多,并且每个线程都是执行一个很短的任务就结束了,这样频繁的创建线程会大大降低系统的效率,因为频繁的创建和销毁线程需要时间
而java中的线程池可以使得线程能够复用,执行完任务不被销毁而继续执行其他任务
2.1线程池概念
- 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
线程池:容器—->(ArrayList,HashSet,LinkedList,HashMap)
合理利用线程池能够带来三个好处:
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
2.3线程池的使用
Java里面线程池的顶级接口是java.util.concurrent.Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
Executors类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
public Future<?> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
使用线程池中线程对象的步骤:
- 创建线程池对象。
- 创建Runnable接口子类对象。(task)
- 提交Runnable接口子类对象。(take task)
- 关闭线程池(一般不做)。
public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"创建来一个新的线程执行");
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
1. 创建线程池对象。
2. 创建Runnable接口子类对象。(task)
3. 提交Runnable接口子类对象。(take task)
4. 关闭线程池(一般不做)。
* */
public class Demo01ThreadPool {
public static void main(String[] args) {
//1
ExecutorService es= Executors.newFixedThreadPool(2);
//3
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
es.submit(new RunnableImpl());
}
}
pool-1-thread-1创建来一个新的线程执行
pool-1-thread-2创建来一个新的线程执行
pool-1-thread-1创建来一个新的线程执行
//两个线程执行三个任务