java线程基础学习(二)

线程学习

一 线程的生命周期

​ 一个线程的生命周期中,它要经过创建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)这五种状态。当线程进入运行状态后,它不是一直“霸占”CPU运行,一般的操作系统是采用抢占式的方式来让线程获得CPU。所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞、就绪之间切换。

  • 新建无执行资格和执行权力
  • 就绪: 拥有执行的资格
  • 运行: 拥有执行权
  • 阻塞无执行资格和执行权力
  • 死亡: 对象已销毁

在这里插入图片描述

二 运行和阻塞状态

​ 当处于就绪状态的线程获得CPU,它就会执行run()方法(所以run()方法是由线程获得CPU以后自动执行),线程就进入了运行状态。如果一个计算机只有一个CPU,那么在任意时刻只有一个线程处于运行状态。相对的,如果有多个CPU,那么在同一时刻就可以有多个线程并行执行。但是,当处于就绪状态的线程数大于处理器数时,仍然会存在多个线程在同一CPU上轮换执行的现象,只是计算机的运行速度非常快,人感觉不到而已。

​ 当一个线程开始运行后,它不可能一直持有CPU(除非该线程执行体非常短,瞬间就执行结束了)。所以,线程在执行过程中需要被中断,目的是让其它线程获得执行的CPU的机会。线程的调度细节取决于底层平台所采用的策略。对于抢占式策略的系统而言,系统会给每一个可执行线程一个时间段来处理任务,当该时间结束后,系统就会剥夺该线程所占用资源(即让出CPU),让其它线程获得执行机会。

​ 所有的现代桌面和服务器操作系统都采用抢占式调度策略,但一些小型设备,如:手机,则可能采用协作式调度策略。在这样的系统中,只有当一个线程调用了它的sleep()或yield()方法后才会放弃所占用的资源。也就是说,此时必须该线程主动放弃占用资源,才能轮到其他就绪状态的线程获得CPU,不然必须要等当前线阻塞/死亡以后,其他线程才有机会运行。

当如下情况发生时,线程会进入阻塞状态:

1、线程调用sleep()方法主动放弃占用的处理器资源;

2、线程调用了一个阻塞式IO方法,在该方法返回以前,该线程被阻塞;

3、线程试图获得一个同步监视器,但该监视器被其他线程持有;

4、线程在等待某个通知;

5、线程调用suspend()方法将该线程挂起。但这个方法容易导致死锁,不建议使用;

​ 当正在执行的线程被阻塞以后,其他线程可以获得执行的机会,被阻塞的线程会在合适的时候进入就绪状态,而不是进入运行状态。也就是说,当线程阻塞解除后,必须重新等待线程调度器再次调度它,而不是马上获得CPU。所以针对上述线程阻塞情况, 如何让线程重新进入就绪状态,有如下几种情况:

1、调用sleep()方法的线程经过了指定时间;

2、线程调用的阻塞式IO方法已经返回;

3、线程成功地获得了试图取得的同步监视器;

4、线程在等待通知时,其他线程发出了一个通知;

5、处于挂起状态的线程被调用了resume()恢复方法;

下面是线程状态转换图:

在这里插入图片描述

从图中可以看出,线程从阻塞状态只能进入就绪状态,而不能直接进入运行状态。而就绪状态到运行状态之间的转换通常不受程序控制,而由系统线程调度所决定,当处于就绪状态的线程获得处理器资源时,该线程进入运行状态;当处于运行状态的线程失去处理器器资源时,该线程进入就绪状态。但有一个方法可以控制线程从运行状态转为就绪状态,那就是yiled()方法。

三 线程优先级

每个线程的都有一个线程优先级属性,理论上线程优先级越高,越容易获取cpu执行线程

在这里插入图片描述

  1. 根据图示,Thread类有三个优先级静态常量:MAX_PRIORITY为10,为线程最高优先级;MIN_PRIORITY取值为1,为线程最低优先级;NORM_PRIORITY取值为5,为线程中间位置的优先级。默认情况下,线程的优先级为NORM_PRIORITY。
  2. Java中的线程在同等情况下,对于两个同时启动的线程,优先级高的线程先获取CPU资源,先被真正运行,优先级低的线程后获取CPU资源,后被执行。特殊情况在于现在计算机都是多核多线程的配置,有可能优先级低的线程比优先级高的线程先运行,优先级高的线程可能比优先级低的线程后运行,线程的执行先后还是由Java虚拟机调度决定的。
