多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池

https://lishizheng.blog.csdn.net/article/details/114382518

https://blog.csdn.net/qq_36188127/article/details/108867650

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第1张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第2张图片

错别字改正:人为干预

线程就是独立的执行路径;

在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,GC 线程;

main()称之为主线程,为系统的入口,用于执行整个程序;

在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预。

对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;

线程会带来额外的开销,如 CPU 调度时间,并发控制开销。

每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第3张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第4张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第5张图片

package lishizheng.demo01;

//创建线程方式1:继承Thread类,重写run方法,调用start开启线程
public class TestThread1 extends  Thread {
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码:" + i);
        }
    }

    public static void main(String[] args) {
        //main线程,主线程
        //创建一个线程对象
        TestThread1 testThread1 = new TestThread1();

        //调用start方法开启线程
        testThread1.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("我在学习多线程: " + i);
        }
    }
}

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第6张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第7张图片

package lishizheng.demo01;

//创建线程方式2:实现Runnable接口,重写run方法,执行线程需要丢入实现Runnable接口的实现类
public class TestThread3 implements Runnable {
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码:" + i);
        }
    }

    public static void main(String[] args) {

       //创建Runnable接口的实现类对象
        TestThread3 testThread3 = new TestThread3();

        //创建线程对象,通过线程对象来开启我们的线程,代理
        Thread thread = new Thread(testThread3);

        thread.start();

        //等价写法:
        // new Thread(testThread3).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("我在学习多线程: " + i);
        }
    }
}

/*
运行代码其中一部分:可以发现线程的执行顺序是由CPU来调度的!!!并不完全按照代码书写的顺序。
我在学习多线程: 0
我在看代码:0
我在学习多线程: 1
我在学习多线程: 2
我在学习多线程: 3
我在学习多线程: 4
我在学习多线程: 5
我在学习多线程: 6
我在学习多线程: 7
我在学习多线程: 8
我在学习多线程: 9
我在学习多线程: 10
我在学习多线程: 11
我在学习多线程: 12
我在学习多线程: 13
我在学习多线程: 14
我在学习多线程: 15
我在看代码:1
我在学习多线程: 16
我在看代码:2
我在学习多线程: 17
我在看代码:3
我在学习多线程: 18

*/

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第8张图片

创建线程方式3,实现Callable接口

package com.spring.boot.entity;

import java.util.concurrent.Callable;

public class TestThread implements Callable{

	@Override
	public String call() throws Exception {
		return "实现Callable接口";
	}

}


package com.spring.boot.entity;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ProducerAndConsumer {
	public static void main(String[] args) {
		
		FutureTask futureTask = new FutureTask(new TestThread());
		new Thread(futureTask,"c").start();
		try {
			String s = futureTask.get();
			System.out.println(s);
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
		
	}
}


//打印
实现Callable接口

线程并发问题

火车抢票实例:多线程操作同一资源对象

下面的代码实现抢票的功能:共有10张火车票,三个人来抢。目的是学习并体会多线程对同一个资源对象操作的情况。

补充Thread.currentThread().getName()获取线程的名字

package com.sjmp.demo01;



// 多个线程同时操作同一个对象
// 买火车票的例子
// 发现问题:多个线程操作同一个资源,线程不安全,数据紊乱!

public class TicketRunnable implements Runnable{

    private int ticketNums = 10;

    @Override
    public void run() {
        while (true){
            if (ticketNums<=0){
                break;
            }
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketNums--;
            System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums+"票");

        }
    }

    public static void main(String[] args) {
//        实现了 Runnable 接口的类,创建其实例
        TicketRunnable ticketRunnable = new TicketRunnable();
//        ticketRunnable 实例可用于多个线程,其中的资源被共享。
        new Thread(ticketRunnable,"01小明+++++").start();
        new Thread(ticketRunnable,"02老师-----").start();
        new Thread(ticketRunnable,"03黄牛=====").start();

    }
}


03黄牛=====-->拿到了第8票
01小明+++++-->拿到了第8票
02老师------->拿到了第8票
03黄牛=====-->拿到了第7票
02老师------->拿到了第7票
01小明+++++-->拿到了第7票
02老师------->拿到了第6票
03黄牛=====-->拿到了第6票
01小明+++++-->拿到了第6票
01小明+++++-->拿到了第5票
03黄牛=====-->拿到了第4票
02老师------->拿到了第5票
03黄牛=====-->拿到了第3票
02老师------->拿到了第3票
01小明+++++-->拿到了第3票
02老师------->拿到了第1票
03黄牛=====-->拿到了第2票
01小明+++++-->拿到了第2票
02老师------->拿到了第0票
01小明+++++-->拿到了第-1票
03黄牛=====-->拿到了第-1票

