Thread源码学习及案例演示

版权声明: https://blog.csdn.net/UtopiaOfArtoria/article/details/79932535

Thread源码学习及案例演示

Thread源码学习

这里我只截取了java.lang.Thread.java的部分源码—我能看懂的一部分,注释部分由我自己翻译。但受我的翻译及专业水平所限,可能错误颇多,望请勿喷。

Thread.java的部分源码即注释如下

/*
 * @since   JDK1.0
 * 线程这个类是JDK1.0就有的,它实现了Runnable接口
 */
public class Thread implements Runnable {

    // Thread是用一个枚举类型State来描述它当前所处的状态
    public enum State {
        // 线程还未启动
        NEW,
        // 就绪状态:调用了start方法后,线程的状态由NEW变为RUNNABLE
        RUNNABLE,
        // 阻塞状态:调用了sleep方法后,线程的状态会变为BLOCKED
        BLOCKED,
        // 调用了join/wait方法后,线程会处于等待状态
        // 本线程调用了wait方法,会处于等待状态;其他线程调用了notify或notifyAll方法才能唤醒
        // 其他线程调用了join方法,本线程会处于等待状态;只有调用join方法的线程处于terminate即死亡状态才能唤醒
        WAITING,
        // 定时等待状态,其在wait/join方法中传入了一个参数time,本线程会在time时间内处于定时等待状态
        TIMED_WAITING,
        // 终止状态,即死亡状态:run方法或main方法执行完毕,线程状态变为TERMINATED
        TERMINATED;
    }
    // 获取当前线程的状态
    public State getState() {
        // ...
    }
    // volatile:当前线程的状态对其他线程可见,0:线程初始化默认是NEW状态
    private volatile int threadStatus = 0;

    // 线程的优先级范围1-10,默认是5,设置后不可更改
    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;

    // 返回当前线程的本地静态方法
    public static native Thread currentThread();

    // 告诉操作系统,该线程会让出CPU时间片,即暂停本线程的执行。
    // 这个方法一般是在DEBUG测试时才会用到。
    public static native void yield();

    // 让当前线程的状态变为BLOCKED,持续时间为mills毫秒,也就是我们常说的阻塞状态
    public static native void sleep(long millis) throws InterruptedException;
    // 让当前线程的状态变为BLOCKED,持续时间为mills毫秒 + nanos纳秒
    public static void sleep(long millis, int nanos)
    throws InterruptedException {
        // ...
    }

    // Thread的初始化
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        // ...
    }

    // Thread构造函数的默认方法,无参,默认命名为Thread-数字
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    // Thread构造函数的重载方法,可以传入一个Runnable对象
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    // Thread构造函数的重载方法,可以给该线程命名
    public Thread(String name) {
        init(null, null, name, 0);
    }

    // 让线程的状态从NEW变为RUNNABLE,会让JVM去调用run()方法;
    // 这个方法能确保多个线程同时运行;
    // 一个线程只能调用一个start()方法
    public synchronized void start() {
        // ...
    }

    // 重写了接口Runnable的run()方法。线程在该方法中执行需要完成的功能
    @Override
    public void run() {
        // ...
    }

    // 由系统来调用该方法,确保能顺利清理该线程
    private void exit() {
       // ...
    }

    // 仅仅是设置一个线程处于中断的标记,不会对线程造成任何影响
    // 执行该方法后,遇到该方法调用了wait()/join()/sleep()方法时会抛出异常并清除该标记。
    public void interrupt() {
        // ...
    }
    // 测试当前线程是否有中断标记,而且该方法调用后会清除中断标记。
    // 所以,如果连续成功地调用该方法两次,第二次的结果一定是false
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    // 测试当前线程是否调用了清除中断标记的方法,这个方法对线程的中断标记没有影响
    public boolean isInterrupted() {
        return isInterrupted(false);
    }

    // 测试这个线程是否还活着
    public final native boolean isAlive();

    // 设置线程的优先级
    public final void setPriority(int newPriority) {
        // ...
    }
    // 获取线程的优先级
    public final int getPriority() {
        return priority;
    }

    // 设置线程的名字
    public final synchronized void setName(String name) {
        // ...
    }
    // 获取线程的名字
    public final String getName() {
        return new String(name, true);
    }

    // 当前正在执行的线程会等待调用join(millis)的线程millis毫秒。
    // 两个本来是并行的线程1、线程2。线程1调用了join()方法后,线程2会等待线程1执行了millis毫秒后再开始执行。如果join()方法不传入参数或者传入的是0,就是等线程1执行完毕后,线程2再执行
    public final synchronized void join(long millis)
    throws InterruptedException {
        // ...
    }
    // 参考上个方法
    public final synchronized void join(long millis, int nanos)
    throws InterruptedException {
      // ...
    }

    // 其他线程会等待调用join方法的线程die后,再继续执行
    public final void join() throws InterruptedException {
        join(0);
    }

    // 设置是否为守护线程,只能在线程启动前调用该方法
    public final void setDaemon(boolean on) {
       // ...
    }
    // 当前进程是否为守护进程
    public final boolean isDaemon() {
        return daemon;
    }
}

