chengyuyu

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
  10 随笔 :: 0 文章 :: 0 评论 :: 1070 阅读

多线程

1 程序、进程、线程

Process 进程

Thread 线程

2 线程创建

  • Thread class(继承Thread类)
  • Runnable 接口
  • Callable 接口(了解)

区别:run() vs. start()

image-20211130155048116

2.1 继承Thread类

// 创建线程方式1: 继承Thread类,重写run方法,调用start开启线程
public class TestThread01 extends Thread{
    @Override
    public void run() {
        //run方法线程体
    }

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

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

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

注:不调用run()方法,而是调用start()方法

实现多线程下载

2.2 实现Runnable接口

// 创建线程方法2: 实现runnable接口, 重写run()方法, 执行县城需要丢入runnable接口实现类, 调用start方法
public class TestThread02 implements Runnable{
    @Override
    public void run() {
        // run方法线程体
    }

    public static void main(String[] args) {
        // 创建runnable接口实现类对象
        TestThread02 testThread02 = new TestThread02();

        // 创建线程对象,通过线程对象来开启线程,代理
        new Thread(testThread02).start();
    }
}

2.3 小结

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

3 扩充内容

3.1 并发问题

例子:买火车票

public class TestTicket implements Runnable{
    private int ticketNum = 10;

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

    public static void main(String[] args) {
        TestTicket ticket = new TestTicket();

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

/* 输出:
王五 拿到了第 9 张票
李四 拿到了第 10 张票
张三 拿到了第 9 张票
张三 拿到了第 8 张票
王五 拿到了第 6 张票
李四 拿到了第 7 张票
王五 拿到了第 5 张票
张三 拿到了第 5 张票
李四 拿到了第 5 张票
张三 拿到了第 4 张票
王五 拿到了第 3 张票
李四 拿到了第 3 张票
李四 拿到了第 2 张票
王五 拿到了第 1 张票
张三 拿到了第 2 张票

进程已结束,退出代码 0

*/

3.2 静态代理

3.3 Lambda表达式

为什么要用:

  • 避免匿名内部定义过多
  • 代码简洁
  • 只留下核心逻辑

函数式接口:如果只包含唯一一个抽象方法,即为函数式接口

推导lambda表达式

public class TestLambda {
    // 3.静态内部类(把实现类放到内部,加static)
    static class Test02 implements Test{
        @Override
        public void lambda() {
            System.out.println("2.静态内部类");
        }
    }

    public static void main(String[] args) {
        Test test1 = new Test01();
        test1.lambda();

        test1 = new Test02();
        test1.lambda();

        // 4.局部内部类
        class Test03 implements Test{
            @Override
            public void lambda() {
                System.out.println("3.局部内部类");
            }
        }
        test1 = new Test03();
        test1.lambda();

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

        // 6.用lambda简化
        test1 = () ->{
            System.out.println("5.用lambda简化");
        };
        test1.lambda();

    }
}

// 1.实现一个函数式接口
interface Test{
    void lambda();
}

// 2.实现类
class Test01 implements Test{
    @Override
    public void lambda() {
        System.out.println("1.一般实现类");
    }
}

/* 输出:
1.一般实现类
2.静态内部类
3.局部内部类
4.匿名内部类
5.用lambda简化
*/

lambda简化

// lambda简化
public class TestLambda2 {

    // 2.内部静态类
    static class TestClass02 implements TestInter{
        @Override
        public void method(int num) {
            System.out.println("2.静态内部类 参数 = "+ num);
        }
    }

    public static void main(String[] args) {
        class TestClass03 implements TestInter{
            @Override
            public void method(int num) {
                System.out.println("3.局部内部类 参数 = "+ num);
            }
        }

        TestInter test = new TestClass01();
        test.method(88); // 一般实现类
        test = new TestClass02();
        test.method(77); // 静态内部类
        test = new TestClass03();
        test.method(66); // 局部内部类

        test = new TestInter() {
            public void method(int num) {
                System.out.println("4.匿名内部类 参数 = "+ num);
            }
        };
        test.method(55); // 匿名内部类

        test = (int num) -> {
            System.out.println("5.lambda形式 参数 = "+ num);
        };
        test.method(44); // lambda未简化

        test = (num) -> {
            System.out.println("6.lambda简化参数类型 参数 = "+ num);
        };
        test.method(33); // lambda简化参数类型

        test = num -> {
            System.out.println("7.lambda简化括号 参数 = "+ num);
        };
        test.method(22); // lambda简化括号

        test = num ->  System.out.println("8.lambda简化花括号 参数 = "+ num);
        test.method(11); // lambda简化花括号
        
    }
}

interface TestInter{
    void method(int num);
}

class TestClass01 implements TestInter{
    @Override
    public void method(int num) {
        System.out.println("1.实现类 参数 = "+ num);
    }
}

4 线程状态

(16条消息) Java线程的6种状态及切换(透彻讲解)_潘建南的博客-CSDN博客_线程状态

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

4.1线程停止

// 测试stop
// 1.建议先整正常停止-->利用次数,不建议死循环;
// 2.建议设置一个标志位停止-->flag
// 3.不要使用stop或destroy等JDK不建议的方法
public class TestStop implements Runnable{
    // 1.设置一个标志位
    private boolean flag = true;
    
    @Override
    public void run() {
        int i = 0;
        while (flag) {
            System.out.println("正在执行线程-->"+i++);
        }
    }
    // 2.设置一个公开的方法停止线程,转换标志位
    public void StopThread(){
        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("主函数的执行数 "+ i);
            if (i == 60){
                // 调用StopThread方法,转换标志位,停止线程
                testStop.StopThread();
                System.out.println("该线程停止");
            }
        }
    }
}

4.2线程休眠 sleep

// 模拟网络延时: 放大问题的发生性
// 模拟倒计时
public class TestSleep{
    public static void main(String[] args) throws InterruptedException {
        // 模拟倒计时
        try {
            tenDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打印当前系统时间
        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();
            }
        }

    }
    // 模拟倒计时放的方法
    public static void tenDown() throws InterruptedException {
        int num = 10;
        while (true) {
            Thread.sleep(1000); // 每一秒跑一次
            System.out.println(num--);
            if (num<=0) {
                break;
            }
        }
    }
}

4.3线程礼让 yield

