线程简介

任务

多任务:

多任务.png

进程和线程

程序、进程、线程

程序,进程,线程.png

多线程:

多线程.png

进程Process和线程Thread

  • 我们把一个任务叫做一个进程,浏览器是一个进程,音乐播放器是另一个进程。
  • 一些进程内部还需要同时执行多个子任务。例如,我们在使用Word时,Word可以让我们一边打字,一边进行拼写检查,同时还可以在后台进行打印,我们把这些子任务叫做线程
  • 进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。线程由CPU调度和执行。

注意*️:很多的多线程是模拟出来的,真正的多线程是指有多个CPU,即多核处理器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有了同时执行的错觉。

本章核心概念

  • 线程是独立的执行单元
  • 在程序运行时,即使自己没有创建线程,后台也会有多个线程,如主线程,gc线程。
  • main()叫做主线程,是系统的入口,用于执行整个程序。
  • 在一个进程中,如果开辟了多个线程,由调度器安排调度线程的运行,调度器是与操作系统紧密相关的,执行的先后顺序不能人为干预。
  • 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制。
  • 线程会带来额外的开销,如CPU调用时间,并发控制开销。
  • 每个线程在自己的工作内存中交互,内存控制不当会造成数据不一致。

线程实现【重点难点】

线程的三种创建方式:

创建线程的三种方式.png

继承Thread类【重点】

创建线程方法一.png

普通方法调用和多线程:

普通方法调用和多线程.png

多线程

代码实现:

package com.thread.demo01;

/*
    创建线程的方式一:继承Thread类
    1.继承Thread类
    2.重写run()方法
    3.调用start()方法,开启线程
注意:线程开启不一定立即执行,由CPU调度执行,无法人为干预
 */
public class ThreadTest01 extends Thread {
    @Override
    public void run() {
        //run()方法的线程体
        for (int i = 1; i <= 100; i++) { //执行100次循环
            System.out.println("我在看代码呀----" + i);
        }
    }

    public static void main(String[] args) {
        //main线程,是主线程

        //创建线程对象
        ThreadTest thread = new ThreadTest();
        //调用start()方法开启线程,现在两个线程同时执行
        thread.start();

        for (int i = 1; i <= 500; i++) {
            System.out.println("我在学习多线程----" + i);
        }
    }
}

效果:多个线程交替执行

image-20211230102010496

单线程:先执行完run()方法再执行后面,按顺序执行,只有一个线程

代码实现:

package com.thread.demo01;

/*
    创建单线程的方式一:继承Thread类
    1.继承Thread类
    2.重写run()方法
    3.调用run()方法
注意:线程开启不一定立即执行,由CPU调度执行,无法人为干预
 */

public class ThreadTest extends Thread {
    @Override
    public void run() {
        //run()方法的线程体
        for (int i = 1; i <= 100; i++) {
            System.out.println("我在看代码呀----" + i);
        }
    }

    public static void main(String[] args) {
        //main线程,是主线程

        //创建线程对象
        ThreadTest thread = new ThreadTest();
        thread.run(); //先执行run()方法再执行后面,按顺序执行,只有一个线程

        for (int i = 1; i <= 500; i++) {
            System.out.println("我在学习多线程----" + i);
        }
    }
}

效果:

image-20211230101422461

应用:使用多线程下载图片

下载图片.png

谷歌搜索并下载Commons IO包,把commons-io.jar包复制到项目中,需要把包Add as Library添加到类库。

代码实现:

package com.thread.test;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

/*
    使用多线程同时下载图片
 */
public class ThreadTest extends Thread {
    private String url;
    private String name;

    //构造器
    public ThreadTest(String url, String name) {
        this.url = url; //网络图片地址
        this.name = name; //保存的文件名
    }

    //下载图片的线程体
    @Override
    public void run() {
        WebDownload webDownload = new WebDownload(); //创建下载器对象
        webDownload.download(url, name); //下载文件
        System.out.println("下载的文件名为:" + name);
    }

