持续探索java多线程

先看看这张经典的图
在这里插入图片描述

Java线程调度

在多线程中,Java虚拟机必须实现一个有优先权的、基于优先级的调度机制。这意味着Java程序中的每一个线程被分配到一定的优先权。优先级可以被开发者改变。优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。

线程的优先权

  1. 使用时没有指定优先权时,默认是普通优先级。
  2. 优先级高的线程将会优先被执行,但是不能保证线程在启动时就进入运行状态。
  3. 调度程序决定哪一个线程被执行。
  4. 使用setPriority() 方法来设置线程的优先级。
  5. 线程在被使用前需要设置优先级,否则为默认普通优先。

java虚拟机有自己的指令集以及各种运行时内存区域,Java 虚拟机是整个 Java 平台的基石,是 Java 技术用以实现硬件无关与操作系统无关的关键部分。其中线程调度与常见的linux系统类似,但并不限定线程是以时间片运行,如果感兴趣可以查阅Linux的书籍了解系统线程调度的过程。

yield()方法

yield意味着放手,放弃,投降。一个调用yield()方法的线程告诉虚拟机它乐意让其他线程占用自己的位置。
源码注释介绍:
A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint .

简单测试

未加入yieid方法
public class Example {

    public static void main(String[] args) {
        Thread t1 = new Example1(); // 创建线程1
        Thread t2 = new Example2(); // 创建线程2
        t1.setPriority(Thread.MIN_PRIORITY); // 运行前设置优先级为最低
        t2.setPriority(Thread.MAX_PRIORITY); // 运行前设置优先级为最高

        t1.start();
        t2.start();
    }
}

class Example1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++){
            System.out.println("t1 ---- " + i);
        }
    }
}

class Example2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++){
            System.out.println("t2 -- " + i);
        }
    }
}

结果输出

t2 -- 0
t2 -- 1
t2 -- 2
t2 -- 3
t2 -- 4
t1 ---- 0
t1 ---- 1
t1 ---- 2
t1 ---- 3
t1 ---- 4

结果也表明我们设置的线程优先级更高的t2先运行,然后再执行t1,但此结果不是唯一,与运行时线程的状态有关,并不是每次都是顺序的(与调度的方法有关,调度从多方面来决定是否运行该线程,有时调度器觉得某个线程排队等候时间过长,即使优先级较低,也可能先于高优先级的线程先执行)。

加入yield()方法
public class Example {

    public static void main(String[] args) {
        Thread t1 = new Example1(); // 创建线程1
        Thread t2 = new Example2(); // 创建线程2
        t1.setPriority(Thread.MIN_PRIORITY); // 运行前设置优先级为最低
        t2.setPriority(Thread.MAX_PRIORITY); // 运行前设置优先级为最高

        t1.start();
        t2.start();
    }
}

class Example1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++){
            System.out.println("t1 ---- " + i);
            Thread.yield();
        }
    }
}

class Example2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++){
            System.out.println("t2 -- " + i);
            Thread.yield();
        }
    }
}

结果输出:

t2 -- 0
t1 ---- 0
t2 -- 1
t1 ---- 1
t2 -- 2
t1 ---- 2
t2 -- 3
t1 ---- 3
t2 -- 4
t1 ---- 4

线程执行完任务后,会调用yield()方法,来告诉虚拟机愿意把让出cpu让其他线程先执行,所以我们会看到t1与t2交替着运行,此结果并不是唯一,与调度的方法有关。(仿佛有点鸡肋)

join()方法

在这里插入图片描述
源码如上图,注释也清楚的写明了等待这个线程结束,同时join还有一个带参数的重载方法,意思为等待X秒时间,如果过了X秒线程还未结束,那就不等了,接下去做别的事情。

代码示例

public class Example {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Example1();
        t1.start();
//        t1.join();
        System.out.println(new Date());
    }
}

class Example1 extends Thread{
    @Override
    public void run() {
        try {
            System.out.println(new Date());
            Thread.sleep(3000);
        }catch (Exception e){

        }
    }
}

当不加入join方法时,两个时间打印几乎在同时就打印了出来,也就是说t1的线程放在了后台运行,主线程没有等待t1的睡眠结束,再接下去执行。 如果加入了join方法,那么t1线程在打印完当前时间后,进入到睡眠状态,此时主线程会一直等待t1睡眠结束之后,然后打印第二个时间。

带超时时间的代码示例

public class Example {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Example1();
        t1.start();
        t1.join(2000);
        System.out.println(new Date());
    }
}

class Example1 extends Thread{
    @Override
    public void run() {
        try {
            System.out.println(new Date());
            Thread.sleep(3000);
        }catch (Exception e){

        }
    }
}

在这个带超时时间的join方法,我们告诉主线程,等待t1线程2秒的时间,如果2秒内线程未结束,则不继续等待,往下执行。因为我们的t1线程在打印时间后要睡眠3秒钟,2秒内不会结束,所以主线程会再2秒后去打印另一个时间打印,如下图所示
在这里插入图片描述

