栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

多线程——初学总结

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程——初学总结

目录

一. 多线程概述

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


public static void sleep​(long millis, int nanos) throws 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);

        }
    }
}

运行结果:

Thread-0床前明月光0
main床前明月光0
Thread-0床前明月光1
main床前明月光1
Thread-0床前明月光2
main床前明月光2

3.interrupt标记线程中断
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)

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);

        }
    }
}

运行结果:
Thread-0床前明月光0
main床前明月光0
main床前明月光1
Thread-0床前明月光1
Thread-0床前明月光2
main床前明月光2
用户线程执行完毕,守护线程死亡

 四. 线程安全问题

1.线程不安全

买票代码

三个线程同时执行,如果电脑只有一个脑子(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);
        }
    }
}

运行结果:

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

2.线程安全 

1.同步代码块-synchronized
//格式    任何对象都可以当作锁对象
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){
            
            //同步代码块
            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.显示锁 -Lock
Lock 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();
        }
    }
}

运行结果:
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

4.显示锁与隐式锁的区别

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) {
        
        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 {
    
    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) {
    
        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 {
    

    public static void main(String[] args) {
    
        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) {
    
        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:床前明月光

以上是我初学多线程知识的总结,可能有些地方总结不到位,或者有点出入,欢迎大家指正补充 ,接下来的日子有时间我会继续更新其他的知识总结,希望能和大家一起共同进步。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/877698.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号