  • 礼让线程:让当前正在执行的线程暂停,但不阻塞
  • 将线程运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功
// 测试礼让线程
// 礼让不一定成功
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield,"线程A").start();
        new Thread(myYield,"线程B").start();
    }
}

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

4.4线程强制执行 join

join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("强制执行线程 join -->"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 启动线程
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin); //后面还需要join,这里保留名称
        thread.start();

        // 主线程
        for (int i = 0; i < 200; i++) {
            if (i == 20){
                // i=20的时候强制执行该线程,并且该线程执行完才继续执行主线程
                thread.join();
            }
            System.out.println("主线程 -->"+i);
        }
    }
}

4.5线程状态观测

五种状态

详见JDK文档:Thread.State

image-20211201121500095

4.6线程的优先级

使用以下方法改变或获取优先级:

  • getPriority(),setPriority(int XXX)

优先级的设定建议在start()之前

4.7守护线程 daemon

线程分为用户线程守护线程

  • 虚拟机必须确保用户现场执行完毕

  • 虚拟机不必等待守护线程执行完毕,如:后台操作日志、监控内存、垃圾回收等

5 线程同步

场景:多个线程操作同一个资源,即并发。

线程同步是一种等待机制,多个需要同时访问同一对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用。

锁机制(synchronized):为了确保数据在方法中被访问的正确性。当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可。存在以下问题:

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

5.1三个不安全的例子

不安全的购票

// 不安全的买票
public class UnsafeTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station,"乘客A").start();
        new Thread(station,"乘客B").start();
        new Thread(station,"乘客C").start();
    }
}

class BuyTicket implements Runnable{

    // 票
    private int ticketNum = 10;
    boolean flag = true;

