目录
一. 多线程概述
1.进程与线程
2.同步与异步
3.并发与并行
二. 实现多线程
1.通过继承Thread实现多线程
2.通过实现Runnable实现多线程
3.通过实现Callable实现多线程
三. Thread类常用方法
1.sleep(休眠)
2.currentThread调用当前线程的对象
3.interrupt标记线程中断
4.setDaemo设置守护线程
四. 线程安全问题
1.线程不安全
2.线程安全
1.同步代码块-synchronized
2.同步方法-synchronized
3.显示锁 -Lock
4.显示锁与隐式锁的区别
5.公平锁与非公平锁
五. 线程死锁问题
六. 线程通信问题
1.第一代代码(出现数据错乱)
2.第二代代码(出现重复 *** 作)
3.第三代代码(正确代码)
七. 线程的六种状态
八. 线程池
1.定长线程池
2.单线程线程池
3.缓存线程池
4.周期性线程池
九. Lambda表达式
一. 多线程概述 1.进程与线程
进程:进程是指一块内存中运行的应用程序(软件),现在有部分软件,一个软件就有多个进程,但是在学习过程中为了好理解,可以把一个进程当作一个独立工作的软件来理解。每个进程都有自己独立的空间(栈和堆),除非通过一些技术手段,否则两个进程之间没办法实现相互通信。
线程:线程是指一个程序中执行的任务,一个程序(进程)内有多个线程,每个线程都有自己独立的栈,共享堆内存,当一个进程中没有一个线程执行了,代表软件关闭,也就是说一个进程在执行,那么该进程中至少有一个线程在运行。
多线程:多线程就是指一个一个程序可以执行多个任务。
补充:多线程实际上是提高了运行效率,而不是速度,很多人在理解多线程时,都觉得多个任务执行是加快了速度,其实不然,原因在于:Java多线程的线程调度采用的是抢占式调度机制。
解释:你的电脑是六核处理器的,则有12个大脑(cpu),则意味着你的电脑实际上只能在同一时刻执行12个任务,然而电脑上执行的任务是远远大于这个数量的,所以每个任务就会抢占时间偏,只是电脑运算速度很快,抢占时间偏时间很短几乎可以忽略,然后造成的所有任务同时进行的假象。实际上是优先级越高的抢占到的几率越大,优先级相同则抢到几率一样。
一个小问题:1000个人 *** 作服务器是同时执行速度快,还是排队执行快?这个问题会产生一个误区,这个误区就是刚才补充的问题。会有人觉得同时执行快,实际上是排队快。因为1000个人同时 *** 作那么抢占时间偏那个过程也会花一点时间,因为要来回切换抢占,虽然可以忽略,但是在量很大的时候,也会占一点时间,所有实际上是排队,一个线程给它执行完快,但是效率低。
2.同步与异步同步:排队执行,效率低但安全
异步:同时执行,效率高但不安全
3.并发与并行并发:同一时间段执行
并行:同一时刻执行
误区:服务器有5000个任务并行,大脑才多少个,就5000个同时执行了?所以不对
二. 实现多线程 1.通过继承Thread实现多线程package com.java.test;
public class Test {
public static void main(String[] args) {
//创建子线程对象
MyThread myThread = new MyThread();
//开启子线程
myThread.start();
//主线程执行的任务
for (int i=0;i<3;i++){
System.out.println("疑是地上霜"+i);
}
}
}
class MyThread extends Thread{
@Override
//通过重写Thread类中的run方法来实现子线程要执行的任务
public void run() {
for (int i=0;i<3;i++){
System.out.println("床前明月光"+i);
}
}
}
运行结果:
床前明月光0
床前明月光1
疑是地上霜0
床前明月光2
疑是地上霜1
疑是地上霜2
小贴士:每次运行输出的结果顺序都不同,因为每次抢占时间偏不同。
2.通过实现Runnable实现多线程package com.java.test;
public class Test {
public static void main(String[] args) {
//创建任务对象
MyRunnable m = new MyRunnable();
//创建子线程,通过传入Runnable实例对象完成任务,没有重写Thread中的run方法
Thread t = new Thread(m);
//开启子线程
t.start();
//主线程任务
for (int i=0;i<3;i++){
System.out.println("疑是地上霜"+i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<3;i++){
System.out.println("床前明月光"+i);
}
}
}
运行结果:
床前明月光0
床前明月光1
疑是地上霜0
疑是地上霜1
疑是地上霜2
床前明月光2
小贴士:通过实现Runnable接口实现多线程达到了任务与线程分离,并且接口可以多实现,类不能多继承,所以使用Runnable有时候更方便,也不是不推荐使用Thread,根据需求进行 *** 作。
3.通过实现Callable实现多线程Runnable与Callable相同点:都是接口,都是创建Thread开启线程,
Runnable与Callable不同点:Callable中的call方法可以抛异常,使用Callable还可以通过创建
FutureTask 调用get方法获取返回值,使主线程等待子线程执行完毕再执行。
package com.java.test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test5 {
public static void main(String[] args) {
//创建Callable
Callable c = new MyCallable();
//创建FutureTask
FutureTask f = new FutureTask<>(c);
//开启子线程
new Thread(f).start();
try {
//调用get方法使主线程阻塞,等待子线程执行完毕,主线程才执行
Integer integer = f.get();
System.out.println("子线程执行完毕,获得返回值:"+integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//主线程任务
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":疑是地上霜"+i);
}
}
}
class MyCallable implements Callable{
@Override
public Integer call() throws Exception {
for (int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName()+":床前明月光"+i);
}
return 100;
}
}
运行结果:
Thread-0:床前明月光0
Thread-0:床前明月光1
Thread-0:床前明月光2
Thread-0:床前明月光3
Thread-0:床前明月光4
子线程执行完毕,获得返回值:100
main:疑是地上霜0
main:疑是地上霜1
main:疑是地上霜2
main:疑是地上霜3
main:疑是地上霜4
小贴士: 如果中间不调用f.get方法,则子线程和主线程还是抢占时间偏执行
三. Thread类常用方法 1.sleep(休眠)public static void sleep(long millis) throws InterruptedException
/*参数
millis - 以毫秒为单位的睡眠时间长度
异常
IllegalArgumentException - 如果 millis值为负数
InterruptedException - 如果有任何线程中断了当前线程。 抛出此异常时,将清除当前线程的中断状态 */
public static void sleep(long millis, int nanos) throws InterruptedException
/*参数
millis - 以毫秒为单位的睡眠时间长度
nanos - 0-999999睡觉的额外纳秒
异常
IllegalArgumentException - 如果 millis值为负数,或者值 nanos不在 0-999999范围内
InterruptedException - 如果有任何线程中断了当前线程。 抛出此异常时,将清除当前线程的中断状态*/
package com.java.test;
public class Test {
public static void main(String[] args) throws InterruptedException {
for (int i=0;i<3;i++){
System.out.println("床前明月光"+i);
Thread.sleep(1000);
}
}
}
运行结果:
床前明月光0
床前明月光1
床前明月光2
每隔1秒打印一次
2.currentThread调用当前线程的对象public static Thread currentThread()
/*返回对当前正在执行的线程对象的引用*/
package com.java.test;
public class Test {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+"床前明月光"+i);
}
}
}.start();
for (int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+"床前明月光"+i);
}
}
}
3.interrupt标记线程中断运行结果:
Thread-0床前明月光0
main床前明月光0
Thread-0床前明月光1
main床前明月光1
Thread-0床前明月光2
main床前明月光2
public void interrupt()
/*中断此线程。 */
package com.java.test;
public class Test {
public static void main(String[] args) {
//创建子线程
MyThread m = new MyThread();
//开启子线程
m.start();
for (int i=0;i<3;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"床前明月光"+i);
}
//子线程打上标记
m.interrupt();
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//发现标记后进入catch语句块,在该语句块选择释放资源结束子线程或者继续执行
System.out.println("主线程执行完毕,发现子线程终端标记,结束子线程");
return;
}
System.out.println(Thread.currentThread().getName()+"床前明月光"+i);
}
}
}
运行结果:
Thread-0床前明月光0
main床前明月光0
Thread-0床前明月光1
main床前明月光1
main床前明月光2
Thread-0床前明月光2
主线程执行完毕,发现子线程终端标记,结束子线程
小贴士:每当线程执行到wait,sleep,interrupt,interrupted方法时,都会判断该线程是否有中断标记,如果有则进入对应的catch语句块中执行释放资源或者其他代码。
4.setDaemo设置守护线程我们常说main方法是主线程,再开启其他的线程为子线程。而线程在程序中的真正划分为用户线程和守护线程。主线程死了子线程还再继续,程序仍然进行。主线程和子线程都是用户线程。守护线程不能掌握自己的生命,用户线程是自己决定死亡,而守护线程是看用户线程,用户线程死亡守护线程也跟着死亡,因此一般不建议在守护线程进行IO *** 作,可能导致资源没办法释放。
public final void setDaemon(boolean on)
/*将此主题标记为daemon线程或用户线程
参数
on - 如果为 true ,则将此线程标记为守护程序线程
异常
IllegalThreadStateException - 如果此主题是 alive
SecurityException - 如果 checkAccess()确定当前线程无法修改此线程*/
package com.java.test;
public class Test {
public static void main(String[] args) {
//创建子线程
MyThread m = new MyThread();
//设置用户线程
m.setDaemon(true);
//开启子线程
m.start();
for (int i=0;i<3;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"床前明月光"+i);
}
System.out.println("用户线程执行完毕,守护线程死亡");
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"床前明月光"+i);
}
}
}
四. 线程安全问题 1.线程不安全运行结果:
Thread-0床前明月光0
main床前明月光0
main床前明月光1
Thread-0床前明月光1
Thread-0床前明月光2
main床前明月光2
用户线程执行完毕,守护线程死亡
买票代码
三个线程同时执行,如果电脑只有一个脑子(cpu),那么当极限情况count只有一张的时候,ABC三个线程在执行count--前都先后进入了while循环,当一个线程控制count--,但是由于count变量只有一个,导致变成0了也会继续--并输出,则就是线程不安全
package com.java.test;
public class Test {
public static void main(String[] args) {
//创建一个买票的任务,只能创建一个任务,保证买的是共同的10张票
Ticket t = new Ticket();
//创建三个线程买这10张票
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class Ticket implements Runnable{
//10张票
private int counts = 10;
@Override
public void run() {
while (counts>0){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票成功
counts--;
System.out.println(Thread.currentThread().getName()+"售票,剩余票数:"+counts);
}
}
}
2.线程安全 1.同步代码块-synchronized运行结果:
Thread-2售票,剩余票数:7
Thread-1售票,剩余票数:7
Thread-0售票,剩余票数:7
Thread-2售票,剩余票数:6
Thread-1售票,剩余票数:4
Thread-0售票,剩余票数:4
Thread-2售票,剩余票数:3
Thread-0售票,剩余票数:1
Thread-1售票,剩余票数:1
Thread-2售票,剩余票数:0
Thread-1售票,剩余票数:-2
Thread-0售票,剩余票数:-1
//格式 任何对象都可以当作锁对象
synchroniezd(锁对象){}
package com.java.test;
public class Test {
public static void main(String[] args) {
//创建一个买票的任务,只能创建一个任务,保证买的是共同的10张票
Ticket t = new Ticket();
//创建三个线程买这10张票
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class Ticket implements Runnable{
//10张票
private int counts = 10;
//锁,不能写在run方法内,因为得保证锁唯一
private Object o = new Object();
@Override
public void run() {
while (true){
/*Object o = new Object();*/
//同步代码块
synchronized(o){
if (counts>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票成功
counts--;
System.out.println(Thread.currentThread().getName()+"售票,剩余票数:"+counts);
}else {
break;
}
}
}
}
}
运行结果:
Thread-0售票,剩余票数:9
Thread-0售票,剩余票数:8
Thread-0售票,剩余票数:7
Thread-0售票,剩余票数:6
Thread-0售票,剩余票数:5
Thread-0售票,剩余票数:4
Thread-0售票,剩余票数:3
Thread-0售票,剩余票数:2
Thread-0售票,剩余票数:1
Thread-0售票,剩余票数:0
小贴士:0线程拿到锁的几率更大,因为最先执行,并且当它先抢到后下次抢离锁更近,抢到的几率更大,就更容易连续抢到。我执行了很多遍一直都是0线程抢完了,不想再执行了,就放弃执行了,哈哈
2.同步方法-synchronized在需要同步 *** 作的方法前面加上synchronized修饰
synchronized的锁是调用这个方法的对象,如果这个方法被静态修饰,它的锁就是:类.class
执行synchronized修饰的方法也会进行排队,一次只能有一个线程执行由其修饰的方法,如果有多个方法被synchronized修饰,那么线程也只能有执行其中一个方法,其他的排队等待,(因为锁相同)即使另外的方法实现的功能不同
如果同步代码块锁了一段代码,同步方法锁了一段代码,同步代码块的锁的对象也是this(注意静态不能使用this),那么也是只能执行其中一个,一个线程执行,其他线程没办法执行同样锁的方法或者代码块
package com.java.test;
public class Test {
public static void main(String[] args) {
//创建一个买票的任务,只能创建一个任务,保证买的是共同的10张票
Ticket t = new Ticket();
//创建三个线程买这10张票
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class Ticket implements Runnable{
//10张票
private int counts = 10;
@Override
//执行买票任务
public void run() {
while (true){
boolean flag = sole();
if (!flag)
return;
}
}
//买票的同步方法
public synchronized boolean sole(){
if (counts>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票成功
counts--;
System.out.println(Thread.currentThread().getName()+"售票,剩余票数:"+counts);
return true;
}else {
return false;
}
}
}
运行结果:
Thread-0售票,剩余票数:9
Thread-0售票,剩余票数:8
Thread-0售票,剩余票数:7
Thread-0售票,剩余票数:6
Thread-0售票,剩余票数:5
Thread-0售票,剩余票数:4
Thread-0售票,剩余票数:3
Thread-0售票,剩余票数:2
Thread-0售票,剩余票数:1
Thread-0售票,剩余票数:0
小贴士: 如果在主线程中创建三个Ticket对象分别为t1,t2,t3,分别传入三个线程则没办法同步买票票,因为锁的对象不一样
3.显示锁 -LockLock l = new ReentrantLock();
package com.java.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
//创建一个买票的任务,只能创建一个任务,保证买的是共同的10张票
Ticket t = new Ticket();
//创建三个线程买这10张票
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class Ticket implements Runnable{
//10张票
private int counts = 10;
//自己创建的锁
Lock l = new ReentrantLock();
@Override
public void run() {
while (true){
//上锁
l.lock();
if (counts>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票成功
counts--;
System.out.println(Thread.currentThread().getName()+"售票,剩余票数:"+counts);
}else {
//解锁,如果这儿不解锁的话,始终有一个线程锁着
l.unlock();
break;
}
//解锁
l.unlock();
}
}
}
4.显示锁与隐式锁的区别运行结果:
Thread-2售票,剩余票数:9
Thread-2售票,剩余票数:8
Thread-2售票,剩余票数:7
Thread-2售票,剩余票数:6
Thread-2售票,剩余票数:5
Thread-2售票,剩余票数:4
Thread-2售票,剩余票数:3
Thread-2售票,剩余票数:2
Thread-2售票,剩余票数:1
Thread-2售票,剩余票数:0
synchronized修饰的为隐式锁,Lock创建的为显示锁
1.出身不同:synchronized是Java中的关键字,是jvm维护的,属于jvm层面。Lock是调动对应的API,是属于API层面的。
2.使用方式不同:synchronized是Java自助进行上锁解锁的,是由系统维护的,如果非逻辑问题的话话,是不会出现死锁的。Lock是人为的进行上锁解锁的,如果没有释放锁,就有可能导致出现死锁的现象。
3.等待是否可中断:synchronized是不可中断的。除非抛出异常或者正常运行完成。Lock可以中断的。
4.加锁的时候是否可以公平:synchronized:非公平锁。lock:两者都可以的。默认是非公平锁。在其构造方法的时候可以传入Boolean值。
5.唤醒:synchronized:要么随机唤醒一个线程;要么是唤醒所有等待的线程。Lock:用来实现分组唤醒需要唤醒的线程,可以精确的唤醒,而不是像sync那样,不能精确唤醒线程。
6.从性能比较:1.6之前使用Java提供的Lock对象,性能更高一些。1.6之后两者差不多
5.公平锁与非公平锁公平锁:先到的先执行
非公平锁:大家一起抢,谁抢到谁执行
上面所学的都是非公平锁,公平锁的创建:
//参数传入true则为公平锁,默认为非公平锁false
Lock l = new ReentrantLock(true);
五. 线程死锁问题
例子:罪犯警察对峙
如果主线程p.say执行调用c.fun之前,子线程也开始执行c.say,p等待c.say执行完,然后c.say中需要调用p.fun,c也在等待p的执行完,则造成死锁
不死锁的情况:主线程p执行完了,子线程才开始执行c.say(一般电脑性能越好越容易死锁)
避免死锁:最好在调用一个方法的时候产生了一个锁,就不要再在其中产生另一把锁
package com.java.test;
public class Test {
public static void main(String[] args) {
Police p = new Police();
Convict c = new Convict();
new Thread(new Runnable(){
@Override
public void run() {
c.say(p);
}
}).start();
p.say(c);
}
}
class Police{
public synchronized void say(Convict c){
System.out.println("警察说:你放了人质,我们就放过你");
c.result();
}
public synchronized void result(){
System.out.println("警察暂时放过了罪犯,成功解救了人质");
}
}
class Convict{
public synchronized void say(Police p){
System.out.println("罪犯说:你们放过我,我就放了人质");
p.result();
}
public synchronized void result(){
System.out.println("罪犯放了人质,暂时逃脱了");
}
}
六. 线程通信问题运行结果1:(死锁)
警察说:你放了人质,我们就放过你
罪犯说:你们放过我,我就放了人质运行结果2:(成功)
警察说:你放了人质,我们就放过你
罪犯放了人质,暂时逃脱了
罪犯说:你们放过我,我就放了人质
警察暂时放过了罪犯,成功解救了人质
生产者与消费者问题
1.第一代代码(出现数据错乱)package com.java.test;
public class Test4 {
public static void main(String[] args) {
Food f = new Food();
Cook c = new Cook(f);
Waiter w = new Waiter(f);
c.start();
w.start();
}
}
class Cook extends Thread{
private Food food;
public Cook(Food food){
this.food = food;
}
@Override
public void run() {
for (int i=0;i<100;i++){
if (i%2==0){
try {
//制作小米粥
food.setNamAanFla("小米粥","甜");
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
try {
//制作皮蛋瘦肉粥
food.setNamAanFla("皮蛋瘦肉粥","咸");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Waiter extends Thread{
private Food food;
public Waiter(Food food){
this.food = food;
}
@Override
public void run() {
for (int i=0;i<100;i++){
try {
//每隔100毫米端一次盘子
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
food.get();
}
}
}
class Food{
private String name;
private String flavour;
//制作食物
public void setNamAanFla(String name,String flavour) throws InterruptedException {
this.name = name;
//为了增大出错率
Thread.sleep(100);
this.flavour = flavour;
}
//端走食物
public void get(){
System.out.println("服务员端走:"+name+",味道:"+flavour);
}
}
运行结果:(截取部分结果)
服务员端走:皮蛋瘦肉粥,味道:咸
服务员端走:小米粥,味道:甜
服务员端走:皮蛋瘦肉粥,味道:甜
服务员端走:小米粥,味道:咸
造成现象原因:由于食物刚制作完名称,味道还没赋予,厨师线程丢掉时间偏,服务员抢到,就端走了食物,所以食物的味道是上一次的,就造成了数据错乱的现象
2.第二代代码(出现重复 *** 作)在第一代代码基础上给食物类中的设置获取方法前面加上隐式锁
class Food{
private String name;
private String flavour;
//制作食物
public synchronized void setNamAanFla(String name,String flavour) throws InterruptedException {
this.name = name;
//为了增大出错率
Thread.sleep(100);
this.flavour = flavour;
}
//端走食物
public synchronized void get(){
System.out.println("服务员端走:"+name+",味道:"+flavour);
}
}
运行结果:(部分截取)
服务员端走:小米粥,味道:甜
服务员端走:皮蛋瘦肉粥,味道:咸
服务员端走:小米粥,味道:甜
服务员端走:皮蛋瘦肉粥,味道:咸
服务员端走:皮蛋瘦肉粥,味道:咸
服务员端走:皮蛋瘦肉粥,味道:咸
造成现象原因:加了隐式锁,同步 *** 作,可能出现厨师生产完了,服务员没来的急端走,厨师继续生产和服务员刚端走,厨师没来的急生产,又把上次的端走,则出现重复的 *** 作
3.第三代代码(正确代码)通过使用Object类中的wait方法和notifyAll方法来使线程等待和唤醒,进行交替 *** 作
package com.java.test;
public class Test4 {
public static void main(String[] args) {
Food f = new Food();
Cook c = new Cook(f);
Waiter w = new Waiter(f);
c.start();
w.start();
}
}
class Cook extends Thread{
private Food food;
public Cook(Food food){
this.food = food;
}
@Override
public void run() {
for (int i=0;i<100;i++){
if (i%2==0){
try {
//制作小米粥
food.setNamAanFla("小米粥","甜");
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
try {
//制作皮蛋瘦肉粥
food.setNamAanFla("皮蛋瘦肉粥","咸");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Waiter extends Thread{
private Food food;
public Waiter(Food food){
this.food = food;
}
@Override
public void run() {
for (int i=0;i<100;i++){
try {
//每隔100毫米端一次盘子
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
food.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Food{
private String name;
private String flavour;
//用于第一次判断,保证先做饭,后上菜
private boolean flag = true;
//制作食物
public synchronized void setNamAanFla(String name,String flavour) throws InterruptedException {
if (flag){
this.name = name;
//为了增大出错率
Thread.sleep(100);
this.flavour = flavour;
flag = false;
//唤醒其他所有线程
this.notifyAll();
//该线程等待
this.wait();
}
}
//端走食物
public synchronized void get() throws InterruptedException {
if (!flag) {
System.out.println("服务员端走:" + name + ",味道:" + flavour);
flag = true;
//唤醒其他所有线程
this.notifyAll();
//该线程等待
this.wait();
}
}
}
七. 线程的六种状态运行结果:(部分截取)
服务员端走:小米粥,味道:甜
服务员端走:皮蛋瘦肉粥,味道:咸
服务员端走:小米粥,味道:甜
服务员端走:皮蛋瘦肉粥,味道:咸
服务员端走:小米粥,味道:甜
服务员端走:皮蛋瘦肉粥,味道:咸
服务员端走:小米粥,味道:甜
八. 线程池
如果并发的线程数量很多,并且每个线程都执行一个很短的时间任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁的创建线程和销毁线程需要时间,线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的 *** 作,节省了大量时间和资源。
好处:降低资源消耗。提高响应速度。提高线程的可管理性。
1.定长线程池//创建格式 i是该固定线程池的最大容纳线程个数
ExecutorService service = Executors.newFixedThreadPool(int i)
//执行
service.execute(Runnable实例);
package com.java.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test6 {
public static void main(String[] args) {
/**
* 定长线程池.
* (长度是指定的数值)
* 执行流程
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
});
}
}
运行结果:
pool-1-thread-1:执行完毕
pool-1-thread-2:执行完毕
pool-1-thread-1:执行完毕
由于设置的定长线程池最大容量为2,所以第三个任务是在重复使用已经存在的线程路径
2.单线程线程池//创建格式
ExecutorService service = Executors.newSingleThreadExecutor();
//执行
service.execute(Runnable实例);
package com.java.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test6 {
/**
*效果与定长线程池 创建时传入数值1 效果一致.
* 单线程线程池.
* 执行流程:
* 1. 判断线程池 的那个线程 是否空闲
* 2. 空闲则使用
* 4. 不空闲,则等待 池中的单个线程空闲后 使用
*/
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
});
}
}
运行结果:
pool-1-thread-1:执行完毕
pool-1-thread-1:执行完毕
pool-1-thread-1:执行完毕
单线程只有一个线程进行 *** 作,所以线程名都一致
3.缓存线程池//创建格式
ExecutorService service = Executors.newCachedThreadPool();
//执行
service.execute(Runnable实例);
package com.java.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test6 {
public static void main(String[] args) {
/**
* 缓存线程池.
* (长度无限制)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程 并放入线程池, 然后使用
*/
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
});
}
}
运行结果:
pool-1-thread-2:执行完毕
pool-1-thread-3:执行完毕
pool-1-thread-1:执行完毕
pool-1-thread-3:执行完毕
缓存线程,先进入三个任务执行,就创建了三个线程,中途休息1秒,有任务执行完毕,第四个任务进去拿已经创建了的线程执行任务
4.周期性线程池//定时执行格式:(i是线程最大容纳量)
ScheduledExecutorService service = Executors.newSheduledThreadPool(int i);
//执行
service.scheduled(runnable类型的任务,延迟执行时长数字,时长数字的单位);
package com.java.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Test6 {
/* 周期任务 定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*
* 周期性任务执行时:
* 定时执行, 当某个时机触发时, 自动执行某任务 .*/
public static void main(String[] args) {
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 延迟执行时长数字
* 参数3. 时长数字的单位
*/
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
},5, TimeUnit.SECONDS);
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
},3, TimeUnit.SECONDS);
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕");
}
},1, TimeUnit.SECONDS);
}
}
运行结果:(先后输出)
pool-1-thread-1:执行完毕
pool-1-thread-2:执行完毕
pool-1-thread-1:执行完毕
//周期执行格式:(i是线程最大容纳量)
ScheduledExecutorService service = Executors.newSheduledThreadPool(int i);
//执行
service.scheduledAtFixedRate(runnable类型的任务,延迟执行时长数字,周期时长数字,时长数字的单位);
package com.java.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Test6 {
public static void main(String[] args) {
/**
* 周期执行
* 参数1. runnable类型的任务
* 参数2. 时长数字(延迟执行的时长)
* 参数3. 周期时长(每次执行的间隔时间)
* 参数4. 时长数字的单位
*/
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕5");
}
},5, 5,TimeUnit.SECONDS);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕3");
}
},3, 3,TimeUnit.SECONDS);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行完毕2");
}
},2, 2,TimeUnit.SECONDS);
}
}
运行结果:(部分截取)
pool-1-thread-1:执行完毕2
pool-1-thread-2:执行完毕3
pool-1-thread-1:执行完毕2
pool-1-thread-2:执行完毕5
pool-1-thread-1:执行完毕3
小贴士:所有线程池的执行都会发现控制台没有关闭,程序仍然再执行,因为程序还再继续等待任务传入线程池,过一段时间会自动关闭。
九. Lambda表达式Lambda表达式只能用于实现功能接口,这些接口是具有单个抽象方法的接口。 lambda表达式无法实现具有两个抽象方法的接口。
执行一个简单的代码确需要一系列复杂哦的创建对象的步骤,这时候可以使用lambda表达式
lambda表达式就是将一个匿名内部类中的方法参数以及方法体保留下来 ,通过箭头指引
package com.java.test;
public class Test7 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":床前明月光");
}
}).start();
//Lambda表达式
new Thread(()->{
System.out.println(Thread.currentThread().getName()+":床前明月光");
}).start();
}
}
运行结果:
Thread-0:床前明月光
Thread-1:床前明月光
以上是我初学多线程知识的总结,可能有些地方总结不到位,或者有点出入,欢迎大家指正补充 ,接下来的日子有时间我会继续更新其他的知识总结,希望能和大家一起共同进步。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)