案例:龟兔赛跑

模拟龟兔赛跑,两者在同一条跑道,先跑到100步者为胜利者。

用意:体会多线程竞争资源。

package lishizheng.demo01;

public class Race implements  Runnable{

    private static String winner;

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {

            //模拟兔子休息
            if(Thread.currentThread().getName().equals("兔子") && i % 30 == 0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean result = gameOver(i);

            if(result) break;

            System.out.println(Thread.currentThread().getName() + " 跑了 " + i + "步");
        }
    }

    //判断是否完成比赛
    private boolean gameOver(int steps){

        //已经存在胜利者
        if(winner != null){
            return  true;
        }
        if( steps >= 100){
            winner = Thread.currentThread().getName();
            System.out.println("Winner is : " + winner);
            return  true;
        }

        return  false;
    }

    public static void main(String[] args) {
        //new 一个赛道
        Race race = new Race();
        //两个线程同时竞争同一个赛道
        new Thread(race ,"兔子").start();
        new Thread(race ,"乌龟").start();

    }
}



/*
输出结果,其中一部分:
兔子 跑了 22步
乌龟 跑了 98步
兔子 跑了 23步
兔子 跑了 24步
乌龟 跑了 99步
兔子 跑了 25步
Winner is : 乌龟

Process finished with exit code 0

*/

总结:

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第9张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第10张图片

附加,关于Future、FutureTask深入了解:https://blog.csdn.net/javazejian/article/details/50896505

利用Callable修改上面下载图片案例:

package lishizheng.demo02;


import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

//线程创建3:实现Callable接口
/*
*
* */
public class TestCallable implements Callable {
    private String url; //网络图片地址
    private String name; //保存的文件名

    public TestCallable(String url, String name){
        this.url = url;
        this.name = name;
    }

    //下载图片线程的执行体
    @Override
    public Boolean call() {
        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.downloader(url,name);
        System.out.println("已下载文件名为:" + name + "的图片");
        return true;
    }


    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //确保图片ulr是正确的
        TestCallable t2 = new TestCallable("https://img-blog.csdnimg.cn/20210228094648502.png","我的博客图片02.png");
        TestCallable t3 = new TestCallable("https://img-blog.csdnimg.cn/20210303104522231.png","我的博客图片03.png");
        TestCallable t1 = new TestCallable("https://img-blog.csdnimg.cn/20210210194321356.png","我的博客图片01.png");

        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);

        //提交执行
        Future r1 = ser.submit(t1);
        Future r2 = ser.submit(t2);
        Future r3 = ser.submit(t3);

        //获取结果
        boolean res1 = r1.get();
        boolean res2 = r2.get();
        boolean res3 = r3.get();

        //关闭服务
        ser.shutdown();

    }
}


//下载器
class  WebDownLoader{
    //下载方法
    public void downloader(String  url, String  name){
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name)); //调用FileUtils类中的copyURLToFile方法实现下载
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常:downloader方法出现问题");
        }
    }
}

/*
已下载文件名为:我的博客图片01.png的图片
已下载文件名为:我的博客图片03.png的图片
已下载文件名为:我的博客图片02.png的图片

Process finished with exit code 0
*/

静态代理模式:

// 真实对象和代理对象到实现同一个接口

// 代理对象要代理真实对象

好处:代理对象可以做很多真实对象做不了的事情 ;真实对象专注于自己的事情

举例:下面通过婚庆公司代理你来组织婚礼来说明一下静态代理模式的功能。结婚之前要布置现场,然后主人结婚,结婚之后收尾款,这些方法都由婚庆公司对象来调用。

package lishizheng.demo04;

//静态代理模式

//真实对象和代理对象到实现同一个接口
//代理对象到代理真实对象

//好处:代理对象可以做很多真实对象做不了的事情 ;真实对象专注于自己的事情



public class StaticProxy {
    public static void main(String[] args) {
        You you = new You();
        WeddingCompany weddingCompany = new WeddingCompany(you);
        weddingCompany.happyMarry();
    }
}


