Java多线程笔记

线程简介(Process AND Thread)

1.任务,进程,线程,多线程

  • 1.1 任务
    • 1.1.1 多任务:
      多任务处理是指用户可以在同一时间内运行多个应用程序,每个应用程序被称作一个任务。
      现实生活中很多同时要做很多件时间的例子,例如:边吃饭边玩手机,边蹲厕所边玩手机,边听歌边跑步等等。看起来是多个任务都在做,其实本质上我们的大脑在同一时间依旧只做了一件事。
  • 1.2 多线程
    • 1.2.1 多线程
      在程序中同时运行多个线程,每个线程都是独立运行的,即有自己的执行路径、栈、寄存器等资源,并且可以同步地访问共享数据
      原本汽车在一条道路很窄,道路拥挤效率极低,但是为了提高效率,会添加车道。
  • 1.3 进程
    • 1.3.1 程序概念
      程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
    • 1.3.2 进程概念
      进程是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
  • 1.4 线程
    • 1.4.1 线程概念
      通常,一个进程可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。

本章核心概念

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

线程实现(重点)

线程创建(Thread、Runnable、Callable)

在这里插入图片描述

1.Thread class 继承Thread类(重点)
    1. 自定义线程类继承Thread类
    1. 重写run()方法,编写线程执行体
    1. 创建线程对象,调用start()方法启动线程
    1. 例:在这里插入图片描述
总结:注意

线程开启不一定立即执行,由CPU调度执行。执行的时候需要调用start()方法,不能直接调用run()方法。

案例:网图下载

使用多线程下载多个图片。

//  实现多线程下载图片
public class TestThread extends Thread{

    private String url; // 网络图片地址
    private String name; // 保存的文件名

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

    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downLoader(url,name);
        System.out.println("下载了文件名为:" + name );
    }

    public static void main(String[] args) {
        TestThread thread = new TestThread("https://img0.baidu.com/it/u=3663508195,2907650417&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1697043600&t=50896d6d3a067f36990a6a406401f13a","2.jpg");
        TestThread thread2 = new TestThread("https://img0.baidu.com/it/u=3663508195,2907650417&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1697043600&t=50896d6d3a067f36990a6a406401f13a","3.jpg");
        TestThread thread3 = new TestThread("https://img0.baidu.com/it/u=3663508195,2907650417&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1697043600&t=50896d6d3a067f36990a6a406401f13a","4.jpg");

        thread.start();
        thread2.start();
        thread3.start();
    }
}

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


    1. 效果展示:
      在这里插入图片描述