    public static void main(String[] args) {
        //创建线程对象
        ThreadTest thread01 = new ThreadTest("https://img.syt5.com/2021/0607/20210607042123800.jpg.420.554.jpg", "1.jpg");
        ThreadTest thread02 = new ThreadTest("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTRWvlbwRLHLSUMxDKEw3S8yJjN5UqrnZlDvQ&usqp=CAU", "2.jpg");
        ThreadTest thread03 = new ThreadTest("https://n.sinaimg.cn/sports/2_img/upload/0b43a5e3/83/w750h933/20190704/3596-hzfekep7254308.jpg", "3.jpg");
        ThreadTest thread04 = new ThreadTest("https://lh3.googleusercontent.com/proxy/ZkdwKWp3f4XlSpjTCKDqjWs5Fy80NOA-FB3t-vwCUKWX_CVlKc5LceE1Z3elABY_byjVRym3Q1hHRPnLxS5ycS0C490-MUSkCFLLQR3J9yfAkD34EttiUfK-NLrG", "4.jpg");
        ThreadTest thread05 = new ThreadTest("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS_iysqZXf0MRZtsm5GpUw_SvxCSnZrUFsaqQ&usqp=CAU", "5.jpg");

        //开启线程
        //理想执行顺序是:先下载t1,然后t2,最后t3
        //实际执行顺序是:t1,t3,t2,每次执行顺序可能都不一样
        thread01.start();
        thread02.start();
        thread03.start();
        thread04.start();
        thread05.start();
    }
}

//下载器
class WebDownload {
    //下载方法
    public void download(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("文件下载失败");
        }
    }
}

实现Runnable接口【重点】

创建线程方式二,实现Runnable接口(推荐使用)

package com.thread.demo01;

/*
    使用多线程
    创建线程方法二:实现Runnable接口
    1、实现Runnable接口,重写run()方法
    2、创建Thread对象需要传入runnable接口,通过Thread对象开启线程
 */
public class ThreadTest implements Runnable {

    //编写线程体
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println("我在看代码呀----" + i);
        }
    }

    public static void main(String[] args) {
        //创建实现类对象
        ThreadTest threadTest = new ThreadTest();

        //创建Thread对象,通过Thread对象开启线程
        Thread thread = new Thread(threadTest); //需要一个runnable接口
        thread.start();
//        new Thread(thread03).start(); //可读性差

      	//主线程的500次循环
        for (int i = 1; i <= 500; i++) {
            System.out.println("我在学习多线程呀----" + i);
        }
    }
}

效果:多个线程之间交替执行

image-20211230110646155

线程实现小结:

创建线程小结.png

应用:多个线程同时操作同一个对象

问题:多个线程操作同一个资源的情况下,线程不安全,数据混乱!

多次运行看结果!

package com.thread.test;

/*
    多个线程同时操作同一个对象
    场景:买火车票
    发现问题:多个线程操作同一个资源的情况下,线程不安全,数据混乱!
 */
public class ThreadTest implements Runnable{
    //火车票数量
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) { //没有余票的情况
                break;
            }
            try { //有票的情况
                //线程睡眠,0.2秒
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //获取线程信息
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "-->拿到了第" + ticketNums-- + "张票");
        }
    }

    public static void main(String[] args) {
        //创建实现类对象
        ThreadTest ticketThread = new ThreadTest();

        //创建Thread对象
        Thread thread01 = new Thread(ticketThread, "小明");
        Thread thread02 = new Thread(ticketThread, "小红");
        Thread thread03 = new Thread(ticketThread, "黄牛");
        thread01.start(); //开启线程
        thread02.start();
        thread03.start();
    }
}

执行结果:产生抢夺问题,造成数据不安全

image-20211230115117546

应用:龟兔赛跑

龟兔赛跑.png

package com.thread.demo01;

/*
    使用多线程-模拟龟兔赛跑
 */