Thread源码学习总结

重要属性及其含义:

  • threadStatus

    threadStatus,即线程当前所处的状态。线程是用一个抽象的枚举类型State定义了其可能所处的状态。State的值:NEW(刚创建),RUNNABLE(就绪状态),BLOCKED(阻塞状态),WAITING(等待状态),TIME_WAITING(定时状态),TERMINATED(终止状态)。为了便于理解线程的状态变化,详情可见下图:


线程状态切换
  • *_PRIORITY

    PRIORITY指的是线程的优先级,取值范围是1-10。新建的线程默认优先级为5。当两个线程竞争共享资源时,一般情况下优先级高的线程更可能先拿到共享资源。

  • name

    name,线程的名称。线程的名称默认是Thread-数字,这里的数字就是新建的线程序号。为了提高程序的可读性,一般会设置不同的name。

重要方法及其作用:

  • currentThread()

    获取当前正在执行的线程。

  • start()

    让线程的状态从NEW变为RUNNABLE,随后会让JVM去调用该线程的run()方法;每个线程只能调用一次该方法。使用start()方法启动线程,才是真正地实现了线程的异步运行。

  • run()

    这是Thread类的一个普通方法。在这个方法内部实现的功能,就是该线程实际要完成的功能。使用run()方法启动线程,实际线程间还是同步执行的。

  • yield()

    当前线程让出操作系统的CPU时间片。操作系统实际是通过将CPU时间片分发给不同的线程,然后根据这些CPU时间片的顺序串行运行其所属的线程,并根据CPU时间片的归属在不同的线程之间进行切换。这种方式运行多个线程,用于切换时间特别快,会让用户觉得这多个线程在同时运行(并发)。

以下图片参考自以操作系统的角度述说线程与进程

操作系统-线程执行切换

  • sleep()

    让当前线程处于阻塞状态。传入一个参数时,在参数的范围内出阻塞状态,时间过后就变为RUNNABLE状态。不传入参数时,线程停止运行,等待其他线程调用了notify()或者notifyAll()方法后才会唤醒该线程,使该线程处于RUNNABLE状态。

  • join()

    当前线程会等到调用join()方法的线程处于TERMINATED状态后,再去执行。常见的用法就是:主线程需要用到子线程的执行返回的结果,子线程调用join()方法后,主线程会在子线程执行完之前一直处于阻塞状态。

  • interrupt(),interrupted(),isInterrupted()

    interrupt()方法仅仅是设置一个线程处于中断的标记,不会对线程造成任何影响。interrupted()方法是当前线程是否有中断标记,该方法执行完后会将中断标记清除。
    isInterrupted()方法是检测当前线程是否调用了interrupted()方法,该方法不会对线程中的中断标记造成影响。

  • wait(),notify(),notifyAll()

这三个都是Object的方法。wait()方法与sleep()方法的效果类似,都是让当前线程处于阻塞状态。notify()方法是唤醒随机的一个处于wait状态的线程。notifyAll()方法是唤醒所有处于wait状态的线程。

案例演示

场景假设

张三、李四、王五3个人同时吃盘子里的10个包子。张三每吃一个包子要2s,李四每吃一个包子要3s,王五每吃一个包子要4s。

场景还原如下图:

多线程案例-场景还原

过程还原如下图:

多线程案例-过程还原

代码实现

这个小项目的源码在我的gitHub账号上

公有资源Dish.java

/**
 * 盘子中记录的是此时其中还有num个foodName
 * @author zhenye 2018/4/12
 */
public class Dish {
    // 食物名称
    private String foodName;
    // 食物数目
    private int  num;

    Dish(String foodName, int num){
        this.foodName = foodName;
        this.num = num;
    }

    public String getFoodName(){
        return this.foodName;
    }

    public void setFoodName (String foodName) {
        this.foodName = foodName;
    }

    public int getNum() {
        return this.num;
    }

    public void setNum (int num) {
        this.num = num;
    }
}

顾客Guest.java

/**
 * 记录每个顾客的信息Guest
 * @author zhenye 2018/4/12
 */
public class Guest implements Runnable {
    // 对于这三个顾客而言,盘子中的食物是共享资源。static修饰!!!
    public static Dish dish;
    // 顾客名称
    private String name;
    // 该顾客吃一个食物的耗时
    private long time;
    // 统计每个顾客吃了多少食物,初始值为0
    private int count = 0;

    Guest (String name, long time) {
        this.name = name;
        this.time = time;
    }

    // 场景还原
    public void run() {
        while (true) {
            if (canGetFood()) {
                eat();
            }else {
                System.out.println( this.name + "想从盘子中拿"+dish.getFoodName()  +"吃,发现已经盘子中已经没有了,总共吃了" + this.count + "个" + dish.getFoodName());
                return;
            }
        }
    }

    // 这个方法必须是同步方法!!!同一时刻只能有一个人从盘子中拿食物。
    private static synchronized  boolean canGetFood () {
        boolean result = dish.getNum() > 0;
        if (result) {
            dish.setNum(dish.getNum() - 1);
            System.out.println( Thread.currentThread().getName() + "拿出一个"+dish.getFoodName() +"后,此时盘子中还剩下" + dish.getNum() + "个。");
        }
        return result;
    }

    // 模拟顾客拿到食物后的进食时间
    private void eat() {
        try {
            Thread.sleep(this.time);
            this.count++;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试类Main.java

package food;

/**
 * 多线程场景还原的测试类
 * @author zhenye 2018/4/12
 */
public class Main {
    public static void main(String[] args) {
        System.out.println("场景模拟开始:");
        // 进来了三个顾客:张三,李四,王五
        Guest guest1 = new Guest("张三",2000L);
        Guest guest2 = new Guest("李四",3000L);
        Guest guest3 = new Guest("王五",4000L);

        // 他们点了盘菜:10个包子
        Dish dish = new Dish("包子",10);
        Guest.dish = dish;

        Thread t1 = new Thread(guest1);
        t1.setName("张三");
        Thread t2 = new Thread(guest2);
        t2.setName("李四");
        Thread t3 = new Thread(guest3);
        t3.setName("王五");

        // start()方法启动三个线程,保证三个线程在同时运行
        t1.start();
        t2.start();
        t3.start();

        // 当前线程是main,等t1,t2,t3三个线程都处于DEAD状态后,再继续执行之后的代码。
        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("场景模拟结束!");
    }
}

测试效果图如下:

多线程案例-结果展示

最终的测试结果可能会有所变化,即最后一个包子可能是张三拿到,也可能是王五拿到。但是最终的结果是符合预期的,因为盘子中的包子数目怎么测试都不会到-1,即保证了线程的安全。

总结

结合Thread源码以及案例的学习,我有了以下的体会。

  • 1 多线程的优势在哪里?

    我认为多线程的优势主要有两个:1. 使用多线程来还原并发场景,能极大地提高程序的可读性;2. 使用多线程处理哪些不需要考虑线程安全的资源时,能提高程序的效率。

  • 2 如何保证线程安全?

    线程安全,也就是保证多个线程在操作共享资源时不会出现数据错乱的情况。我的想法是:确保同一时间,只能有一个线程在操作共享资源。加锁的方式之一—案例中canGetFood()方法的被关键字synchronized修饰后,同一时刻只能有一个线程调用此方法。

猜你喜欢

转载自blog.csdn.net/UtopiaOfArtoria/article/details/79932535
今日推荐