/**
 * @author yds
 * @program thread-study
 * @description 测试线程优先级是否绝对左右线程执行顺序
 * @create 2020-10-28 14:12
 **/
public class ThreadPriority {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                System.out.println("t1 优先级为3");
            }
        });
        Thread.sleep(1000);
        thread.setPriority(3);
        Thread thread2 = new Thread(() -> {
            while (true) {
                System.out.println("t2 优先级为10");
            }
        });
        thread2.setPriority(10);
        thread.start();
        thread2.start();
    }
}

如图所以,我命名T1,T2两个线程,将T1的线程优先级设置为3,T2的优先级为最大值10,如果优先级能够决定执行顺序,那么只要T1线程没有结束那么,T2应该一直处于就绪态

在这里插入图片描述

如上图所示,我们能够发现,T1 ,T2在交替执行,那么我们可以得知优先级并不能决定执行顺序,而且在极端情况,可能会出现优先级倒置,优先级低的先执行

总结,一般情况下不会对线程设定优先级,更不会让业务严重依赖于线程的优先级,一般保持默认就可以了。那么默认是多少。线程一般默认优先级和父类保持一致,一般是5,因为main方法的优先级是5,所以由他派生出来的线程都是5

四 线程顺序

​ 由于线程优先级并不能决定线程执行顺序,但是在某些情况下我们需要线程按照一定顺序执行,比如现在抢票,突然有个vip会员抢票,作为vip的线程,你其他的线程肯定要给我让路,不然你这个系统谁愿意冲会员。

/**

- @author yds

- @program thread-study

- @description 实现线程的join方法 join抢占线程,其余线程均阻塞

- @create 2020-09-23 14:45
  **/
  public class ThreadJoin implements Runnable{

  @Override
  public void run() {
      for (int i = 0; i < 5; i++) {
          System.out.println(Thread.currentThread().getName()+"--->Vip 抢占位置"+i);
      }
  }

  public static void main(String[] args) throws InterruptedException {
      ThreadJoin t1=new ThreadJoin();
      Thread tt1=new Thread(t1,"会员a");
      tt1.start();
      for (int i = 0; i < 10; i++) {
          System.out.println(Thread.currentThread().getName()+i+"抢占票数");
          if(i==5){
              tt1.join();
          }
      }
  }
  }

如上图所示,一个main线程和一个名为”会员a“的线程,我按照代码逻辑,”会员a“和main抢位置,当i=5时,会员不干了,我作为会员要插队,所以它使用会员技能 join();,按照逻辑,插队后它要先抢占5个位置

在这里插入图片描述

总结, join()确实能够强行插队

  1. yeild–礼让线程

​ 既然出现了插队,那肯定还会出现让队,举个例子就是,你正排队准备打饭,来了一个熟人,你说来,你在我这里排队,但是这种礼让可能会失败,后面的排队的哥们不满(cpu资源不够,礼让失败),也可能成功(cpu资源充足)

/**
 * @author yds
 * @program thread-study
 * @description 作为线程礼让 礼让不一定成功 线程执行取决于cpu
 * @create 2020-09-23 12:10
 **/
public class ThreadYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"结束");
    }

    public static void main(String[] args) {
        ThreadYield t1=new ThreadYield();
        new Thread(t1,"a").start();
        new Thread(t1,"b").start();
    }
}

如图所示,新建了a,b两个线程,在执行逻辑中,线程yield()

在这里插入图片描述
在这里插入图片描述