public class Race implements Runnable{
    //胜利者
    private static String winner;

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            //模拟兔子休息
            if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) { //当i能被10整除时,线程睡眠一次
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //判断比赛是否结束
            boolean flag = gameOver(i);
            //如果比赛结束了,就停止程序
            if (flag) {
                break;
            }

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

    //判断是否完成比赛
    private boolean gameOver(int steps) {
        //判断是否有胜利者
        if (winner != null) { //已经有胜利者了
            return true;
        } else {
            if (steps == 100) {
                winner = Thread.currentThread().getName();
                System.out.println("winner is " + winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();

        new Thread(race, "兔子").start();
        new Thread(race, "乌龟").start();
    }
}

实现Callable接口【了解】

Callable接口.png

使用Callable的好处:

1.可以定义返回值类型

2.可以抛出异常

package com.thread.test;

import org.apache.commons.io.FileUtils;

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

/*
    使用多线程
    方法3:实现Callable接口
    1、实现Callable接口,重写call()方法
    2、创建实现类对象
    3、创建执行服务的接口
 */
public class CallableTest implements Callable<Boolean> {
    private String url;
    private String name;

    public CallableTest(String url, String name) {
        this.url = url; //网络图片地址
        this.name = name; //保存的文件名
    }

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

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建实现类对象
        CallableTest thread01 = new CallableTest("https://img.syt5.com/2021/0607/20210607042123800.jpg.420.554.jpg", "1.jpg");
        CallableTest thread02 = new CallableTest("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTRWvlbwRLHLSUMxDKEw3S8yJjN5UqrnZlDvQ&usqp=CAU", "2.jpg");
        CallableTest thread03 = new CallableTest("https://n.sinaimg.cn/sports/2_img/upload/0b43a5e3/83/w750h933/20190704/3596-hzfekep7254308.jpg", "3.jpg");
        CallableTest thread04 = new CallableTest("https://lh3.googleusercontent.com/proxy/ZkdwKWp3f4XlSpjTCKDqjWs5Fy80NOA-FB3t-vwCUKWX_CVlKc5LceE1Z3elABY_byjVRym3Q1hHRPnLxS5ycS0C490-MUSkCFLLQR3J9yfAkD34EttiUfK-NLrG", "4.jpg");
        CallableTest thread05 = new CallableTest("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS_iysqZXf0MRZtsm5GpUw_SvxCSnZrUFsaqQ&usqp=CAU", "5.jpg");

        //创建执行服务的接口
        ExecutorService ser = Executors.newFixedThreadPool(5);

        //提交执行
        Future<Boolean> r1 = ser.submit(thread01);
        Future<Boolean> r2 = ser.submit(thread02);
        Future<Boolean> r3 = ser.submit(thread03);
        Future<Boolean> r4 = ser.submit(thread04);
        Future<Boolean> r5 = ser.submit(thread05);

        //获取结果
        Boolean rs1 = r1.get();
        Boolean rs2 = r2.get();
        Boolean rs3 = r3.get();
        Boolean rs4 = r4.get();
        Boolean rs5 = r5.get();

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

//下载器
class WebDownload {
    //下载方法
    public void download(String url, String name) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,download方法出现问题");
        }
    }
}

静态代理模式

事例:

静态代理.png

1.Marry接口

package com.thread.test;

//结婚接口
public interface Marry {
    /*
    人生四大喜事
        久旱逢甘露
        他乡遇故知
        洞房花烛夜
        金榜题名时
     */

    void HappyMarry();
}

2.You类,新郎新娘

package com.thread.test;

//实现Marry接口,编写抽象方法的具体实现
//角色:新郎新娘
public class You implements Marry {
    @Override
    public void HappyMarry() {
        System.out.println("要结婚了,很开心!");
    }
}

3.WeddingCompany类,婚庆公司

package com.thread.test;

//实现Marry接口,编写抽象方法的具体实现
//角色:婚庆公司,功能:代理角色,帮助你结婚
public 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 before() {
        System.out.println("结婚之前,布置现场");
    }

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

4.StaticProxy类,静态代理模式

package com.thread.test;

/*
    静态代理模式:
    真实对象和代理对象都要实现同一个接口
    代理对象要代理真实角色

    好处:
    代理对象可以做很多真实对象做不了的事情,在代理对象中添加真实对象没有的方法
    真实对象可以专注做自己的事情
    可以做的事 范围:代理对象>真实对象
 */
public class StaticProxy {
    public static void main(String[] args) {
        You you = new You(); // 你要结婚
        WeddingCompany weddingCompany = new WeddingCompany(you); //婚庆公司代帮助你结婚
        weddingCompany.HappyMarry(); //调用婚庆公司的方法,在里面调用你的方法!
    }
}

Lambda表达式【难点】

Lamda表达式1.png

Lamda表达式2.png

Lamda表达式3.png

使用Lambda表达式的前提是有函数式接口。

函数式接口的定义:如果一个接口中,只有一个抽象方法,那这个接口就是函数式接口。

推导Lambda表达式

1.Like接口,是一个函数式接口

package com.thread.test;

//函数式接口:接口中只有一个抽象方法
public interface Like {
    void lambda();
}

2.ILike类,实现Like接口

package com.thread.test;

//实现接口,重写抽象方法
public class ILike implements Like {
    @Override
    public void lambda() {
        System.out.println("i like lambda");
    }
}

3.LambdaTest类,推导Lambda表达式

package com.thread.test;

/*
    推导Lambda表达式
 */
public class LambdaTest {
    public static void main(String[] args) {
        Like like = new ILike(); //接口指向实现类,多态
        like.lambda(); //本质是调用实现类重写的方法

        Like like2 = new ILike2();
        like2.lambda();

        //2.局部内部类
        class ILike3 implements Like {
            @Override
            public void lambda() {
                System.out.println("局部内部类----i like lambda3");
            }
        }
        Like like3 = new ILike3();
        like3.lambda();

        //3.匿名内部类,没有类的名称,必须借助接口或者父类
        Like like4 = new Like() {
            @Override
            public void lambda() {
                System.out.println("匿名内部类----i like lambda4");
            }
        };
        like4.lambda();

        //4.用Lambda简化
        Like like5 = ()-> {
            System.out.println("使用Lambda表达式----i like lambda5");
        };
        like5.lambda();
    }

    //1.静态内部类
    //实现接口,重写抽象方法
    static class ILike2 implements Like {
        @Override
        public void lambda() {
            System.out.println("静态内部类----i like lambda2");
        }
    }
}

Lambda表达式简化

  • Lambda表达式在只有一行代码的情况下才能简写成一行,如果有多行,就必须用代码块{}括起来。
  • 使用Lambda表达式的前提,接口必须是函数式接口
  • 多个参数也可以去掉参数类型,要去掉就都去掉,参数必须加上括号()

代码实现

1.Love接口,是一个函数式接口

package com.thread.test;

//函数式接口:接口中只有一个抽象方法
public interface Love {
    void love(int num);
}

2.ILove类,实现Love接口

package com.thread.test;

//实现接口,重写抽象方法
public class ILove implements Love {
    @Override
    public void love(int num) {
        System.out.println("i love code" + num);
    }
}

3.LambdaTest类,演示简化Lambda表达式的步骤

package com.thread.test;

/*
    推导Lambda表达式
 */
public class LambdaTest {
    public static void main(String[] args) {
        Love love = new ILove(); //接口指向实现类,多态
        love.love(1); //本质是调用实现类重写的方法

        Love love2 = new ILove2();
        love2.love(2);

        //2.局部内部类
        class ILove3 implements Love {
            @Override
            public void love(int num) {
                System.out.println("局部内部类----i love code" + num);
            }
        }
        Love love3 = new ILove3();
        love3.love(3);

        //3.匿名内部类,没有类的名称,必须借助接口或者父类
        Love love4 = new Love() {
            @Override
            public void love(int num) {
                System.out.println("匿名内部类----i love code" + num);
            }
        };
        love4.love(4);

        //4.用Lambda简化
        Love love5 = (int num)-> {
            System.out.println("使用Lambda表达式----i love code" + num);
        };
        love5.love(5);

        //简化1:去掉参数类型
        Love love6 = (num)-> {
            System.out.println("使用Lambda表达式----i love code" + num);
        };
        love6.love(6);
        //简化2:去掉括号()
        Love love7 = num-> {
            System.out.println("使用Lambda表达式----i love code" + num);
        };
        love7.love(7);
        //简化3:去掉花括号{}
        Love love8 = num-> System.out.println("使用Lambda表达式----i love code" + num);
        love8.love(8);

        /*
            总结:
            Lambda表达式在只有一行代码的情况下才能简写成一行,如果有多行,就必须用代码块{}括起来。
            使用Lambda表达式的前提,接口必须是函数式接口
            多个参数也可以去掉参数类型,要去掉就都去掉,参数必须加上括号()
         */

    }

    //1.静态内部类
    //实现接口,重写抽象方法
    static class ILove2 implements Love {
        @Override
        public void love(int num) {
            System.out.println("静态内部类----i love code" + num);
        }
    }
}

线程状态【重点】

线程五大状态:

线程五大状态1.png

线程五大状态2.png

线程方法

线程方法.png

停止线程

停止线程.png

package com.thread.test;

/*
    停止线程
    1.建议线程正常停止--->利用次数,不建议死循环。
    2.使用标志位--->设置一个标志位(推荐)
    3.不要使用stop或者destroy等过时方法,以及JDK不建议使用的方法

    思路:当标志位为false的时候,停止执行线程
    1、提供一个改变标志位Boolean值的方法
    2、在循环中使用标志位作为判断条件,停止循环也就停止线程
 */
public class ThreadStop implements Runnable {

    //设置一个标志位
    private Boolean flag = true;

    @Override
    public void run() {
        int i = 1;
        while (flag) {
            System.out.println("我在看代码鸭----" + (i++));
        }
    }

    //提供一个改变标志位的公有方法
    public void flagStop() {
        this.flag = false;
    }

    public static void main(String[] args) {
        //创建实现类对象
        ThreadStop threadStop = new ThreadStop();

        //创建Thread对象
        Thread thread = new Thread(threadStop);
        thread.start(); //开启线程

        //主线程执行1000次循环,要求到500次时停止线程
        for (int i = 1; i <= 1000; i++) {
            System.out.println("我在写bug鸭----" + i);

            if (i == 500) {
                threadStop.flagStop(); //调用stop()方法改变标志位,让线程停止
                System.out.println("线程已停止~");
            }
        }

    }

}

线程休眠

线程休眠.png

模拟网络延时:

package com.thread.test;

/*
    线程休眠
    示例:模拟网络延时,抢火车票
    模拟网络延时的作用:放大问题的发生性,本质是多个线程对同一个资源进行操作
 */
public class ThreadSleep implements Runnable {

    //火车票数量
    private int ticketNums = 10;

    //编写线程体
    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) { //没有余票的情况
                break;
            }
            //线程睡眠,模拟延时
            try { //有票的情况
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //获取线程信息
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "-->拿到了第" + ticketNums-- + "张票"); //买票操作,票数-1
        }
    }

    public static void main(String[] args) {
        //创建实体类对象
        ThreadSleep threadSleep = new ThreadSleep();

        //创建Thread对象
        Thread thread01 = new Thread(threadSleep, "小李");
        Thread thread02 = new Thread(threadSleep, "小陈");
        Thread thread03 = new Thread(threadSleep, "黄牛");
        thread01.start(); //开启线程
        thread02.start();
        thread03.start();
    }
}

