Java_Thread学习【个人笔记/已完结】

跟着狂神大大学习线程啦!操作系统课上学过一些,所以这一部分应该很快就能学完的!

一、线程简介

1.1 程序、进程、线程

程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

进程:进程是程序的一次执行过程,是一个动态的概念。它是系统资源分配的单位。

线程:通常一个进程中包含若干个线程。线程是 CPU 调度和执行的单位。

注:一个 CPU 在同一个时间点,只能执行一个线程,但由于切换的快,会产生多个线程同时执行的错觉。

二、线程实现(重点)

三种线程创建方式

  • Thread class:继承 Thread 类
  • Runnable 接口:实现 Runnable 接口
  • Callable 接口:实现 Callable 接口

2.1 Thread 类

使用 Thread 类创建线程步骤如下:

  • 自定义线程类继承 Thread 类
  • 重写 run () 方法,编写线程执行体
  • 创建线程对象,调用 start () 方法启动线程

run () 方法与 start () 方法区别(图 from 狂神说):
请添加图片描述
调用 start () 方法的代码部分:

public class TestThread01 extends Thread{
    @Override
    public void run() {
        //run方法体线程
        for (int i = 0; i < 20; i++) {
            System.out.println("我是run方法体线程" + i);
        }
    }

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

        //创建一个线程对象
        TestThread01 testThread01 = new TestThread01();

        //调用start()方法开启线程
        testThread01.start();

        for (int i = 0; i < 20; i++) {
            System.out.println("我是main方法体线程" + i);
        }
    }
}

部分输出结果显示:
在这里插入图片描述
由此可以发现,使用 start () 方法,两个线程是并行交替执行的。

调用 run () 方法的代码部分 (不要用!只是展示一下和 start () 方法的区别!)

public class TestThread01 extends Thread{
    @Override
    public void run() {
        //run方法体线程
        for (int i = 0; i < 20; i++) {
            System.out.println("我是run方法体线程" + i);
        }
    }

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

        //创建一个线程对象
        TestThread01 testThread01 = new TestThread01();

        //调用run()方法开启线程
        testThread01.run();

        for (int i = 0; i < 20; i++) {
            System.out.println("我是main方法体线程" + i);
        }
    }
}

部分输出结果显示:
在这里插入图片描述
由此可见,调用 run () 方法后,CPU 先去执行 run () 线程,再去执行主线程。(因为 run () 现在在前调用的。若 run () 线程在主线程输出之后调用,则会先执行主线程的输出,再执行 run () 中的输出)

总结:线程开启不一定执行,由 CPU 调度执行。

2.2 实现 Runnable 接口

实现 Runnable 接口创建线程步骤如下:

  • 定义 MyRunnable 类实现 Runnable 接口
  • 重写 run () 方法,编写线程执行体
  • 创建线程对象,调用 start () 方法启动线程

代码部分:

public class TestThread02 implements Runnable{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我是run方法线程体" + i);
        }
    }

    //main方法线程体
    public static void main(String[] args) {
        //创建Runnable接口实现类
        TestThread02 testThread02 = new TestThread02();
        
        //创建线程对象,通过线程对象来开启线程 --> 代理
        new Thread(testThread02).start();

        for (int i = 0; i < 20; i++) {
            System.out.println("我是main方法线程体" + i);
        }
    }
}

部分运行结果显示:
在这里插入图片描述
总结:

① 继承 Thread 类

  • 子类继承 Thread 类具备多线程能力
  • 启动线程:子类对象 . start ()
  • 不建议使用:避免 OOP 单继承局限性

② 实现 Runnable 接口

  • 实现接口 Runnable 具备多线程能力
  • 启动线程:传入目标对象 + Thread 对象 . start ()
    new Thread (目标对象) . start ()
  • 推荐使用:避免单继承的局限性,灵活方便,方便同一个对象被多个线程调用

2.3 多个线程操作同一个对象(案例:模拟买票)

案例:模拟买票

