多线程——初学总结

多线程——初学总结,第1张

目录

一. 多线程概述

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

        }
    }
}

运行结果:

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)
/*将此主题标记为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);

        }
    }
}

运行结果:
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){
            /*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.显示锁 -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) {
        /**
        * 定长线程池.
        * (长度是指定的数值)
        * 执行流程
        * 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:床前明月光

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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/916728.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存