模拟倒计时:

package com.thread.test;

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

/*
    线程休眠
    示例:模拟倒计时

    思路:10秒倒计时,每过一秒就递减打印一个数,到0秒的时候结束。
 */
public class ThreadSleep {

    public static void main(String[] args) {
        ThreadSleep threadSleep = new ThreadSleep();
        threadSleep.timeDown(10); //调用倒计时方法,设置倒计时秒数

        //打印当前系统时间
        Date date = new Date(System.currentTimeMillis()); //获取系统当前时间
        //创建SimpleDateFormat对象
        String formatDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
        System.out.println(formatDate);
    }

    //倒计时方法
    public void timeDown(int num) {
        int count = num;
        while (true) {
            if (num <= 0) { //到0秒的情况
                System.out.println(count + "秒倒计时结束~");
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(num--);
        }
    }
    
}

线程礼让

线程礼让.png

多运行几次看结果

代码实现:

1.MyYield类,执行线程礼让操作

package com.thread.test;

//线程礼让
public class MyYield implements Runnable {
    @Override
    public void run() {
      	//i等于3时,执行线程礼让操作
        for (int i = 0; i < 5; i++) {
            if (i == 3) {
                System.out.println("当前的线程是:   " + Thread.currentThread().getName());
                Thread.yield(); //线程礼让
            }
            System.out.println("执行的线程是:" + Thread.currentThread().getName());
        }
    }
}

2.ThreadYield类

package com.thread.test;

/*
    线程礼让
    礼让不一定成功,看CPU心情
 */
public class ThreadYield {
    public static void main(String[] args) {
        //创建实现类对象
        MyYield myYield = new MyYield();

        //创建Thread对象
        Thread thread01 = new Thread(myYield, "迪丽热巴");
        Thread thread02 = new Thread(myYield, "古力娜扎");
        thread01.start();
        thread02.start();
    }
}

运行结果:

image-20211230175746704

线程插队

线程插队.png

package com.thread.test;

/*
    线程插队join()
    可以想象成插队
 */
public class ThreadJoin implements Runnable {
    