interface Marry{
    void happyMarry();
}

class  You implements Marry{
    @Override
    public void happyMarry() {
        System.out.println("结婚,超开心");
    }
}


//代理角色
class WeddingCompany implements Marry{

    //代理谁? 真实目标角色
    private  Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void happyMarry() {
        before();
        this.target.happyMarry();//真实对象
        after();
    }

    private void after() {
        System.out.println("结婚之后收尾款");
    }

    private void before() {
        System.out.println("结婚之前布置现场");
    }
}


/*
输出结果:

结婚之前布置现场
结婚,超开心
结婚之后收尾款

Process finished with exit code 0
*/

和多线程有什么关系呢?

复习Thread调用方法的时候,原理是一样的,它本身是Runnable接口的代理。 下面通过婚庆公司和线程进行对比。

public class StaticProxy {
    public static void main(String[] args) {

        You you = new You();
        new WeddingCompany(you).happyMarry();



        //Runnable 是被代理的对象,Thread 是代理
        /*
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("----结婚----");
                }
            }).start();
        */

        //使用 lamda 表达式
        new Thread(()->{
            System.out.println("----结婚----");
        }).start();


    }
}

Lamda表达式

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第11张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第12张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第13张图片

Lamda表达式的演进:

package com.sjmp.demo02;



public class LamdaExpression {
//    3.2 实现函数式接口的第二种方法,静态内部类
    static class Like2 implements ILike{
        @Override
        public void lamda() {
            System.out.println("------3.2 静态内部类实现函数式接口-----");
        }
}


    public static void main(String[] args) {

//    3.1 实现函数式接口的第一种方法
        ILike like1 = new Like1();
        like1.lamda();
        System.out.println("--3.1 普通方法实现函数式接口--");

//    3.2 实现函数式接口的第二种方法,静态内部类
        new Like2().lamda();


//    3.3 局部内部类实现函数式接口
        class Like3 implements ILike{
            @Override
            public void lamda() {
                System.out.println("------3.3 局部内部类实现函数式接口--------");
            }
        }

        new Like3().lamda();

//    3.4 匿名内部类实现函数式接口

        new ILike() {
            @Override
            public void lamda() {
                System.out.println("------3.4 匿名内部类实现函数式接口----------");
            }
        }.lamda();



//      3.5 lamda 表达式实现函数式接口

        ILike like5 = ()->{
            System.out.println("--3.5 lamda 表达式实现函数式接口--");
        };
        like5.lamda();
    }

}

// 1. 定义一个函数式接口
interface ILike{
    void lamda();
}

// 2. 实现类
class Like1 implements ILike{
    @Override
    public void lamda() {

    }
}

线程状态:

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第14张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第15张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第16张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第17张图片

停止线程stop

  • 不推荐使用JDK提供的stop方法,destroy方法
  • 推荐让线程自己停止下来,建议使用标志位进行终止变量:当flag == false时,线程终止。

举例如下: 测试主线程和teststop线程的执行过程,运行期间让teststop停止。

package lishizheng.demo05;

//测试stop
//1.建议线程正常停止:利用此时,不建议死循环
//2.建议使用标志位
public class TestStop implements Runnable {

    // 1.设置一个标志位
    private boolean flag  = true;

    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println("run...Thread: " + i++);
        }
    }

    //2.设置公开的方法停止线程
    public void stop(){
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();

        for (int i = 0; i < 30; i++) {
            System.out.println("main " +i);
            if( i == 20){
                //调用stop方法切换线程标志位,让线程停止
                 testStop.stop();
                System.out.println("线程已停止");
            }
        }
    }
}

/*
输出结果:
main 0
run...Thread: 0
main 1
run...Thread: 1
main 2
run...Thread: 2
main 3
run...Thread: 3
main 4
main 5
run...Thread: 4
main 6
run...Thread: 5
main 7
main 8
main 9
main 10
main 11
main 12
main 13
main 14
main 15
main 16
main 17
main 18
main 19
main 20
run...Thread: 6
线程已停止
main 21
main 22
main 23
main 24
main 25
main 26
main 27
main 28
main 29

Process finished with exit code 0

*/

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第18张图片

线程休眠sleep

sleep指定当前线程阻塞的毫秒数,时间到后线程进入就绪状态