    @Override
    public void run() {
        // 买票
        while (flag){
            buy();
        }
    }
    // 买票的方法
    private void buy(){
        // 判断是否有票
        if (ticketNum<=0){
            flag = false; // 线程停止
            return;
        }
        // 模拟延时
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 买票
        System.out.println(Thread.currentThread().getName()+"拿到票-->"+ticketNum--);
    }
}

不安全的取钱

// 不安全的取钱
// 两个人去银行取钱
public class UnsafeBank {
    public static void main(String[] args) {
        // 账户
        Account account = new Account(100,"我的存款");

        Drawing owner1 = new Drawing(account,50,"成员A");
        Drawing owner2 = new Drawing(account,80,"成员B");

        owner1.start();
        owner2.start();
    }
}

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

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

// 银行:模拟取款
class Drawing extends Thread{
    // 1.定义参数
    Account account; // 账户
    int drawMoney; // 取了多少钱
    int nowMoney; // 现在多少钱

    // 2.构造方法
    public Drawing(Account account, int drawMoney, String name){
        super(name); // 调用父类的方法
        this.account = account;
        this.drawMoney = drawMoney;
    }

    // 3.重写run方法
    @Override
    public void run() {
        // 判断有没有钱
        if (account.money-drawMoney<=0){
            System.out.println("余额不足,不能提取。");
            return;
        }
        // sleep可以放大问题的发生性
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 卡内余额 = 余额 - 取的钱
        account.money = account.money-drawMoney;
        // 现在多少钱 = 现在的钱 + 取的钱
        nowMoney = nowMoney + drawMoney;

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

    }
}   

不安全的集合

// 线程不安全的集合
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 5000; i++) {
            new Thread(() ->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        System.out.println(list.size());
    }
}
/*
* 输出:
* 4997*/

5.2同步方法

synchronized关键字:synchronized方法和synchronized块

synchronized方法控制对”对象“的访问,每个对象对应一把锁,每个synchronized方法都有必须获得调用该方法的锁才能执行,否则线程会被阻塞。方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

锁的对象是变化的量,即增、删、改的量。

synchronized方法

synchronized块

  • synchronized(Obj){}
  • Obj称之为同步监视器
    • Obj可以是任意对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的监视器就是this,就是这个对象本上,或者是class。
  • 同步监视器的执行过程
    1. 第一个线程访问,锁定同步监视器,执行其中代码。
    2. 第二个线程访问,发现同步监视器被锁定,无法访问。
    3. 第一个线程访问完毕,解锁同步监视器。
    4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问。

JUC中的线程安全类型:CopyonWriteArrayList

参考Java JUC:(17条消息) Java JUC总结_频率coo的博客-CSDN博客_java juc

// 测试JUC安全类型的集合
// JUC
/*
 * CopyOnWriteArrayList/CopyOnWriteArraySet : “写入并复制”
 * 注意:添加操作多时,效率低,因为每次添加时都会进行复制,开销非常的大。并发迭代操作多时可以选择。
 */
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }

5.3死锁

某一同步块同时拥有两个以上对象的锁时,就可能发生”死锁“的问题。

相互等待对方的资源

例子

// 死锁:多个线程互相占用对方所需要的资源,形成僵持。
public class DeadLock {
    public static void main(String[] args) {
        Makeup girl1 = new Makeup(0,"张三");
        Makeup girl2 = new Makeup(1,"李四");
        girl1.start();
        girl2.start();
    }
}

// 口红
class Lipstick{

}

// 镜子
class Mirror {

}

// 化妆
class Makeup extends Thread{
    // 需要的资源只有一份,用static保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();
    int choice; // 选择
    String name; // 使用化妆品的人

    // 构造器
    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(1000);
                synchronized (lipstick){
                    System.out.println(this.name+"获得口红的锁");
                }
            }
        }
    }
}

解决:一个同步块中不能有两个对象的锁

// 化妆方法,互相持有对方的资源
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(1000);
        }
        // 解决:将代码块放到外面
        synchronized (lipstick){
            System.out.println(this.name+"获得口红的锁");
        }
    }
}