    //编写线程体
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("线程vip来了" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //创建实现类对象
        ThreadJoin threadJoin = new ThreadJoin();
        
        //创建Thread对象,开启线程
        Thread thread = new Thread(threadJoin);
        thread.start(); //开启线程

        //主线程
        for (int i = 0; i < 50; i++) {
            if (i == 5) {
                thread.join(); //线程插队
            }
            System.out.println("main" + i);
        }
    }
}

运行结果:

一旦线程开始插队就会一直执行,直到完成它的操作,才会让其他进程执行。

image-20211230194818741

查看线程状态

线程状态.png

线程五大状态2.png

package com.thread.test;

/*
    查看线程的状态
 */
public class ThreadState implements Runnable {
    public static void main(String[] args) throws InterruptedException {

        //创建实现类对象
        ThreadState threadState = new ThreadState();
        //创建Thread对象,开启线程
        Thread thread = new Thread(threadState);
        //观察线程状态
        Thread.State state = thread.getState();
        System.out.println(state); //NEW

        //观察启动后
        thread.start();
        state = thread.getState();
        System.out.println(state); // RUNNABLE

        // TIMED_WAITING,TERMINATED,BLOCKED,WAITING
        while (state != Thread.State.TERMINATED) { //只要线程不终止,就一直输出状态
            Thread.sleep(100);
            state = thread.getState(); //更新线程状态
            System.out.println(state); //输出线程状态
        }
    }