sleep存在异常InterruptedException,要抛出

sleep可以模拟网络延迟,倒计时等

每个对象都有一个锁,sleep不会释放锁。

举例:sleep模拟网络延时,发现代码的漏洞:这里是多线程操作同一个对象,造成线程不安全。通过sleep延时可以发现程序的执行过程并不是我们预计的那样。

package lishizheng.demo05;



//模拟网络延时:放大问题的可能性,容易发现问题
public class TestSleep implements Runnable{
    private int ticketNums = 10;

    @Override
    public void run() {

        while (true){

            if(ticketNums <= 0) break;
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println( Thread.currentThread().getName() + "拿到了第 " + ticketNums-- +"张票");
        }
    }

    public static void main(String[] args) {
        //线程不安全:多个线程操作同一个对象
        TestSleep testThread4 = new TestSleep();
        new Thread(testThread4, "小明").start();
        new Thread(testThread4,"老师").start();
        new Thread(testThread4,"黄牛").start();

    }
}

/*
运行结果:
黄牛拿到了第 9张票
老师拿到了第 8张票
小明拿到了第 10张票
老师拿到了第 7张票
小明拿到了第 7张票
黄牛拿到了第 6张票
黄牛拿到了第 5张票
小明拿到了第 4张票
老师拿到了第 5张票
小明拿到了第 3张票
黄牛拿到了第 1张票
老师拿到了第 2张票

Process finished with exit code 0
*/

模拟获取系统时间

package com.spring.boot.entity;

import java.text.SimpleDateFormat;
import java.util.Date;

public class DemoTest{

    public static void main(String[] args) throws InterruptedException {
    	//打印当前系统时间
        Date startTime   = new Date(System.currentTimeMillis());//获取系统当前时间

        while (true){
            Thread.sleep(1000);
			System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
			startTime   = new Date(System.currentTimeMillis());//更新时间
        }
    }

	
}

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第19张图片

package lishizheng.demo05;

//测试礼让线程
//礼让不一定成功,看CPU心情
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();

        new  Thread(myYield,"a").start();
        new  Thread(myYield,"b").start();
    }
}


class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
}

/*

a线程开始执行
b线程开始执行
a线程停止执行
b线程停止执行

Process finished with exit code 0
*/

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第20张图片

package lishizheng.demo05;

//测试join方法
//想象为插队

public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("线程vip来了:" +i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //启动我们的线程
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();

        //主线程
        for (int i = 0; i < 500; i++) {
            if( i == 100){
                thread.join(); //插队,主线程等待插队线程运行结束
            }
            System.out.println("main "+i);
        }
    }
}

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第21张图片

package lishizheng.demo05;

//观察测试线程的状态
public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() ->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("=============");
        });

        //观察状态
        Thread.State state = thread.getState();
        System.out.println("状态:"+ state);

        //观察启动后
        thread.start();
        state = thread.getState();
        System.out.println("启动后状态:"+state);

        while (state != Thread.State.TERMINATED){
            Thread.sleep(100);
            state = thread.getState();//更新线程状态
            System.out.println("现在的状态是:"+state);
        }
    }
}

/*
状态:NEW
启动后状态:RUNNABLE
现在的状态是:TIMED_WAITING
现在的状态是:TIMED_WAITING
现在的状态是:TIMED_WAITING
现在的状态是:TIMED_WAITING
现在的状态是:TIMED_WAITING
现在的状态是:TIMED_WAITING
现在的状态是:TIMED_WAITING
现在的状态是:TIMED_WAITING
现在的状态是:TIMED_WAITING
现在的状态是:TIMED_WAITING
=============
现在的状态是:TERMINATED

Process finished with exit code 0

*/

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第22张图片

package lishizheng.demo05;

public class TestPriority {
    public static void main(String[] args) {
        //主线程默认优先级
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);

        //先设置优先级
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();
    }
}

class MyPriority implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
    }
}

/*
main--->5
Thread-0--->5
Thread-2--->4
Thread-3--->10
Thread-1--->1

Process finished with exit code 0
*/

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第23张图片

package lishizheng.demo05;

//测试守护线程
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();


        Thread thread = new Thread(god);
        thread.setDaemon(true);//默认是false,表示是用户线程

        thread.start();//上帝线程

        new Thread(you).start();//用户线程启动


    }
}

