Java基础(二十)——守护线程(setDaemon)、合并线程(join)、读写分离集合(CopyOnWriteArrayList)、生产者消费者模式

Java基础(二十)——守护线程

一、守护线程——setDaemon()

1、概念和用法

守护线程:当非守护线程销毁的时候,守护线程跟着销毁。当运行的唯一线程是守护线程时,Java虚拟机将退出。

用法:
在这里插入图片描述

注意:线程启动前必须调用此方法。

2、效果

主线程循环输出10次,子线程循环输出一百次,效果:
在这里插入图片描述

可以看到,主线程输出完毕以后,子线程会一直输出。

如果给子线程设置了守护线程以后,主线程执行完毕,子线程会跟着销毁:
在这里插入图片描述

3、额外知识:垃圾回收

只有 main 方法的时候,也是有子线程的,这个线程就是垃圾回收线程,也称之为垃圾回收机制。

二、合并线程——join()

1、解释

当把 A 线程并入到 B 线程之后,设置一些条件给 B 线程,当条件到达时,B 线程会暂停执行,会等 A 线程执行完毕之后再执行。

2、效果

代码:

public class Demo01 {
    
    
    public static void main(String[] args) {
    
    
        //A线程
        MyThread1 thread1 = new MyThread1();
        //B线程
        MyThread2 thread2 = new MyThread2(thread1);     //  传递 A 线程。这里 B 是 A 的守护线程。
        thread1.start();
        thread2.start();
    }
}

//A线程
class MyThread1 extends Thread{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            try {
    
    
                Thread.sleep(5);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("A线程:"+i);
        }
    }
}

//B线程
class MyThread2 extends Thread{
    
    
    private Thread thread;

    public MyThread2(Thread thread) {
    
       // 通过有参构造方法传递 A 线程进来
        this.thread = thread;
    }

    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
            try {
    
    
                Thread.sleep(5);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            if(i == 50){
    
            //  当 B 等于 50 的时候,就停止,等 A 执行完毕,再执行。
                try {
    
    
                    //把A线程并入到B线程中
                    thread.join();          //  调用守护线程
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            System.out.println("B:"+i);
        }
    }
}

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

三、CopyOnWriteArrayList——读写分离集合

1、迭代时修改结构发生异常

前面学过,ArrayList 是动态数组,当往中间删除或增加的时候,后面数据的下标会发生变化。如果发生变化的时候进行其他操作呢?

看代码:
在这里插入图片描述
在迭代的时候,进行增加操作,结果是什么样呢?

结果是会报错:
在这里插入图片描述

在使用 ArrayList 迭代数据的时候,如果修改集合结构会发生并发修改异常。

所以做不了一边遍历一边修改集合结构(删除或者添加等等这类操作)。这显然不符合很多业务场景的使用。所以,有其他集合可以完成。

2、CopyOnWriteArrayList

CopyOnWriteArrayList 是读写分离的集合。

在这里插入图片描述
现在换成这个数组,再来看看结果:
在这里插入图片描述

这时候可以发现,能够进行相应的操作。

3、原理

从上面的结果中也可以大概猜出来一点了,边修改边读的时候,读的还是原本的数据,修改完了再读,才是修改后的数据。

在这里插入图片描述
原理:
在进行操作的时候,会复制出另外一个一模一样的数组出来作为副本,这时候如果有删除或者添加的操作,是作用到新出来的副本那里,但是读的时候,还是读的原来的数组。当所有操作完毕以后,这时候就会指向新的数组这里,这时候再读,就是读新的数组。

四、生产者消费者模式——设计模式

1、概念解释

很好解释:比如去买奶茶,或者买什么东西,商家肯定是先做好一部分先存着,不可能来一个才开始准备一个。所以商家会准备一部分,有消费者来了,就进行售卖,售卖的同时也会制作。如果卖完了,就会暂停售卖,这时候在制作商品。制作到一定份数的商品时,又会开放售卖。

生产者、消费者、奶茶、存放奶茶的柜台、运行的主程序,总共这五个文件。

生产者消费者模式是一个很经典的模式。

2、生产者代码

/**
 * @author Ran
 * @since JDK 1.8
 *
 *      生产者
 */
public class Produce implements Runnable{
    
    
    private Counter counter;