    //编写线程体
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(".......");
    }
}

线程优先级

线程优先级.png

守护线程

守护线程.png

代码实现

1.God类,守护进程

package com.thread.test;

//上帝,模拟守护线程
public class God implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("上帝守护着你!");
        }
    }
}

2.You类,用户进程

package com.thread.test;

//你,用户进程
public class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("一生都要开心的活着!");
        }
    }
}

3.ThreadDaemon类,设置守护进程

package com.thread.test;

/*
    守护线程
    示例:上帝守护着你
 */
public class ThreadDaemon {
    public static void main(String[] args) {

        //创建实体类对象
        God god = new God();
        You you = new You();

        //创建Thread对象,开启线程
        //创建守护进程
        Thread threadDaemon = new Thread(god);
        threadDaemon.setDaemon(true); //默认是false表示用户线程,正常的线程都是用户线程
        threadDaemon.start();

        //创建用户进程
        Thread threadUser = new Thread(you);
        threadUser.start(); //开启用户进程

    }
}

线程同步【重点难点】

多个线程操作同一个资源

线程同步机制

并发

并发1.png

线程同步

线程同步.png

线程同步1.png

队列和锁

队列和锁.png

三大不安全事例

同步方法

同步方法.png

同步方法的弊端

同步方法的弊端.png

同步块

同步块.png

同步方法:

1.BuyTicket类,定义买票操作

package com.thread.test;

//买票操作
//思路:设置总票数,每买一张票总票数-1。如果有余票就执行买票操作,否则退出买票程序
public class BuyTicket implements Runnable {
    //火车票数量
    private int TicketNums = 10;
    //标志位
    private Boolean flag = true;

    //编写线程体
    @Override
    public void run() {
        buy();
    }

    //因为数据不安全,所以加上 synchronized同步方法
    //synchronized 同步方法,锁的是this,BuyTicket对象
    //一个线程独占buy()方法,等它用完,其他线程才能使用buy()方法
    public synchronized void buy() {
        while (flag) {
            if (TicketNums <= 0) { //没有余票的情况
                flag = false; //改变标志位停止循环
                System.out.println("所有车票已经售光");
                break;
            }
            //模拟延时
            try { //有票的情况
                Thread.sleep(100); //线程休眠
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //买票操作
            System.out.println(Thread.currentThread().getName() + "拿到第" + TicketNums-- + "张票");
        }
    }
}

2.ThreadUnsafeBuyTicket类,程序入口

package com.thread.test;

/*
    解决“不安全的买票”问题
    使用synchronized 同步方法
 */
public class ThreadUnsafeBuyTicket {
    public static void main(String[] args) {
        //创建实现类对象
        BuyTicket buyTicket = new BuyTicket();
        //创建Thread对象,开启线程
        Thread thread01 = new Thread(buyTicket, "机智如我");
        Thread thread02 = new Thread(buyTicket, "乱抢的你们");
        Thread thread03 = new Thread(buyTicket, "可恶的黄牛");
        thread01.start();
        thread02.start();
        thread03.start();
    }
}

运行结果:没有加synchronized 同步方法的情况

image-20211231090424190

运行结果:加了synchronized 同步方法的情况

image-20211231093243934

同步块:

1.Account类,银行账户信息

package com.thread.test;

//银行账户
public class Account {
    int money; //余额
    String name; //卡名

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}

2.Drawing类,取款操作

package com.thread.test;

//银行:模拟取款
//思路:取款操作,一个账户会减钱,另一个账户会加钱
public class Drawing extends Thread {
    Account account;
    int drawingMoney; //取钱的金额
    int nowMoney; //手上的金额

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //编写线程体
    //取钱操作,用 synchronized代码块,默认锁的是this
    @Override
    public void run() {

        //同步块
        //锁的对象,就是变化的量
        synchronized (account) {
            //判断有没有钱
            if (account.money - drawingMoney < 0) {
                System.out.println(Thread.currentThread().getName() + "钱不够,取不了");
                return;
            }
            //模拟延时,可以放大问题的发生性
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //卡内余额 = 余额 - 你取的钱
            account.money = account.money - drawingMoney;
            //你手里的钱
            nowMoney = nowMoney + drawingMoney;

            System.out.println(account.name + "余额为:" + account.money);
            System.out.println(this.getName() + "手里的钱:" + nowMoney);
//        System.out.println(Thread.currentThread().getName() + "手里的钱:" + nowMoney);
        }

    }
}

3.ThreadUnsafeBank,程序入口

package com.thread.test;

/*
    解决“不安全的取钱”问题
    事例:两个人去银行取钱,需要账户
 */
public class ThreadUnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(10000, "结婚基金");