class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("上帝保佑着你");
        }
    }
}

class  You implements  Runnable{

    @Override
    public void run() {
        for (int i = 1; i < 100; i++) {
            System.out.println("已经开心活过了"+ i +"年");
        }
        System.out.println("goodbye world");
    }
}

/*
部分运行结果
已经开心活过了92年
已经开心活过了93年
已经开心活过了94年
已经开心活过了95年
已经开心活过了96年
已经开心活过了97年
已经开心活过了98年
已经开心活过了99年
goodbye world
上帝保佑着你
上帝保佑着你
上帝保佑着你
上帝保佑着你
上帝保佑着你
Process finished with exit code 0


*/

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第24张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第25张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第26张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第27张图片

package lishizheng.demo06;

//不安全地买票
public class UnsafeBuyTicket {

    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket,"小米").start();
        new Thread(buyTicket,"小明").start();
        new Thread(buyTicket,"黄牛").start();
    }
}

class  BuyTicket implements Runnable{
    private  int ticketNums = 10;
    boolean flag = true;
    @Override
    public void run() {

        //模拟延时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //买票
        while (flag){
            buy();
        }
    }

    private void buy(){
        if(ticketNums <= 0) {
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName() + " 拿到" + ticketNums--);
    }
}

例子:线程不安全的集合,比如ArrayList,ArrayList为啥线程不安全,可以参考知乎:https://zhuanlan.zhihu.com/p/140374850

package com.sjmp.Concurrent;

import java.util.ArrayList;

/**
 * @author: sjmp1573
 * @date: 2020/11/17 22:22
 * @description:
 */

public class UnsafeList {
    public static void main(String[] args) {
        ArrayList list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}


9999

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第28张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第29张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第30张图片

锁的对象obj是变化的量,即需要增删改的对象。

import java.util.ArrayList;

public class UnsafeList {


    public static void main(String[] args) {
        ArrayList list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized(list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }


}


//10000

测试JUC线程安全的集合类:

package com.spring.boot.entity;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

//测试JUC安全类型的集合
public class TestJUC {
  public static void main(String[] args) throws InterruptedException {
      List list = new CopyOnWriteArrayList();

      for (int i = 0; i < 10000; i++) {
          new  Thread(() -> {
              list.add(Thread.currentThread().getName());
          }).start();
      }
      Thread.sleep(3000);
      System.out.println(list.size());
  }
}

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第31张图片

死锁举例 :

灰姑娘和白雪公主都喜欢化妆,这里的化妆需要镜子和口红两者都具备才能完成。 当灰姑娘拿到口红,而白雪拿到镜子的时候,两者相互等待,这样就构成死锁。

程序进入死锁(卡死),如下图:

package lishizheng.demo06;

//死锁:多个线程互相持有对方所需的资源,然后形成僵持。
public class DeadLock {
    public static void main(String[] args) {
        Makeup girl1 = new Makeup(0,"灰姑娘");
        Makeup girl2 = new Makeup(1,"白雪公主");

        //启动线程
        girl1.start();
        girl2.start();
    }
}

//口红
class  Lipstick{
}

//镜子
class  Mirror{
}

class Makeup extends Thread{

    //需要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice; // 选择
    String  girlName; //使用化妆品的人

    Makeup(int myChoice, String myGirlName){
        this.choice = myChoice;
        this.girlName = myGirlName;
    }

    @Override
    public void run() {
        //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    //化妆,互相持有对方的锁,即需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if(choice == 0){
            synchronized (lipstick){//获得口红的锁
                System.out.println(girlName + "获得口红的锁");
                Thread.sleep(1000);
                synchronized (mirror){
                    System.out.println(girlName + "获得镜子的锁");
                }
            }

        }
        else{
            synchronized (mirror){
                System.out.println(girlName + "获得镜子的锁");
                Thread.sleep(2000);
                synchronized (lipstick){
                    System.out.println(girlName + "获得口红的锁");
                }
            }
        }
    }
}

解决办法:

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第32张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第33张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第34张图片

package lishizheng.advance;

import java.util.concurrent.locks.ReentrantLock;

//测试Lock锁
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}

class TestLock2 implements  Runnable{

    private int ticketNums = 10;
    //定义lock锁
    //可重入锁 re + entrant + lock 
    private  final ReentrantLock lock = new ReentrantLock();