public class TestThread03 implements Runnable{
    //票数
    private int tickets = 10;

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

    public static void main(String[] args) {

        TestThread03 testThread03 = new TestThread03();

        new Thread(testThread03,"张三").start();
        new Thread(testThread03,"李四").start();
        new Thread(testThread03,"王五").start();
    }
}

运行结果显示:
在这里插入图片描述
但是可能会存在问题:多个线程在操作同一个资源的情况下,线程不安全,数据会紊乱。后续线程同步时会解决该问题。

2.4 案例:龟兔赛跑

public class TestThread04 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)
            try {
                Thread.sleep(100);
            } 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;
        }
        if (steps == 100){
            winner = Thread.currentThread().getName();
            System.out.println("winner is " + winner);
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        TestThread04 testThread04 = new TestThread04();
        new Thread(testThread04,"乌龟").start();
        new Thread(testThread04,"兔子").start();
    }
}

部分运行结果显示:
在这里插入图片描述

2.5 实现 Callable 接口(了解)

① 实现 Callable 接口,需要返回值类型
② 重写 call 方法,需要抛出异常
③ 创建目标对象
④ 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1)
⑤ 提交执行:Future< Boolean > result1 = ser.submit(t1)
⑥ 获取结果:boolean r1 = result1.get()
⑦ 关闭服务:ser.shutdownNow()

2.6 静态代理

public class Main {
    public static void main(String[] args) {
        Company company = new Company(new Person());
        company.HappyMarry();
    }
}

interface Marry{
    void HappyMarry();
}

//真实角色
class Person implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("张三要结婚了");
    }
}

//代理角色
class Company implements Marry{

    private Marry target;

    public Company(Marry target){   //构造器
        this.target = target;
    }

    @Override
    public void HappyMarry() {
        System.out.println("结婚前");
        this.target.HappyMarry();
        System.out.println("结婚后");
    }
}

输出结果:

结婚前
张三要结婚了
结婚后

静态代理模式总结:

  • 真实对象和代理对象都要实现同一个接口
  • 代理对象要代理真实角色
  • 好处:
    ① 代理对象可以做很多真是对象做不了的事情
    ② 真实对象专注做自己的事情

2.7 Lambda 表达式

直接上例子:

new Thread ( ()->System.out.println("这就是Lambda表达式") ).start();

为什么要使用 Lambda 表达式?

  • 避免匿名内部类过多
  • 可以让代码看起来很简洁
  • 去掉了一些没有意义的代码,只留下核心逻辑
  • 实质属于函数式编程

关键:函数式接口

函数式接口定义:

  • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。例如:
    public interface Runnable{
        public abstract void run();
    }
  • 对于函数式接口,我们可以用过 Lambda 表达式来创建该接口的对象。

Lambda 表达式进化过程。

一开始我们学习的时候:

public class Main {
    public static void main(String[] args) {
        ILike like = new Like();
        like.lambda();
    }
}

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

//2.实现类
class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("I Like Lambda");
    }
}

演变成静态内部类(接口同上):

public class Main {

    //静态内部类
    static class Like implements ILike{
        @Override
        public void lambda() {
            System.out.println("I Like Lambda");
        }
    }

    public static void main(String[] args) {
        ILike like = new Like();
        like.lambda();
    }
}

还有局部内部类(接口同上):

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

        //局部内部类
        class Like implements ILike{
            @Override
            public void lambda() {
                System.out.println("I Like Lambda");
            }
        }

        ILike like = new Like();
        like.lambda();
    }
}

进化成匿名内部类:

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

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

最终版!Lambda 表达式:

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

        //Lambda表达式
        ILike like = () -> {
            System.out.println("I Like Lambda");
        };
        like.lambda();
    }
}

来个带参数的例子!有进化过程!

① 初始版本

public class Main {
    public static void main(String[] args) {
        ILove love = new Love();
        love.love(233);
    }
}

interface ILove{
    void love(int a);
}

class Love implements ILove{
    @Override
    public void love(int a) {
        System.out.println("I Love You -->" + a);
    }
}