        //你
        Drawing you = new Drawing(account, 500, "你");
        Drawing girlfriend = new Drawing(account, 1000, "girlfriend");
        you.start();
        girlfriend.start();

    }
}

运行结果:

一个账户取钱操作完,另一个账户才能去取钱

image-20211231101229796

死锁【重点难点】

死锁.png

避免死锁的方法

死锁避免方法.png

1.Lipstick类

package com.thread.test;

//口红
public class Lipstick {
}

2.Mirror类

package com.thread.test;

//镜子
public class Mirror {
}

3.Makeup类,

package com.thread.test;

//化妆
public class Makeup extends Thread {

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

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

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

    @Override
    public void run() {
        //化妆
        makeup();
    }

    //化妆,互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() {
        if (choice == 0) {
            synchronized (lipstick) { //获得口红的锁
                System.out.println(this.girlName + "获得口红的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

//                synchronized (mirror) { //一秒钟后想获得镜子的锁
//                    System.out.println(this.girlName + "获得镜子的锁");
//                }
            }
            //把同步块拿出来,就可以解决死锁问题
            synchronized (mirror) { //一秒钟后想获得镜子的锁
                System.out.println(this.girlName + "获得镜子的锁");
            }
        } else {
            synchronized (mirror) { //获得镜子的锁
                System.out.println(this.girlName + "获得镜子的锁");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
//
//                synchronized (lipstick) { //一秒钟后想获得口红的锁
//                    System.out.println(this.girlName + "获得口红的锁");
//                }
            }
            synchronized (lipstick) { //一秒钟后想获得口红的锁
                System.out.println(this.girlName + "获得口红的锁");
            }
        }
    }
}
package com.thread.test;

/*
    死锁
    死锁:多个线程互相持有对方需要的资源,然后形成僵持
 */
public class DeadLock {
    public static void main(String[] args) {
        Makeup girl1 = new Makeup(0, "亚丝娜");
        Makeup girl2 = new Makeup(1, "加藤惠");

        girl1.start();
        girl2.start();
    }
}

Lock(锁)【重点难点】

Lock(锁).png

Lock(锁)2.png

Lock(锁)3.png

可重入锁ReentrantLock

代码实现:

1.Lock类,定义买票操作,给线程加锁/解锁

package com.thread.test;

import java.util.concurrent.locks.ReentrantLock;

public class Lock implements Runnable {
    //火车票数量
    int ticketNums = 10;

    //定义Lock锁
    private final ReentrantLock lock = new ReentrantLock();

    //编写线程体
    @Override
    public void run() {
        while (true) {

            try {
                //加锁
                lock.lock();
                if (ticketNums > 0) { //有票的情况
                    try {
                        Thread.sleep(100); //线程休眠
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "抢到第" + ticketNums-- + "张票");
                } else { //没有余票的情况
                    System.out.println(Thread.currentThread().getName() + "所有车票已售光");
                    break;
                }
            }  finally {
                //解锁
                lock.unlock();
            }

        }
    }
}

2.LockTest类,程序入口

package com.thread.test;