2.Runnable接口 实现Runnable接口(最为重点)
    1. 定义MyRunnable类实现Runnable接口
    1. 实现run()方法,编写线程执行体
    1. 创建线程对象,调用start()方法启动线程(执行线程需要丢入Runnable接口实现类,再调用start方法
    1. 如图:在这里插入图片描述
跟直接extend Thread的区别:

实现接口的时候再调用方面的区别,Runnable需要多一步把线程对象放到Thread里面

小结:注意
    1. 继承Thread类
    • 1.1 子类继承Thread类具备多线程能力
    • 1.2 启动线程:子类对象.start()
    • 1.3 不建议使用:避免OOP(面向对象程序设计(Object Oriented Programming))单继承的局限性。
    1. 实现Runnable接口
    • 2.1 实现接口Runnable具有多线程能力
    • 2.2 启动线程:传入目标对象+Thread对象.start()
    • 2.3 推荐使用:避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用在这里插入图片描述
    1. 买车票的例子(多个线程操作同一个资源的情况下,线程不安全,数据紊乱。)在这里插入图片描述
  • 现象(数据紊乱)在这里插入图片描述
案例:龟兔赛跑-Race

在这里插入图片描述

public class Race implements Runnable{
    // 胜利者
    private static String winner;
    @Override
    public void run() {
        // 来个100米的距离
        for (int i = 0; i <= 100; i++) {

            // 模拟兔子睡觉
            if (Thread.currentThread().getName().equals("兔子") && i%10==0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 判断比赛是否结束
            boolean flag = gameOver(i);
            // 如果比赛结束,则停止程序
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
        }
    }

    // 判断是否完成比赛
    public 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();
    }
}

  • 效果:在这里插入图片描述
3.Callable接口 实现Callable接口(学习阶段了解)
  • 1.在这里插入图片描述
案例:修改上面的图片下载
package com.dapeng.callcable;

import com.dapeng.Demo.TestThread;
import org.apache.commons.io.FileUtils;

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

/**
 * @Description 通过Callable接口创建线程
 * 好处:
 *  1. 可以自定义返回值
 *  2. 可以抛出异常
 * @Author zhaopeng
 * @Date 2023/10/10 16:51
 */
public class testCallable implements Callable<Boolean> {

    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 {
        testCallable t1 = new testCallable("https://img0.baidu.com/it/u=3663508195,2907650417&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1697043600&t=50896d6d3a067f36990a6a406401f13a","2.jpg");
        testCallable t2 = new testCallable("https://img0.baidu.com/it/u=3663508195,2907650417&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1697043600&t=50896d6d3a067f36990a6a406401f13a","3.jpg");
        testCallable t3 = new testCallable("https://img0.baidu.com/it/u=3663508195,2907650417&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1697043600&t=50896d6d3a067f36990a6a406401f13a","4.jpg");

        // 创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);
        // 提交执行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);

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

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

}

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


实现静态代理对比Thread
  • 总结:
    • 1.真实对象和代理对象都要事先同一个接口
      1. 代理对象要代理真实角色
  • 好处:
      1. 代理对象可以做很多真实对象做不了的事情
      1. 真实对象专注做自己的事情
package com.dapeng.staticProxy;

/**
 * @Description
 * @Author zhaopeng
 * @Date 2023/10/10 17:09
 */
// 总结:静态代理模式总结:
//    真实对象和代理对象都要事先同一个接口
//    代理对象要代理真实角色
//    好处:代理对象可以做很多真实对象做不了的事情
//          真实对象专注做自己的事情
public class staticProxy {
    public static void main(String[] args) {
        You you = new You(); // 真实对象
		// 通过对比发现,括号里面的都是接口或者实现接口的类。
        new Thread(()-> System.out.println("斤斤计较")).start();
        new WeddingCompany(new You()).HappyMarry();
//        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("结婚前~~~~收钱");
    }
}

补充:Lamda表达式
    1. Lamda表达式概念:如下图
      在这里插入图片描述
    1. 为什么要使用Lamda表达式:在这里插入图片描述
    1. 了解函数式接口:在这里插入图片描述
    1. 推导Lamda表达式:在这里插入图片描述在这里插入图片描述在这里插入图片描述
    1. Lamda简化:在这里插入图片描述在这里插入图片描述
Lamda小结:注意
    1. lamda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就用代码块包裹。
    1. 前提是接口为函数式接口。(只有一个方法的接口)
    1. 多个参数也可以去掉参数类型,要去掉都去掉,必须加上括号。

线程状态(5个)

    1. 线程状态:在这里插入图片描述在这里插入图片描述
    1. 线程的方法:在这里插入图片描述
    1. 停止线程:在这里插入图片描述
    • 3.1 小案例,让线程停止:
package com.dapeng.Demo;

/**
 * @Description 测试stop
 * 1. 建议线程正常停止----> 利用次数,不建议死循环
 * 2. 建议使用标志位----> 设置一个标志位
 * 3. 不要使用stop或者destory等过时或者JDK不建议使用的方法
 * @Author zhaopeng
 * @Date 2023/10/11 10:13
 */

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 < 1000; i++) {
            System.out.println("main" + i);
            if (i==100){
                // 调用stop的方法,切换标志位,让线程停止
                testStop.stop();
                System.out.println("线程停止了...");
            }
        }
    }
}

3.2 效果图:在这里插入图片描述

    1. 线程的休眠:(1000ms = 1s)在这里插入图片描述
    • 4.1 模拟网络延时和倒计时案例(写在一起了)
package com.dapeng.Demo;

/**
 * @Description
 * // 模拟网络延时: 放大问题的发生性
 * @Author zhaopeng
 * @Date 2023/10/11 10:34
 */
public class TestSleep implements Runnable{

    // 票数
    private int tickNums = 10;


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

    /**
     * 倒计时
     * @throws InterruptedException
     */
    public static void tenDown() throws InterruptedException {
        int num = 10;
        while (true){
            Thread.sleep(1000);
            System.out.println(num--);
            if (num<=0){
                break;
            }
        }
    }

    public static void main(String[] args) {
//        TestSleep ticket = new TestSleep();
//
//        new Thread(ticket,"小明").start();
//        new Thread(ticket,"老师").start();
//        new Thread(ticket,"黄牛").start();
        try {
            tenDown();
        } catch (InterruptedException e) {
            System.out.println("倒计时出错");
            e.printStackTrace();
        }
    }
}

    1. 线程的礼让:在这里插入图片描述
    • 5.1礼让测试Demo:
package com.dapeng.Demo;