② 静态内部类

public class Main {
    
    //静态内部类
    static class Love implements ILove{
        @Override
        public void love(int a) {
            System.out.println("I Love You -->" + a);
        }
    }
    
    public static void main(String[] args) {
        ILove love = new Love();
        love.love(233);
    }
}

③ 局部内部类

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

        //局部内部类
        class Love implements ILove{
            @Override
            public void love(int a) {
                System.out.println("I Love You -->" + a);
            }
        }
        
        ILove love = new Love();
        love.love(233);
    }
}

④ 匿名内部类

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

        //匿名内部类
        ILove love = new ILove() {
            @Override
            public void love(int a) {
                System.out.println("I Love You -->" + a);
            }
        };
        love.love(233);
    }
}

⑤ Lambda 表达式

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

        //Lambda表达式
        ILove love = (int a) -> {
            System.out.println("I Love You -->" + a);
        };

        love.love(233);
    }
}

⑥ Lambda 表达式再简化(去参数化类型、去花括号)
前提:代码只有一行

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

        //Lambda表达式简化
        ILove love = a -> System.out.println("I Love You -->" + a);

        love.love(233);
    }
}

三、线程状态

3.1 线程的五大状态

创建状态、就绪状态、运行状态、阻塞状态、死亡状态

贴图!(图 from 狂神说)
请添加图片描述
请添加图片描述

3.2 停止线程

不推荐使用 JDK 提供的 stop() 、destroy() 方法!

  • 推荐让线程自己停下来 --> 利用次数,不建议死循环
  • 建议使用一个标志位作为终止变量。当 flag = false ,则终止线程运行。
public class TestStop implements Runnable{

    //定义一个私有标志位,安全
    private boolean flag = true;

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

    //设置一个公开的方法转换标志位,从而停止线程
    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 < 100; i++) {
            System.out.println("main" + i);
            if (i == 50){
                //调用stop1方法转换标志位,停止线程
                testStop.stop();
                System.out.println("线程该停止了");
            }
        }
    }
}

部分输出结果显示:
在这里插入图片描述

3.3 线程休眠

sleep 指定当前线程阻塞毫秒数

  • sleep 存在异常 InterruptedException
  • sleep 时间达到后线程进入就绪状态
  • sleep 可以模拟网络延时、倒计时等
  • 每一个对象都有一个锁,sleep 不会释放锁

模拟网络延时:能够放大问题的发生性
例子同 2.3 模拟买票。

模拟倒计时:

public class Main{

    public static void main(String[] args) {
        try {
            tenDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

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

打印当前系统时间:

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

public class Main{

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

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

3.4 线程礼让

  • 礼让线程,让当前正在执行的线程暂停但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让 CPU 重新调度,礼让不一定成功!看 CPU 心情!

大白话:礼让就是让线程从运行态变为就绪态,就绪态到运行态是需要 CPU 调度的,所以 A 礼让结束后,下一次 CPU 可能还是会调度 A 。

代码部分:

public class Main {

    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() + "线程停止执行");
    }
}

输出结果(礼让成功):

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

输出结果(礼让不成功):

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

3.5 线程强制执行

Join 合并线程:待此线程执行完成后,再执行其他线程。此线程执行时,其他线程处于阻塞态。相当于插队。

代码部分:

public class TestJoin 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 {
        //开启线程
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);

        //主线程
        for (int i = 0; i < 20; i++) {
            if (i == 10){
                thread.start();
                thread.join();  //代理插队
            }
            System.out.println("main线程" + i);
        }
    }
}

部分运行结果显示:
在这里插入图片描述

3.6 线程状态观测

Thread.State

NEW:新建进程
RUNNING:运行中
BLOCKED:阻塞
WAITING:等待另一个线程执行特定动作
TIMED_WAITING:等待另一个线程到达指定等待时间
TERMINATED:死亡

代码部分:

public class Main {

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

