Bootstrap

Java线程创建、状态和执行顺序

线程调度和时间片

现代操作系统提供了强大的线程管理能力,因此Java不需要单独的进行线程的管理和调度,而是将线程调度工作委托给操作系统去完成。在某些操作系统中(Solaris)中,JVM中的线程和操作系统的本地线程甚至可以做到一一对应起来。

由于cpu的计算频率相当的高,每秒钟计算几十亿次。因此现代操作系统都是以毫秒为维度对cpu进行分片,然后通过时间分片进行线程的调度和管理。window XP 时间片的长度是20ms。

当前各个操作系统主流的调度模型为 “抢占式调度模型”,即优先级较高的线程,会有限分配cpu时间片。关于 “抢占式调度模型” 的详细信息,可以在操作系统原理中找寻到。

JAVA线程的创建方式

在JAVA中,可以通过下面3种方式来创建线程:

1、集成Thread类;

2、实现Runnable接口;

3、实现Callable接口;


package com.java.thread.demo001;

import java.util.concurrent.*;

/**
 * 1、java实现线程的3种方式 Thread Runnable Callable
 * */
public class Demo001_a {

    public static void main(String[] args) {

        System.out.println(Thread.currentThread().getName() + "  main 线程");
        new ThreadTest().start();
        new Thread(new RunnableTest()).start();

        //callable 线程启动
        try {
            CallableTest callable = new CallableTest();
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Future<String> future = executor.submit(callable);
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class ThreadTest extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "通过Thread类实现线程");
    }
}

class RunnableTest implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "通过Runnable类实现线程");
    }
}

class CallableTest implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "通过Callable类实现线程");
        return "callable 线程";
    }
}

JAVA线程的状态

在java中一个线程从创建到消亡,需要经历多种不同的状态,这个也称为线程的生命周期。
线程的生命周期大概如下图所示:

在这里插入图片描述

NEW: 线程被构建,但是还没有调用start()方法。

RUNNABLE:可运行状态,可运行状态包括运行中状态和就绪状态。

BLOCKED:阻塞状态,等待其他线程释放锁。

WAITING: 等待状态,需要等待其他线程对其进行通知或者中断等操作,从而进入下一个状态。

TIME_WAITING:超时等待状态,可以在一定时间自行返回。

TERMINATED:终止状态,当前线程执行完毕。

下面我们通过程序来验证线程的生命周期。

package com.java.thread.demo001;

import java.util.concurrent.*;

public class Demo001_b {

    public static void main(String[] args) {

         new Thread(new WaitingTime(),"WaitingTime").start();
         new Thread(new WaitingState(),"WaitingState").start();

         //先启动,并且抢占到锁 且不释放,TIMED_WAITING 状态
        new Thread(new BlockedThread(),"BlockedThread-1").start();
        //后启动,等待BlockedThread-1 释放锁, BLOCKED 状态
        new Thread(new BlockedThread(),"BlockedThread-2").start();
    }
}

/**
 * java.lang.Thread.State: TIMED_WAITING (sleeping)
 * 1、超时等待状态、可以在一定的时间自行返回;
 * */
class WaitingTime implements Runnable{
    @Override
    public void run() {
        while(true){
            waitSecond(2000);
        }
    }

