主线程:执行主(main)方法的线程 单线程程序:java程序中只有一个线程 执行从main方法开始,从上到下依次执行 JVM执行main方法,main方法会进入到栈内存 JVM会找操作系统开辟一条main方法通向cpu的执行路径 cpu就可以通过这个路径来执行main方法 而这个路径有一个名字,叫main(主)线程
Java使用 java.lang.Thread
类代表 线程 ,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来 创建 并 启动多线程 的步骤如下:
java.lang.Thread
类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类
实现步骤:
1.创建一个Thread类的子类 2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?) 3.创建Thread类的子类对象 4.调用Thread类中的方法start方法,开启新的线程,执行run方法 void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
结果是两个线程并发地运行;当前线程(main线程)和另一个线程(创建的新线程,执行其 run 方法)。
多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同一个优先级,随机选择一个执行
测试类:
public class ThreadDemo01 { public static void main(String[] args) { MyThread myThread = new MyThread("run"); myThread.start(); for (int i = 0; i < 20; i++) { System.out.println("main"+i); } } } /* main0 run0 run1 run2 run3 run4 run5 run6 run7 run8 run9 run10 run11 run12 run13 run14 run15 run16 run17 run18 run19 main1 main2 main3 main4 main5 main6 main7 main8 main9 main10 main11 main12 main13 main14 main15 main16 main17 main18 main19 */
自定义线程类:
public class MyThread extends Thread { public MyThread(String name) { super(name); } /** * 重写run方法 */ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("run"+i); } } }
创建多线程程序的第二种方式:实现Runnable接口 java.lang.Runnable Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。 java.lang.Thread类的构造方法 Thread(Runnable target) 分配新的 Thread 对象。 Thread(Runnable target, String name) 分配新的 Thread 对象。 实现步骤: 1.创建一个Runnable接口的实现类 2.在实现类中重写Runnable接口的run方法,设置线程任务 3.创建一个Runnable接口的实现类对象 4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象 5.调用Thread类中的start方法,开启新的线程执行run方法
实现Runnable接口创建多线程程序的好处:
1.避免了单继承的局限性
一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程
public class RunnableImpl implements Runnable { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } } public class RunnableDemo { public static void main(String[] args) { RunnableImpl runnable = new RunnableImpl(); new Thread(runnable).start(); for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } }
匿名内部类方式实现线程的创建
匿名:没有名字;内部类:写在其他类内部的类
匿名内部类作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合一步完成
把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
格式: new 父类/接口(){ 重复父类/接口中的方法 };
public class InnerClassThread { public static void main(String[] args) { new Thread(){ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } }.start(); Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } }; new Thread(runnable).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } },"xin").start(); } }
两个线程,一个main线程,一个新线程一起抢夺cpu的执行权。谁抢到就执行对应的代码
main方法压栈执行,其中代码逐步执行 创建一个MyThread对象,在堆内存中 调用mt.run方法,run方法进栈,单线程 调用mt.start方法,开辟新的栈空间,执行run方法 new MyThread.start()重新开辟了一个栈空间 cpu就有了选择的权力,可以执行main方法也可以执行两个run方法 多线程好处,多个线程之间互不影响(在不同的栈空间)
1.使用Thread类中的方法getName() String getName() 返回该线程的名称。 2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称 static Thread currentThread() 返回对当前正在执行的线程对象的引用。
public class MyThread extends Thread{ @Override public void run() { System.out.println(getName()); System.out.println(Thread.currentThread().getName()); } } public class ThreadGetDemo01 { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); } } //Thread-0 //Thread-0
1.使用Thread类中的方法setName(名字) void setName(String name) 改变线程名称,使之与参数 name 相同。 2.创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字 Thread(String name) 分配新的 Thread 对象。
public class MyThread extends Thread{ public MyThread() { } public MyThread(String name) { super(name); } @Override public void run() { System.out.println(Thread.currentThread().getName()); } } public class ThreadSet { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.setName("阿强"); myThread.start(); new MyThread("大强").start(); } } //阿强 //大强
public static void sleep(long millis): 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。 毫秒数结束之后,线程继续执行
public class ThreadSleep { public static void main(String[] args) { for (int i = 0; i < 60; i++) { System.out.println(i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
通过一个案例,演示线程的安全问题:
电影院要卖票,模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。
来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟
<img src="https://raw.githubusercontent.com/FLVE/FigureBed/master/20200510235227.bmp" style="zoom: 150%;" />
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--; } } } } public class Ticket { public static void main(String[] args) { RunnableImpl runnable = new RunnableImpl(); Thread t0 = new Thread(runnable); Thread t1 = new Thread(runnable); Thread t2 = new Thread(runnable); t0.start(); t1.start(); t2.start(); } }
<img src="https://raw.githubusercontent.com/FLVE/FigureBed/master/20200511000256.bmp" style="zoom:150%;" />
t0抢到cpu的执行权,执行到if语句,失去了cou的执行权 t2抢到cpu的执行权,执行到if语句,失去了cou的执行权 t1抢到cpu的执行权,执行到if语句,失去了cou的执行权 t2睡醒了,抢到了cpu执行权,进行卖票,卖掉了最后一张票 t1睡醒了,抢到了cpu执行权,进行卖票,卖第0张票 t0睡醒了,抢到了cpu执行权,进行卖票,卖第-1张票 t0 t1 t2同时执行到正在卖第i张票,,这时候ticket还没有执行到-- 线程安全问题不能产生
同步代码块: synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
synchronized(锁对象){ 可能会出现线程安全问题的代码(访问了共享数据的代码) }
注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着
public class RunnableImpl implements Runnable{ private int ticket = 100; Object object = new Object(); @Override public void run() { while (true){ synchronized (object){ if (ticket>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票"); ticket--; } } } } } //main代码与上代码一致
同步锁原理 使用了一个锁对象,同步锁 3个线程一起抢夺cpu的执行权,谁抢到了,执行run方法卖票 t0抢到了cpu执行权,执行run方法,遇到synchronized代码块 这时t0会检查synchronized代码块中是否有锁对象 发现有,就会获取到锁对象,进入同步中执行 t1抢到了cpu执行权,执行run方法,遇到synchronized代码块 这时t0会检查synchronized代码块中是否有锁对象 发现没有,t1就会进入阻塞状态,会一直等待t0线程归还锁对象, 一直到t0线程执行完同步代码,会把锁对象归还给同步代码块 t1才能获取到锁对象进入同步中执行 同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步
静态的同步方法,锁对象是谁? 不能是this
this是创建对象之后产生的,静态方法优先于对象
静态方法的锁对象是本类的class属性-->class文件对象(反射)
public static synchronized void payTicket(){ 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接口 Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。 Lock接口中的方法: void lock()获取锁。 void unlock() 释放锁。 java.util.concurrent.locks.ReentrantLock implements Lock接口 使用步骤: 1.在成员位置创建一个ReentrantLock对象 2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁 3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
public class RunnableImpl implements Runnable{ private static int ticket = 100; Lock lock = new ReentrantLock(); @Override public void run() { while (true){ lock.lock(); if (ticket>0){ try { Thread.sleep(10); System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票"); ticket--; } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } } } } //main代码同上
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
TimedWaiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
<img src="https://raw.githubusercontent.com/FLVE/FigureBed/master/20200511004421.bmp" style="zoom:150%;" />
Timed Waiting在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。单独的去理解这句话,真是玄之又玄,其实我们在之前的操作中已经接触过这个状态了,在哪里呢?
在卖票的案例中,为了减少线程执行太快,现象不明显等问题,在run方法中添加了sleep语句,这样就强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。
其实当调用了sleep方法之后,当前执行的线程就进入到“休眠状态”,其实就是所谓的Timed Waiting(计时等待)。
Blocked状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。
比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
这是由Runnable状态进入Blocked状态。除此Waiting以及Time Waiting状态也会在某种情况下进入阻塞状态,。
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
同步使用的锁对象必须保证唯一
只有锁对象才能调用wait和notify方法
Obejct类中的方法 void wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。 void notify() 唤醒在此对象监视器上等待的单个线程。 会继续执行wait方法之后的代码
public class WaitAndNotifyDemo01 { public static void main(String[] args) { Object obj = new Object(); new Thread(){ @Override public void run() { while (true){ synchronized (obj){ System.out.println("告知老板要的包子的种类和数量"); 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(500); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj){ System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了"); obj.notify(); } } } }.start(); } }
进入到TimeWaiting(计时等待)有两种方式 1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态 2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态 唤醒的方法: void notify() 唤醒在此对象监视器上等待的单个线程。 void notifyAll() 唤醒在此对象监视器上等待的所有线程。
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
这是多个线程间的一种 协作 机制。谈到线程我们经常想到的是线程间的 竞争(race) ,比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。
就是在一个线程进行了规定操作后,就进入等待状态( wait() ), 等待其他线程执行完他们的指定代码过后 再将其唤醒( notify() );在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
总结如下:
<img src="https://raw.githubusercontent.com/FLVE/FigureBed/master/20200511083947.bmp" style="zoom:150%;" />
等待唤醒机制其实就是经典的“生产者与消费者”的问题。
就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:
包子铺线程生产包子,吃货线程消费包子。 当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。 接下来,吃货线程能否进一步执行则取决于锁的获取情况。 如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。 包子铺线程能否进一步执行则取决于锁的获取情况。
包子资源类:
public class BaoZi { //皮 String pi; //陷 String xian; //包子的状态: 有 true,没有 false,设置初始值为false没有包子 boolean flag = false; }
吃货线程类:
/** * @Author wangjiefang * @Date 9:01 2020/5/11 * @Description **/ /* 消费者(吃货)类:是一个线程类,可以继承Thread 设置线程任务(run):吃包子 对包子的状态进行判断 false:没有包子 吃货调用wait方法进入等待状态 true:有包子 吃货吃包子 吃货吃完包子 修改包子的状态为false没有 吃货唤醒包子铺线程,生产包子 */ public class ChiHuo extends Thread{ //1.需要在成员位置创建一个包子变量 private BaoZi bz; //2.使用带参数构造方法,为这个包子变量赋值 public ChiHuo(BaoZi bz) { this.bz = bz; } //设置线程任务(run):吃包子 @Override public void run() { //使用死循环,让吃货一直吃包子 while (true){ //必须同时同步技术保证两个线程只能有一个在执行 synchronized (bz){ //对包子的状态进行判断 if(bz.flag==false){ //吃货调用wait方法进入等待状态 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("----------------------------------------------------"); } } } }
包子铺线程类:
/** * @Author wangjiefang * @Date 9:01 2020/5/11 * @Description **/ /* 生产者(包子铺)类:是一个线程类,可以继承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){ //包子铺调用wait方法进入等待状态 try { 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+"包子"); //生产包子需要3秒钟 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //包子铺生产好了包子 //修改包子的状态为true有 bz.flag = true; //唤醒吃货线程,让吃货线程吃包子 bz.notify(); System.out.println("包子铺已经生产好了:"+bz.pi+bz.xian+"包子,吃货可以开始吃了"); } } } }
测试类:
public class Demo { public static void main(String[] args) { //创建包子对象; BaoZi bz =new BaoZi(); //创建包子铺线程,开启,生产包子; new BaoZiPu(bz).start(); //创建吃货线程,开启,吃包子; new ChiHuo(bz).start(); } }
执行效果:
包子铺开始做包子 包子造好了:冰皮五仁 吃货来吃吧 吃货正在吃冰皮五仁包子 包子铺开始做包子 包子造好了:薄皮牛肉大葱 吃货来吃吧 吃货正在吃薄皮牛肉大葱包子 包子铺开始做包子 包子造好了:冰皮五仁 吃货来吃吧 吃货正在吃冰皮五仁包子
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池。
由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:
合理利用线程池能够带来三个好处:
Java里面线程池的顶级接口是 java.util.concurrent.Executor
,但是严格意义上讲 Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 java.util.concurrent.ExecutorService
。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在 java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。
Executors类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量) 获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
public Future<?> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行
Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
使用线程池中线程对象的步骤:
线程池:JDK1.5之后提供的 java.util.concurrent.Executors:线程池的工厂类,用来生成线程池 Executors类中的静态方法: static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池 参数: int nThreads:创建线程池中包含的线程数量 返回值: ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程) java.util.concurrent.ExecutorService:线程池接口 用来从线程池中获取线程,调用start方法,执行线程任务 submit(Runnable task) 提交一个 Runnable 任务用于执行 关闭/销毁线程池的方法 void shutdown() 线程池的使用步骤: 1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池 2.创建一个类,实现Runnable接口,重写run方法,设置线程任务 3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法 4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
Runnable实现类代码:
public class RunnableImpl implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行"); } }
线程池测试类:
public class ThreadPool { public static void main(String[] args) { //1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池 ExecutorService es = Executors.newFixedThreadPool(2); //3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法 es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行 //线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用 es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行 es.submit(new RunnableImpl());//pool-1-thread-2创建了一个新的线程执行 //4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行) es.shutdown(); es.submit(new RunnableImpl());//抛异常,线程池都没有了,就不能获取线程了 } }