        //启动线程
        thread.start();
        state = thread.getState();
        System.out.println(state);  //RUNNABLE

        while (state != Thread.State.TERMINATED)    //只要线程不终止
        {
            Thread.sleep(100);
            state = thread.getState();  //更新线程状态
            System.out.println(state);
        }
    }
}

3.7 线程优先级

线程优先级用数字表示,范围1~10.

  • Thread.MIN_PRIORITY = 1
  • Thread.MAX_PRIORITY = 10
  • Thread.NORM_PRIORITY = 5

优先级高的不一定先执行,但是权重大,更容易优先执行。

改变优先级:setPriority(int XXX)
获取优先级:getPriority()

代码部分(先设置优先级再启动):

public class Main {
    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);

        t1.setPriority(Thread.MAX_PRIORITY);    //MAX_PRIORITY = 10
        t1.start();

        t2.setPriority(3);
        t2.start();

        t3.setPriority(Thread.MIN_PRIORITY);    //MIN_PRIORITY = 1
        t3.start();

        t4.setPriority(8);
        t4.start();
    }
}

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

输出结果1:

main–>5
Thread-0–>10
Thread-3–>8
Thread-1–>3
Thread-2–>1

输出结果2:

main–>5
Thread-0–>10
Thread-2–>1
Thread-1–>3
Thread-3–>8

由此可见,并不是优先级高的线程就一定先执行!只是优先执行的概率高了!还是要看 CPU 的调度!

3.8 守护线程(daemon)

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 用户线程:main 线程等
    守护线程:后台记录操作日志、监控内存、垃圾回收(gc)等等

设置守护线程:Thread.setDaemon(boolean)
默认为 false,表示是用户线程。正常的线程都是用户线程。

代码部分:

public class Main {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        //守护线程启动
        Thread thread = new Thread(god);
        thread.setDaemon(true); //设置为守护线程
        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 = 0; i < 36500; i++) {
            System.out.println("你开心的活着...");
        }
        System.out.println("======Goodbye World======");
    }
}

部分输出结果显示:
在这里插入图片描述
注:用户线程停止,虚拟机停止。但虚拟机停止需要一段时间,所以守护线程会在用户线程结束后再运行一段时间。

四、线程同步(重点)

4.1 同步、并发、队列和锁

并发:同一个对象多个线程同时操作

线程同步:处理多线程问题时,多个线程访问同一个对象(并发),并且某些线程还想修改这个对象,这个时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的进行进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

线程同步形成条件:队列 + 锁 --> 安全

由于统一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入了锁机制 synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。但会存在一下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起。
  • 在多线程竞争下,枷锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

关于 Runnable 与 Thread 中哪里写什么的问题:

Runnable就是能做的事情、要做的事情。
Thread 就是能做事情的人。所以 Thread 有 name。

4.2 线程不安全的例子

ArrayList 就是不安全的

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

public class Main {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String >();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start();
        }
        //模拟延时
        Thread.sleep(1000);
        System.out.println(list.size());
    }
}

输出结果:

9999

注:每个人每次执行结果都是不一样的,反正就是要存10000个数据,真正存进去的可能没有10000个。

4.3 同步方法及同步块

同步方法(就是加锁):

	public synchronized void method(int args) {}

缺陷:若将一个大的方法声明为 synchronized 将会影响效率

同步块:

	synchronized (Obj) {}

Obj 称为同步监视器

  • Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this,就是这个对象本身,或者是 class(反射中的知识点)

要锁需要共享的量,也就是同步资源!!!

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

public class Main {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String >();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                synchronized (list) {	//要共享的是list这个资源
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        //模拟延时
        Thread.sleep(1000);
        System.out.println(list.size());
    }
}

输出结果:

10000

4.4 死锁

多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都无法向前推进。某一个同步代码块同时拥有 “两个以上对象的锁” 时,就可能会发生“死锁”的问题。

死锁产生的必要条件:
① 互斥条件。指在一段时间内某资源仅为一个进程所占有。
② 不剥夺条件。指进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,而只能由该进程自己释放。
③ 请求并保持条件。进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程所占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
④ 循环等待条件。指存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。