/**
 * @Description
 * 测试礼让线程
 * 礼让不一定成功,看cpu心情
 * @Author zhaopeng
 * @Date 2023/10/11 10:50
 */
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() + "停止线程");
    }
}

    1. Join:在这里插入图片描述
    • 6.1 JoinDemo案例:
package com.dapeng.Demo;

/**
 * @Description
 * 测试Join方法
 * 想象为插队
 * @Author zhaopeng
 * @Date 2023/10/11 10:54
 */
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 == 200){
                thread.join();// 插队,main线程阻塞
            }
            System.out.println("main" + i);
        }
    }
}

效果图:
在这里插入图片描述
在这里插入图片描述

    1. 线程状态观测:在这里插入图片描述
    • 7.1 观察线程状态Demo:
package com.dapeng.Demo;

/**
 * @Description
 * @Author zhaopeng
 * @Date 2023/10/11 11:06
 */

public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println("要进入睡眠了");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("");
        });

        // 查看状态:
        Thread.State state = thread.getState();
        System.out.println(state); // NEW

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

        //只要线程不终止,就一直输出状态
        while (state != Thread.State.TERMINATED){
            Thread.sleep(100); // 睡眠0.1秒
            state = thread.getState(); // 更新线程状态
            System.out.println( "睡眠的状态:" + state); // 输入状态
        }

    }

}


效果:
在这里插入图片描述在这里插入图片描述

    1. 线程的优先级:在这里插入图片描述
    • 8.1 优先级Demo案例:
package com.dapeng.Demo;

/**
 * @Description
 * @Author zhaopeng
 * @Date 2023/10/11 11:32
 */
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,"t1");
        Thread t2 = new Thread(myPriority,"t2");
        Thread t3 = new Thread(myPriority,"t3");
        Thread t4 = new Thread(myPriority,"t4");
        Thread t5 = new Thread(myPriority,"t5");


        // 先设置优先级,再启动
        t1.start();

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

        t3.setPriority(Thread.MAX_PRIORITY);
        t3.start();

        t4.setPriority(4);
        t4.start();

        t5.setPriority(9);
        t5.start();
    }

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

效果:
在这里插入图片描述
性能倒置:优先级低的线程被cpu调度,但是优先级高的线程在等待(一般不会出现)

    1. 守护(daemon)线程:在这里插入图片描述
    • 9.1 守护线程案例Demo:
package com.dapeng.Demo;

/**
 * @Description
 * 守护线程测试
 * @Author zhaopeng
 * @Date 2023/10/11 11:48
 */
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you =new You();

        Thread godThread = new Thread(god,"god");
        godThread.setDaemon(true); // 默认是false表示用户线程,正常的线程都是用户线程。。。true表示守护线程

        godThread.start();

        new Thread(you,"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 = 0; i < 36500; i++) {
            System.out.println("你开心活着");
        }
        System.out.println("========== GoodBye 世界 ======");
    }
}

效果:在这里插入图片描述

线程同步(重点)

多个线程操作同一个资源

  • 1.并发:同一个对象被多个线程操作
    • 1.1 现实生活中,我们会遇到同一个资源,多人都想用的问题。比如食堂排队或者上厕所排队等,最原生的办法就是排队一个一个来。
    • 1.2 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这个时候我们就需要线程同步,线程同步就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
    1. 队列和锁:
      队列:简单来说就像去食堂排队,如果不排队就会乱了,所以需要依次排队。
      锁 :简单来说,一群人排队上厕所,排好队以后,占着厕所才能安心拉粑粑或者解手。
      形成同步的条件就是:队列加锁,可以解决线程的安全性。
    1. 线程同步:在这里插入图片描述
    1. 同步方法:在这里插入图片描述
    • 4.1同步方法的弊端:在这里插入图片描述
    • 4.2 同步块:在这里插入图片描述
    1. 死锁:在这里插入图片描述
    • 5.1 死锁Demo:
package com.dapeng.juctest;

/**
 * @Description 死锁:多个线程互相抱着对方需要的资源,然后形成僵持
 * @Author zhaopeng
 * @Date 2023/10/11 16:13
 */
public class DeadLock {
    public static void main(String[] args) {
        MakeUp makeUp = new MakeUp(0,"女孩");
        MakeUp makeUp2 = new MakeUp(2,"女孩222");
        
        makeUp.start();
        makeUp2.start();
    }
}

// 口红
class Lipstic {
}

// 镜子
class Mirror {
}

// 化妆
class MakeUp extends Thread {

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

    int choice; // 选择
    String girlname; // 女孩

    MakeUp(int choice, String girlname) {
        this.choice = choice;
        this.girlname = girlname;
    }

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