死锁避免方法

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源儿阻塞时,对已获得的资源保持不放
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  4. 循环等待条件:若干个进程之间形成一种头尾详解的循环等待资源关系

破除任意一个或多个条件就可以避免死锁发生

5.4Lock锁

ReentrantLock类(可重入锁)实现Lock。它拥有和synchronized相同的并发行和内存语义,在实现线程安全的控制中,比较常用的时ReentrantLock,可以显式加锁、释放锁。

image-20211201205928679

例子

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 ticketNum = 10;

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


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

synchronized和Lock的对比

  • Lock是显式锁(手动开启和关闭锁),synchronized是隐式锁,出了作用于自动释放。
  • Lock只有代码块锁,synchronized有代码块锁和方法锁。
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能较好。并且有更好的扩展性(提供更多子类)。
  • 优先级顺序
    • Lock -> 同步代码块(已经进入了方法体,分配了相应资源) -> 同步方法(在方法体之外)

6 线程协作(线程通信)

应用场景:生产者消费问题

  • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者从仓库中取走产品消费。
  • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
  • 如果仓库中放有产品,则消费者可以将商品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。

分析:这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间互相依赖、互为条件。

  • 对于生产者,没有生产产品之前,要通知消费者等待;而生产了产品之后,又需要马上通知消费者消费。
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
  • 在生产者消费者问题中,仅有synchronized是不够的
    • synchronized可阻止并发更新同一个共享资源,实现了同步
    • synchronized不能用来实现不同线程之间的消息传递(通信)

image-20211202150723738

解决方法1:管程法

利用一个缓冲区(第三者)

image-20211202151005175

// 线程通信
// 测试: 生产者消费者模型 --> 利用缓冲区解决: 管程法
// 需要的对象: 生产者, 消费者, 产品, 缓冲区
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++) {
            container.push(new Chicken(i));
            System.out.println("生产了 "+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().id+" 只鸡");
        }
    }
}

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

// 缓冲区
class SynContainer{
    // 需要一个容器大小
    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();
            }
        }
        // 如果容器没有满, 就需要放入产品
        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;
    }
}

notify() 和 notifyAll()

image-20211202162744385

你真的懂wait、notify和notifyAll吗 - 简书 (jianshu.com)

解决方法2:信号灯法(设置标志位)

// 测试生产者消费者问题2: 信号灯法, 设置标志位
public class TestPC2 {
    public static void main(String[] args) {
        Show show = new Show();
        new Actor(show).start();
        new Audience(show).start();
    }
}

// 生产者--演员actor
class Actor extends Thread{
    Show show = new Show();
    public Actor(Show show){
        this.show = show;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i%2==0){
                this.show.giveShow("节目A");
            }else {
                this.show.giveShow("广告!!");
            }
        }
    }
}

// 消费者--观众audience
class Audience extends Thread{
    Show show = new Show();
    public Audience(Show show){
        this.show = show;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            show.watchShow();
        }
    }
}

// 产品--节目show
class Show{
    // 演员表演,观众等待 T
    // 观众观看,演员等待 F
    String show; // 表演的节目
    boolean flag = true;

    // 表演
    public synchronized void giveShow(String show){
        // 如果flag=false,则演员等待
        if (!flag){
            // 等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了 "+show);
        // 通知观众观看
        this.notifyAll(); // 通知唤醒
        this.show = show;
        this.flag = !flag;
    }

    // 观看
    public synchronized void watchShow(){
        // 如果flag=true,则观众等待
        if (flag){
            // 等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观众观看了 "+show);
        // 通知演员表演
        this.notifyAll(); // 通知唤醒
        this.flag = !flag;
    }
}

线程池

避免频繁创建和销毁线程资源。便于线程管理。

相关API:ExecutorService和Executors

image-20211202165252772

// 测试线程池
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() {
        System.out.println("MyThread "+Thread.currentThread().getName());
    }
}

总结

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口
posted on   chengyuyu  阅读(37)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
点击右上角即可分享
微信分享提示