操作系统学过,所以直接贴代码!

public class Main {
    public static void main(String[] args) {
        Makeup g1 = new Makeup(0,"A");
        Makeup g2 = new Makeup(1,"B");

        g1.start();
        g2.start();
    }
}

//口红
class Lipstick{

}

//镜子
class Mirror{

}

class Makeup extends Thread{

    //竞争资源,用static保证只有一份,final保证不允许被更改
    static final Lipstick lipstick = new Lipstick();
    static final Mirror mirror = new Mirror();

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

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

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

    //化妆,开始竞争资源
    private void makeup() throws InterruptedException {
        if (choice == 0){
            synchronized (lipstick){    //获得口红的锁
                System.out.println(this.name + "获得口红的锁");
                Thread.sleep(1000);
                synchronized (mirror){  //已经获得口红的同时还想要镜子
                    System.out.println(this.name + "想获得镜子的锁");
                }
            }
        }
        else{
            synchronized (mirror){  //获得镜子的锁
                System.out.println(this.name + "获得镜子的锁");
                Thread.sleep(2000);
                synchronized (lipstick){    //已经获得镜子的同时还想要口红
                    System.out.println(this.name + "想获得口红的锁");
                }
            }
        }
    }
}

输出结果:
在这里插入图片描述
可以看到,线程之间产生了死锁,无法向前推进,程序终止不了。

五、线程通信问题

5.1 生产者消费者问题

在生产者消费者问题中,仅有 synchronized 是不够的。

  • synchronized 可阻止并发更新同一个共享资源,实现同步
  • synchronized 不能用来实现不同线程之间的消息传递(通信)

方法:
wait():表示线程会等待,直到其他线程通知
wait(long timeout):指定等待的毫秒数
notify():唤醒一个处于等待状态的线程
notifyAll():唤醒同一个对象上所有调用 wait() 方法的线程,优先级别高的优先调度

wait() 与 sleep() 的区别:sleep() 抱着锁睡觉,wait() 会放开锁。

5.2 管程法(缓冲区)

生产者将生产号的数据放入缓冲区,消费者从缓冲区拿出数据

public class Main {
    public static void main(String[] args) {
        Container container = new Container();

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

//生产者
class Producer extends Thread{
    Container container;

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

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

//消费者
class Consumer extends Thread{
    Container container;

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

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

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

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

//缓冲区
class  Container{
    //需要一个容器
    Chicken[] chickens = new Chicken[10];    //容器大小为10
    int count = 0; //容器中现有产品数量

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

            //通知消费者消费
            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;
    }
}

部分输出结果显示:
在这里插入图片描述

5.3 信号灯法

public class Main {
    public static void main(String[] args) {
        Content content = new Content();
        new Writer(content).start();
        new Reader(content).start();
    }
}

//写
class Writer extends Thread{
    Content content;
    public Writer(Content content){
        this.content = content;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i%2 == 0){
                this.content.write("一首诗歌");
            }
            else {
                this.content.write("一篇散文");
            }
        }
    }
}

//读
class Reader extends Thread{
    Content content;
    public Reader(Content content){
        this.content = content;
    }

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

//内容
class Content{
    //写者写,读者等待 T
    //读者读,写者等待 F
    String test;    //文本内容
    boolean flag = true;

    //写方法
    public synchronized void write(String test){
        if (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("写者写了:" + test);

        this.notifyAll();   //通知读者可以读了
        this.test = test;
        this.flag = !flag;
    }

    //读方法
    public synchronized void read(){
        if (flag){  //未写入,等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("读者读了:" + test);

        this.notifyAll();   //通知写者可以写了
        this.flag = !flag;
    }
}

部分代码显示:
在这里插入图片描述

5.4 线程池

好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
  • 便于线程管理

后记:学过操作系统的话还是比较轻松的。Java基础部分差不多了,下一个是前端。冲冲冲!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值