    public Produce(Counter counter) {
    
    
        this.counter = counter;
    }

    @Override
    public void run() {
    
    
        //一个人做100杯奶茶
        for (int i = 0; i < 100; i++) {
    
    
            //生产奶茶
            MilkyTea milkyTea = new MilkyTea(i, Thread.currentThread().getName());
            //放入柜台
            counter.input(milkyTea);
            try {
    
    
                Thread.sleep(1);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

3、消费者代码

/**
 * @author Ran
 * @since JDK 1.8
 *
 *          消费者
 */
public class Consume implements Runnable{
    
    
    private Counter counter;

    public Consume(Counter counter) {
    
    
        this.counter = counter;
    }

    @Override
    public void run() {
    
    
        //一个人买20杯奶茶
        for (int i = 0; i < 20; i++) {
    
    
            counter.output();
            try {
    
    
                Thread.sleep(5000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

4、奶茶代码

/**
 * @author Ran
 * @since JDK 1.8
 *
 *      奶茶
 */
public class MilkyTea {
    
    
    private int id;
    //生产者的名字
    private String produceName;

    public MilkyTea() {
    
    
    }

    public MilkyTea(int id, String produceName) {
    
    
        this.id = id;
        this.produceName = produceName;
    }

    public int getId() {
    
    
        return id;
    }

    public void setId(int id) {
    
    
        this.id = id;
    }

    public String getProduceName() {
    
    
        return produceName;
    }

    public void setProduceName(String produceName) {
    
    
        this.produceName = produceName;
    }

    @Override
    public String toString() {
    
    
        return "MilkyTea{" +
                "id=" + id +
                ", produceName='" + produceName + '\'' +
                '}';
    }
}

5、存放奶茶的柜台代码

/**
 * @author Ran
 * @since JDK 1.8
 *          存放奶茶的柜台
 */
public class Counter {
    
    
    //存放奶茶的数组
    private MilkyTea[] cons = new MilkyTea[10];
    //奶茶的个数
    private int index = 0;

    //生产者放奶茶
    public synchronized void input(MilkyTea milkyTea){
    
    
        //判断柜台是否存满,存满了则不生产
        while(index == cons.length){
    
    
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        //把奶茶存放到数组中
        cons[index++] = milkyTea;
        System.out.println(Thread.currentThread().getName()+"生产了奶茶,编号为:"+milkyTea.getId());
        System.out.println("柜台的奶茶数为:"+index);
        //唤醒消费者
        this.notifyAll();
    }

    //消费者取奶茶
    public synchronized void output(){
    
    
        //判断柜台是否空了,如果空了则不取
        while(index == 0){
    
    
            try {
    
    
                this.wait();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        //获取奶茶
        MilkyTea milkyTea = cons[--index];
        System.out.println(Thread.currentThread().getName()+"拿了"+milkyTea.getProduceName()+"生产的奶茶");
        cons[index] = null;
        //唤醒生产者
        this.notifyAll();
    }
}

6、运行的主函数代码

**
 * @author Ran
 * @since JDK 1.8
 *
 *          生产者消费者模式
 */
public class Demo01 {
    
    
    public static void main(String[] args) {
    
    
        //柜台
        Counter counter = new Counter();
        //生产者
        Produce produce = new Produce(counter);
        //消费者
        Consume consume = new Consume(counter);
        //创建生产者的线程
        new Thread(produce,"售卖员").start();
        //创建消费者的线程
        new Thread(consume,"----消费者1").start();
        new Thread(consume,"----消费者2").start();
        new Thread(consume,"----消费者3").start();
        new Thread(consume,"----消费者4").start();
        new Thread(consume,"----消费者5").start();
    }
}

猜你喜欢

转载自blog.csdn.net/qq_41824825/article/details/121444028