    private void makeUp() throws InterruptedException {
        if (choice == 0){
            synchronized (lipstic){// 获得口红锁
                System.out.println(this.girlname + "获得口红锁");
                Thread.sleep(1000);
                synchronized (mirror){
                    System.out.println(this.girlname + "获得镜子锁");
                }
            }
            // 解决办法,将里面的同步块拿出来
            // synchronized (mirror){
            //       System.out.println(this.girlname + "获得镜子锁");
            //   }
        }else {
            synchronized (mirror){// 获得口红锁
                System.out.println(this.girlname + "获得镜子锁");
                Thread.sleep(2000);
                synchronized (lipstic){
                    System.out.println(this.girlname + "获得口红锁");
                }
            }
            // 解决办法,将里面的同步块拿出来
            // synchronized (lipstic){
            //        System.out.println(this.girlname + "获得口红锁");
            //    }
        }
    }
}

效果:程序卡死。

死锁产生的条件【重点】
  • 死锁产生的四个必要条件:
      1. 互斥条件:一个资源每次只能被一个进程使用。
      1. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
      1. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
      1. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
        上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生
Lock锁
    1. Lock锁
      在这里插入图片描述
      可重入锁(ReentrantLock)
  • Demo:
package com.dapeng.juctest;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description
 * @Author zhaopeng
 * @Date 2023/10/11 16:40
 */
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 {

    // 票
    int tickNums = 10;
    // 定义Lock锁
    private  final  ReentrantLock lock = new ReentrantLock();

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

        }
    }
}

    1. 格式:
  • 在这里插入图片描述
    1. synchronized 和 Lock 的对比:在这里插入图片描述
三大不安全例子:
    1. 买票不安全Demo:
package com.dapeng.syn;

/**
 * @Description
 * @Author zhaopeng
 * @Date 2023/10/11 14:26
 */
public class UnsafeBuyTickets {
    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() {
        // 买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

  // synchronized 同步方法,锁的是this(本身这个类)。 在需要解决并发问题的时候需要加上synchronized
  // private synchronized void buy() throws InterruptedException
    private  void buy() throws InterruptedException {
        // 判断是否有票
        if (ticketNums <= 0){
            flag = false;
            return;
        }
//        模拟延时
        Thread.sleep(100);
        // 买票
        System.out.println(Thread.currentThread().getName()+"拿到了第" + ticketNums--+"张票");
    }
}


效果:
在这里插入图片描述

    1. 银行取钱不安全Demo:
package com.dapeng.syn;

/**
 * @Description
 * @Author zhaopeng
 * @Date 2023/10/11 14:38
 */
public class UnsafeBank {
    public static void main(String[] args) {
        // 账户
        Account account = new Account(100, "结婚基金");

        Drawing you = new Drawing(account,50,"You");
        Drawing wife = new Drawing(account,100,"Wife");

        you.start();
        wife.start();
    }
}

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

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

// 银行:模拟取款
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;
    }

    // 取钱
    @Override
    public void run() {
    // 如果想解决并发问题,通过添加同步块synchronized(account){}来解决并发问题
        // 判断有没有钱
        if (account.money - drawingMoney < 0){
            System.out.println(this.getName() + "钱不够了,取不了");
            return;
        }
        // 线程睡眠,放大问题发生性
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 卡内余额 = 余额 - 你去的钱
        account.money = account.money - drawingMoney;
        // 你手里的钱
        nowMoney = nowMoney + drawingMoney;

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

效果:
在这里插入图片描述

    1. 不安全的List集合Demo:
package com.dapeng.syn;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description
 *
 * // 线程不安全的集合(因为多个线程操作list的时候,可能会把同一个位置给覆盖掉)
 * @Author zhaopeng
 * @Date 2023/10/11 15:11
 */
public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
            // 通过添加同步代码块synchronized(list){}就会安全了
                list.add(Thread.currentThread().getName());
            }).start();
        }

        Thread.sleep(4000);
        System.out.println(list.size());
    }
}

效果:(长度不为10000,因为其中有的List集合某个下标被同时覆盖掉)
在这里插入图片描述

补充:JUC包下的CopyOnWriteArrayList
  • juc Demo:
package com.dapeng.juctest;

import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @Description
 * 测试JUC里面的安全集合
 * @Author zhaopeng
 * @Date 2023/10/11 15:46
 */
public class TestJuc {
    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayList<String> 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());
    }
}

线程通信问题

(生产者消费者模式):生产者(Producer) -----> 数据缓存区 -------> 消费者(Consumer)

    1. 线程通信-分析:在这里插入图片描述
    1. Java提供了几个方法解决线程之间的通信问题:在这里插入图片描述
  • 3.1 解决方式1:管程法:在这里插入图片描述
  • 管程法Demo:
package com.dapeng.juctest;

/**
 * @Description
 * 测试:生产者,消费者-----》利用缓冲区解决:管程法
 * 生产者,消费者,缓冲区,产品
 * @Author zhaopeng
 * @Date 2023/10/11 17:03
 */
public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();

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

// 生产者
class Producer extends Thread{
    SynContainer container;
    public Producer(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 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().i + "只鸡");

        }
    }
}

// 产品
class Chicken{
    int i ;
    public Chicken(int i){
        this.i = i;
    }
}

// 缓冲区
class SynContainer{

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

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

        // 如果没有满,我们就需要丢入产品
        chickens[count] = chicken;
        count++;
        // 可以通知消费者消费了
        System.out.println("生产者---------赶紧消费");
        this.notifyAll();
    }

    // 消费者消费产品
    public synchronized Chicken pop(){
        // 判断能否消费
        if (count == 0) {
            // 等待生产者生产,消费者等待
            try {
                System.out.println("消费者---------容器没了,赶紧生产");
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 如果可以消费,消费
        count--;
        Chicken chicken =  chickens[count];

        // 吃完了通知生产者生产
        System.out.println("消费者---------我吃完了,赶紧生产");
        this.notifyAll();
        return chicken;
    }
}

    1. 2 解决方式2:信号灯法
      在这里插入图片描述
      通过一个标志位来判断,当为ture则可以唤醒,false则等待。
      信号灯法Demo:
package com.dapeng.juctest;

/**
* @Description
* @Author zhaopeng
* @Date 2023/10/11 17:33
*/
public class TestPC2 {
   public static void main(String[] args) {
       Tv tv = new Tv();
       new Player(tv).start();
       new Watcher(tv).start();
   }
}

// 演员
class Player extends Thread{
   Tv tv;
   public Player(Tv tv){
       this.tv = tv;
   }

   @Override
   public void run() {
       for (int i = 0; i < 20; i++) {
           if (i%2 == 0){
               this.tv.play("喜羊羊与灰太狼");
           }else {
               this.tv.play("抖音,记录啊哈生活");
           }
       }
   }
}

// 观众
class Watcher extends Thread{
   Tv tv;
   public Watcher(Tv tv){
       this.tv = tv;
   }

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

// TV
class Tv{
   // 演员表演,观众等待T
   // 观众观看,演员等待F
   String voice; //节目
   boolean flag = true;

   // 表演
   public synchronized void play(String voice){
       // 如果为假,观众观看,演员等待
       if (!flag){
           try {
               this.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       System.out.println("演员表演了" + voice);
       // 通知观众观看,
       this.notifyAll();
       this.voice = voice;
       this.flag = !this.flag;
   }
   // 观看
   public synchronized void watch(){
       // 如果为真,演员表演,观众等待
       if (flag){
           try {
               this.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       // 通知观众表演
       this.notifyAll();
       System.out.println("观众观看了" + voice);
       this.flag = !this.flag;

   }
}

高级主题

    1. 使用线程池:在这里插入图片描述
      线程池Demo:
package com.dapeng.juctest;

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

/**
 * @Description
 * @Author zhaopeng
 * @Date 2023/10/11 17:54
 */
public class TestPool {
    public static void main(String[] args) {
        // 1. 创建服务,创建线程池
        // newFixedThreadPool 参数为:线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

        // 执行
        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() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +"--"+ i );
        }
    }
}

总结:

1. 线程创建(3种方式)

package com.dapeng.juctest;

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

/**
 * @Description 回顾线程的创建
 * @Author zhaopeng
 * @Date 2023/10/12 8:59
 */
public class TestNew {

    public static void main(String[] args) {
        new Thread1().start();
        new Thread(new Thread2()).start();

        FutureTask<String> futureTask = new FutureTask(new Thread3());
        new Thread(futureTask).start();

        try {
            String s = futureTask.get();
            System.out.println(s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

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

// 2. 实现Runnable接口
class Thread2 implements Runnable {

    @Override
    public void run() {
        System.out.println("Thread2");
    }
}

// 3. 实现Callable解耦
class Thread3 implements Callable<String> {


    @Override
    public String call() throws Exception {
        System.out.println("Thread3");
        return "Thread3";
    }
}

线程同步

    1. (JUC安全类型的包)concurrent包下面的一些安全类
    1. 可以通过加synchronized到需要改变的变量的方法或者同步代码块中
    1. 可通过使用JDK5的Lock锁,显式定义锁:private final ReentrantLock lock = new ReentrantLock();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱折磨键盘的大鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值