    //线程等待多少秒
    public static final void waitSecond(long seconds){
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 *  java.lang.Thread.State: WAITING (on object monitor)
 *  1、WaitingState 这个类无论创建多少个实例,
 *     synchronized 锁都是同一个。
 *  2、WaitingState.class.wait() 进入 WAITING 状态。
 * */
class WaitingState implements Runnable{

    @Override
    public void run() {
        while(true){
             synchronized (WaitingState.class){
                 try {
                     WaitingState.class.wait();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
        }
    }
}

class BlockedThread implements Runnable{
    @Override
    public void run() {
        synchronized (BlockedThread.class){
            while(true){
                System.out.println(Thread.currentThread().getName() + " 抢占锁");
                WaitingTime.waitSecond(10);
            }
        }
    }
}


通过jvm工具来查看线程的状态

C:\Users\WIN>jps
12736
2336
23760 NailgunRunner
20100 Launcher
24980 Launcher
12552 Jps
22220
24428 Demo001_b

C:\Users\WIN>jstack 24428

JAVA线程的执行顺序

1、线程的执行顺序是不确定的,也就是说每次调用多个线程的结果是不确定的。

package com.java.thread.demo001;

import java.util.concurrent.TimeUnit;

/**
 * 1、线程的执行顺序;
 *    线程的执行顺序是无法确定的,也就是说每次执行的结果过都是不一样的。
 * */
public class Demo001_c {

    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(() -> {
            System.out.println("thread1..");
        });
        Thread thread2 = new Thread(() -> {
            System.out.println("thread2..");
        });
        Thread thread3 = new Thread(() -> {
            System.out.println("thread3..");
        });

        thread1.start();

        thread2.start();

        thread3.start();
    }
}

//第一次运行结果如下:
thread1..
thread3..
thread2..

//第二次运行结果如下
thread2..
thread3..
thread1..


2、如果想要多线程按照一定的顺序来执行,可以使用 join 来实现。


package com.java.thread.demo001;

import java.util.concurrent.TimeUnit;

/**
 * 1、线程的执行顺序;
 *    线程的执行顺序是无法确定的,也就是说每次执行的结果过都是不一样的。
 * */
public class Demo001_c {

    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(() -> {
            System.out.println("thread1..");
        });
        Thread thread2 = new Thread(() -> {
            System.out.println("thread2..");
        });
        Thread thread3 = new Thread(() -> {
            System.out.println("thread3..");
        });

        thread1.start();
        thread1.join(); //让主线程等待子线程完成

        thread2.start();
        thread2.join();

        thread3.start();
        thread3.join();
    }
}


//运行结果如下:
thread1..
thread2..
thread3..


3、我们来思考一下:join是如何来保证线程的顺序执行的了?

通过追踪join方法的代码,我们发现其是通过JNI调用JDK底层方法wait()来实现的。
调用wait方法会使得主线程处于等待状态,直到子线程结束之后,才执行下一步指令。

线程的优先级

在Thread类中有一个方法可以设置线程的优先级,setPriority(int i ); i的值为 1-10, 最大优先级为10, 最小优先级为1,默认优先级为5。

下面我们通过一个实验来验证一下java中的优先级。

public class Demo002_d {

        public static void main(String[] args) {
                testThreadPriority();
        }

        /**
         * @desc 多线程优先级测试
         * @desc 思路:启动10个线程做计数器累加,线程的优先级分别为 1-10 , 1 分钟后结束。
         *            计数器的值越大, 则说明该线程抢到的cpu时间片越多。
         * */
        public static void testThreadPriority(){
                TestThreadPriority[] threads = new TestThreadPriority[10];
                for(int i=0; i<10;i++){
                        TestThreadPriority thread = new TestThreadPriority();
                        threads[i] = thread;
                        threads[i].setName("thread-" + i);
                        threads[i].setPriority(i+1);
                }

                //启动线程
                for(int i=0; i<10;i++){
                        threads[i].start();
                }

                try {
                        TimeUnit.SECONDS.sleep(20);
                } catch (InterruptedException e) {
                        e.printStackTrace();
                }

                //停止线程
                for(int i=0; i<10;i++){
                        threads[i].stop();
                }

                for(int i=0; i<10;i++){
                        System.out.println(threads[i].getName()
                                + ": "
                                + threads[i].acounter);;
                }
        }

        static class TestThreadPriority extends   Thread{
                public Long acounter = 0L;
                @Override
                public void run() {
                      while(true){
                              acounter ++;
                      }
                }
        }
}

返回结果如下:
thread-0: 21266406
thread-1: 21563155
thread-2: 115825951
thread-3: 87804208
thread-4: 356981196
thread-5: 265969365
thread-6: 618522239
thread-7: 513331872
thread-8: 664392897
thread-9: 764687800

可以观察到,优先级越高其抢占到的cpu执行时间片就越多,这种情况并不是绝对的。但是大体上是无限接近这种情况的。这也侧面印证了cpu执行顺序是无序的。

参考

<<深入理解高并发编程>>

;