wait(),notify(),notifyAll()使用

  1. wait()、notify/notifyAll() 要在synchronized 代码块执行,说明当前线程一定是获取了锁的。
  2. 当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
  3. 当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized代码块的代码或是中途遇到wait() ,再次释放锁。
  4. notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。
  5. notify和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。
  6. notify和notifyAll()的区别只是notify只唤醒一个,选择哪个线程执行由JVM决定。notifyAll()会唤醒所有等待的线程,至于那个线程被先执行取决于JVM。

代码实例

工厂接口

public interface MachineFactory {
    void production(int num);
    void consume(int num);
}

工厂接口实现类

public class MachineFactoryImpl implements MachineFactory {
    // 最大库存量(爆仓啦)
    private final int MAX_STORAGE = 100;
    // 仓库空间
    private LinkedList list = new LinkedList();
    @Override
    public void production(int num) {
        // 负责生产产品
        // 生产前需要判断仓库满了没有,如果仓库放不下,则等待被消费后,满足空间条件再生产
        // 生产时需要持有list的锁,避免因为同时生产和消费时,产生队列下标异常的错误。
        synchronized (list){
            while (list.size() + num > MAX_STORAGE){
                System.out.println("【当前仓库库存量: "+list.size()+"】,【即将生产的货量: "+num+"】 等待仓库位置空间!");
                try {
                    Thread.sleep(2000);
                    // 线程释放锁,进入等待状态
                    list.wait();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            // 满足生产条件进行生产
            for (int i = 0; i<num; i++){
                list.add(new Object());
            }
            System.out.println("【生产的数量:"+num + "】"+"【当前仓库的库存:"+list.size() + "】");
            // 此时可能存在消费者在等待工厂生产产品,所以要通知所有处于wait状态的消费线程
            // 他们将收到状态改变的通知进行判断是否执行程序代码
            list.notifyAll();
        }
    }

    @Override
    public void consume(int num) {
        // 负责消费产品
        // 消费的前提是拥有足够的产品,若是仓库不存在这么多的产品,则进行等待,直到有这么多的产品进行消费
        synchronized (list){
            while (list.size() < num){
                System.out.println("【当前仓库库存量: "+list.size()+"】,【即将消费的数量: "+num+"】 等待工厂生产!");
                try {
                    Thread.sleep(2000);
                    // 让出锁,进入等待状态
                    list.wait();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            // 满足条件
            for (int i = 0; i<num; i++){
                list.remove();
            }
            System.out.println("【消耗的数量:"+num +"】,【当前仓库剩余:"+list.size() + "】");
            // 此时仓库可能已经爆仓,需要通知生产线程进行过了一次消费,此时生产现场收到状态会进行重新的检查
            list.notifyAll();
        }
    }
}

生产者

public class Producer extends Thread{
    private int number;
    private MachineFactory factory;
    private Producer(){
        // 不提供无参构造函数
    }
    public Producer(MachineFactory f, int n){
        this.factory = f;
        this.number = n;
    }
    @Override
    public void run() {
        factory.production(number);
    }
    public void setNumber(int number) {
        this.number = number;
    }
}

消费者

public class Consumer extends Thread {
    private int number;
    private MachineFactory factory;
    private Consumer(){

    }
    public Consumer(MachineFactory f , int n){
        this.factory = f;
        this.number = n;
    }
    @Override
    public void run() {
        factory.consume(number);
    }
    public void setNumber(int number) {
        this.number = number;
    }
}

代码中调用

    public static void main(String[] args) {
        // 实例化一个工厂
        MachineFactoryImpl machineFactory = new MachineFactoryImpl();
        // 实例化几个生产者和消费者,这些生产者和消费者都在同一个工厂进行操作,并且是并行的
        Producer p1 = new Producer(machineFactory,30);
        Producer p2 = new Producer(machineFactory,50);
        Producer p3 = new Producer(machineFactory,50);
        Producer p4 = new Producer(machineFactory,100);
        //
        Consumer c1 = new Consumer(machineFactory,20);
        Consumer c2 = new Consumer(machineFactory,70);
        c1.start();
        c2.start();
        p1.start();
        p2.start();
        p3.start();
        p4.start();
    }

运行结果

【当前仓库库存量: 0】,【即将消费的数量: 20】 等待工厂生产!
【生产的数量:100】【当前仓库的库存:100】
【当前仓库库存量: 100】,【即将生产的货量: 50】 等待仓库位置空间!
【当前仓库库存量: 100】,【即将生产的货量: 50】 等待仓库位置空间!
【消耗的数量:70,【当前仓库剩余:30】
【生产的数量:30】【当前仓库的库存:60】
【当前仓库库存量: 60】,【即将生产的货量: 50】 等待仓库位置空间!
【当前仓库库存量: 60】,【即将生产的货量: 50】 等待仓库位置空间!
【消耗的数量:20,【当前仓库剩余:40】
【生产的数量:50】【当前仓库的库存:90】
【当前仓库库存量: 90】,【即将生产的货量: 50】 等待仓库位置空间!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值