五 线程阻塞
  • 阻塞----线程不执行也不死掉 .
    1. 睡眠状态(sleep):当一个线程执行代码的时候调用了sleep方法后,线程处于睡眠状态,需要设置一个睡眠时间,此时有其他线程需要执行时就会造成线程阻塞,而且sleep方法被调用之后,线程不会释放锁对象,也就是说锁还在该线程手里,CPU执行权还在自己手里,等睡眠时间一过,该线程就会进入就绪状态,典型的“占着茅坑不拉屎”;
    2. 等待状态(wait):当一个线程正在运行时,调用了wait方法,此时该线程需要交出CPU执行权,也就是将锁释放出去,交给另一个线程,该线程进入等待状态,但与睡眠状态不一样的是,进入等待状态的线程不需要设置睡眠时间,但是需要执行notify方法或者notifyall方法来对其唤醒,自己是不会主动醒来的,等被唤醒之后,该线程也会进入就绪状态,但是进入仅需状态的该线程手里是没有执行权的,也就是没有锁,而睡眠状态的线程一旦苏醒,进入就绪状态时是自己还拿着锁的。等待状态的线程苏醒后,就是典型的“物是人非,大权旁落“;
    3. 礼让状态(yield):当一个线程正在运行时,调用了yield方法之后,该线程会将执行权礼让给同等级的线程或者比它高一级的线程优先执行,此时该线程有可能只执行了一部分而此时把执行权礼让给了其他线程,这个时候也会进入阻塞状态,但是该线程会随时可能又被分配到执行权,这就很”中国化的线程“了,比较讲究谦让;
    4. 自闭状态(join):当一个线程正在运行时,调用了一个join方法,此时该线程会进入阻塞状态,另一个线程会运行,直到运行结束后,原线程才会进入就绪状态。这个比较像是”走后门“,本来该先把你的事情解决完了再解决后边的人的事情,但是这时候有走后门的人,那就会停止给你解决,而优先把走后门的人事情解决了;
    5. suspend() 和 resume() :这两个方法是配套使用的,suspend() 是让线程进入阻塞状态,它的解药就是resume(),没有resume()它自己是不会恢复的,由于这种比较容易出现死锁现象,所以jdk1.5之后就已经被废除了,这对就是相爱相杀的一对。
六 线程通讯

上面由于sleep,yield,join都讲过,然后suspend和resume也废除了,就大致讲解一下wait

wait:线程处于等待队列

wait+notify/notifyall=线程通讯,线程通讯的进一步就是线程协作,就是多线程编程的雏形

线程通讯,通讯点在哪里,什么作为条件让线程等待和唤醒,举个吃苹果的例子,一个生产者生产一个苹果到篮子里,消费者看到篮子中有苹果就吃了,生产者看到没苹果又生产一个,以此轮回

package com.yang.thread.wait;

import java.util.Vector;

/**
 * @author yds
 * @program thread-study
 * @description 线程通讯
 * @create 2020-10-28 15:16
 **/
public class ThreadWaitNotifyTest {
    public static void main(String args[]) {
        Vector obj = new Vector();//篮子
        Thread consumer = new Thread(new Consumer(obj));
        Thread producter = new Thread(new Producter(obj));
        consumer.start();
        producter.start();
    }
}
/**
 * 作为消费者不断消费
 */
class Consumer implements Runnable {
    private Vector obj;
    public Consumer(Vector v) {
        this.obj = v;
    }
    public void run() {
        synchronized(obj) {
            while (true) {
                try {
                    if (obj.size() ==0){
                        obj.wait();
                    }
                    obj.clear();
                    System.out.println("消费者:苹果我拿了"+",篮子里有"+obj.size()+"个苹果");
                    obj.notify();
                }
                catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

/**
 * 作为生产者不断生产苹果
 */
class Producter implements Runnable {
    private Vector obj;
    public Producter(Vector v) {
        this.obj = v;
    }
    public void run() {
        synchronized(obj) {
            while (true) {
                try {
                    if (obj.size() !=0){
                        obj.wait();
                    }
                    obj.add(new String("apples"));
                    obj.notify();
                    System.out.println("生产者:我又放了一个苹果"+",篮子有"+obj.size()+"个苹果");
                    Thread.sleep(500);
                }
                catch(Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

按照上图所示,代码中存在三个线程,mian执行整体拿取苹果,Producter生产苹果,Consumer消费苹果。那么触发唤醒线程的对象就是篮子。所以通过构造器将篮子obj带入,并通过obj的数量作为是否暂停当前线程的依据
在这里插入图片描述

如上图所示,线程通讯先找到通讯点在哪里
原文参考

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值