//使用ReentrantLock,给线程加锁/解锁,解决数据不安全问题
public class LockTest {
    public static void main(String[] args) {
        //创建实现类对象
        Lock lock = new Lock();

        //创建Thread对象,开启线程
        Thread thread01 = new Thread(lock);
        Thread thread02 = new Thread(lock);
        thread01.start();
        thread02.start();
    }
}

运行结果:

image-20211231105055432

线程通信【重点难点】

生产者消费者问题

生产者消费者.png

线程同步分析.png

线程通信1.png

线程通信解决方法1.png

线程通信解决方法2.png

管程法

线程通信解决方法1.png

1.Chicken类,产品

package com.thread.test;

//产品
public class Chicken {
    int id; //产品编号

    public Chicken(int id) {
        this.id = id;
    }
}

2.SynContainer类,缓冲区

package com.thread.test;

//缓冲区
public 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;
    }
}

3.Producer类,生产者

package com.thread.test;

//生产者
public class Producer extends Thread {
    SynContainer container;

    public Producer(SynContainer container) {
        this.container = container;
    }

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

4.Consumer类,消费者

package com.thread.test;

//消费者
public class Consumer extends Thread {
    SynContainer container;

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

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

5.PCTest类,程序入口

package com.thread.test;

/*
    生产者消费者模型-->利用缓冲区解决:管程法
    要素:生产者,消费者,产品,缓冲区
 */
public class PCTest {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();

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

运行结果:

image-20211231111355357

信号灯法

线程通信解决方法2.png

代码实现

1.Program类,产品–>节目

package com.thread.test;

//产品-->节目
public class Program {
    //演员表演,观众等待 true
    //观众观看,演员等待 false
    String voice; //表演的节目
    boolean flag = true; //标志位

    //表演,对于演员来说
    public synchronized void play(String voice) {

        if (!flag) { //flag为false的情况
            try {
                this.wait(); //演员等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //flag为true的情况
        System.out.println("演员表演了:" + voice);
        //通知观众观看
        this.notifyAll(); //通知唤醒
        this.voice = voice;
        this.flag = !this.flag;
    }

    //观看,对于观众来说
    public synchronized void watch() {
        if (flag) { //flag为true的情况
            try {
                this.wait(); //观众等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了:" + voice);
        //通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

2.actor类,生产者–>演员

package com.thread.test;

//生产者-->演员
public class actor extends Thread {
    Program program; //产品

    public actor(Program program) {
        this.program = program;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i % 10 == 0) {
                this.program.play("广告-抖音,记录美好生活");
            } else {
                this.program.play("正在为你播放:延禧攻略");
            }
        }
    }
}

3.audience类,消费者–>观众

package com.thread.test;

//消费者-->观众
public class audience extends Thread {
    Program program; //产品

    public audience(Program program) {
        this.program = program;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            program.watch();
        }
    }
}

4.PCTest类,程序入口

package com.thread.test;

/*
    生产者消费者模型2-->利用标志位解决:信号灯法
 */
public class PCTest {
    public static void main(String[] args) {
        //创建实现类对象
        Program program = new Program();

        //创建Thread对象,开启线程
        actor threadActor = new actor(program);
        audience threadAudience = new audience(program);
        threadActor.start(); //开启线程
        threadAudience.start();
    }
}

线程池【重点】

线程池.png

线程池2.pngd

代码实现

1.MyThreadPool类,编写线程体

package com.thread.test;

//线程池
public class MyThreadPool implements Runnable {

    //编写线程体
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

2.ThreadPoolTest类,线程池

package com.thread.test;

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

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

        service.execute(new MyThreadPool()); //需要一个Runnable接口
        service.execute(new MyThreadPool());
        service.execute(new MyThreadPool());
        service.execute(new MyThreadPool());

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

运行结果:

image-20211231114041929

多线程总结

线程实现的三个方式,锁

package com.thread.demo09;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/*
    线程实现的三个方式
 */
public class AllInOne {
    public static void main(String[] args) {
      	//1
        new MyThread1().start();

      	//2
        new Thread(new MyThread2()).start();

      	//3
        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread3());
        new Thread(futureTask).start();
        try {
            Integer integer = futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

//1.继承Thread类
class MyThread1 extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread1");
    }
}

//2.实现Runnable接口
class MyThread2 implements Runnable {
    @Override
    public void run() {
        System.out.println("MyThread2");
    }
}

//3.实现Callable接口
class MyThread3 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("MyThread3");
        return 100;
    }
}

转自:https://www.cnblogs.com/hongchengg/p/15239547.html