    @Override
    public void run() {
        while (true){
            try {
                lock.lock();//加锁
                if(ticketNums > 0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                }
                else break;

            }finally {
                //解锁
                lock.unlock();
            }


        }
    }
}

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第35张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第36张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第37张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第38张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第39张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第40张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第41张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第42张图片

注意:如果有多个生产者和消费者syso打印的内容会不正常,这里不能用if判断,而要用while,这个在后面JUC中会讲解。

源码用的都是while(){ }

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第43张图片

管程法:

package com.sjmp.advanced;

/**
 * @author: sjmp1573
 * @date: 2020/11/18 20:52
 * @description:
 */

// 生产者,消费者,产品,缓冲区
public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();

        new Productor(container).start();
        new Consumer(container).start();
    }

}

// 生产者
class Productor extends Thread{
    SynContainer container;
    public Productor(SynContainer container){
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("生产了"+i+"只鸡");
            container.push(new Chicken(i));
        }
    }
}


class SynContainer{
//    需要一个容器的大小
    Chicken[] chickens = new Chicken[10];
//    容器计数器
    int count = 0;


//    生产者放入产品
    public synchronized void push(Chicken chicken){
//        如果容器满了,就需要等待消费者消费
        if (count == chickens.length){
//            通知消费者消费,生产等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        chickens[count] = chicken;
        count++;
        this.notifyAll();
    }


//    消费者消费产品
    public synchronized Chicken pop(){
//        判断能否消费
        if(count==0){
//            等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
//        如果可以消费
        count--;
        Chicken chicken = chickens[count];
//        可以通知消费了
        this.notifyAll();

        return chicken;
    }
}



class Consumer extends Thread{
    SynContainer container;
    public Consumer(SynContainer container){
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了-->"+container.pop().id+"只鸡");
        }
    }
}



// 产品
class Chicken{
    int id;  //产品编号
    public Chicken(int id){
        this.id = id;
    }
}

信号灯法:加了个flag标志判断

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第44张图片

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第45张图片

package com.sjmp.advanced;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author: sjmp1573
 * @date: 2020/11/18 21:53
 * @description:
 */

public class TestPool {
    public static void main(String[] args) {
//        1.创建服务,创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
//        newFixedThreadPool 参数为线程池大小
//        执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

//        2.关闭连接
        service.shutdown();

    }
}
class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

个人扩展:阿里巴巴手册上不允许使用Executors 去创建

多线程详解(狂神),补充:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池_第46张图片

例子:以阿里巴巴推荐的使用 ThreadPoolExecutor 构造函数自定义参数的方式来创建线程池

import java.util.Date;

/**
 * 这是一个简单的Runnable类,需要大约5秒钟来执行其任务。
 * @author shuang.kou
 */
public class MyRunnable implements Runnable {

    private String command;

    public MyRunnable(String s) {
        this.command = s;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
        processCommand();
        System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
    }

    private void processCommand() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        return this.command;
    }
}
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolExecutorDemo {

    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 10;
    private static final int QUEUE_CAPACITY = 100;
    private static final Long KEEP_ALIVE_TIME = 1L;
    public static void main(String[] args) {

        //使用阿里巴巴推荐的创建线程池的方式
        //通过ThreadPoolExecutor构造函数自定义参数创建
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 0; i < 10; i++) {
            //创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
            Runnable worker = new MyRunnable("" + i);
            //执行Runnable
            executor.execute(worker);
        }
        //终止线程池
        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        System.out.println("Finished all threads");
    }
}

https://cloud.tencent.com/developer/article/1640010 

https://blog.csdn.net/weixin_39636857/article/details/111271174

线程池是一种池化技术,目的是避免线程频繁的创建和销毁带来的性能消耗。它是把已创建的线程放入“池”中,当有任务来临时就可以重用已有的线程,无需等待创建的过程,这样就可以有效提高程序的响应速度。

《Java 并发编程艺术》一书中提到使用线程池的好处:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的资源浪费。
  • 提高响应速度。当任务到达时,不需要等到线程创建就能立即执行。
  • 方便管理线程。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以对线程进行统一的分配,优化及监控。

https://www.cnblogs.com/zjstar12/archive/2012/02/06/2340581.html

https://www.cnblogs.com/zjstar12/archive/2012/02/06/2340581.html

 

 

你可能感兴趣的:(多线程详解狂神)