Java线程的常用知识

之前梳理了进程和线程的概念以及如何创建、结束进程和线程,本文总结一下线程使用过程中的知识点:

生命周期及状态变迁

  • 线程的生命周期包括:新建、就绪、运行、阻塞、死亡这几个状态。

  • 当线程被new之后,处于新建状态,并不是立马就被执行了,此时线程被虚拟机分配了内存以及初始化了线程内的变量。

  • 调用thread对象的start方法时,线程处于就绪状态,就绪的线程并不一定立马就被执行,仅仅只是创建了方法栈和计数器,需要等待JVM根据优先级、算法等规则调度之后才会进入运行状态。

  • 线程运行之后,因为前文说过的,多个线程之间通过轮流切换的方式使用CPU资源,所以JVM将CPU资源调度给别的线程后,当前线程就从运行状态转为就绪状态。

  • 当线程调用sleep方法、或者执行一个阻塞时的IO方法且没有返回结果时、调用了suspend(挂起)方法、调用一个同步对象但当前对象正在被其他线程锁持有时,线程就会进入阻塞状态。

  • 上文说到的几种方法,可以使线程进入死亡状态,该线程就结束了。

状态过程相关注意事项

  • 线程只能调用start方法,不能调用run方法,调用run方法只是将thread对象当作一个普通的对象来运行。
  • 除了新建状态外,其他状态不能再调用start方法,否则会抛出IllegalThreadStateException异常。
  • Thread.sleep()方法会使当前正在被执行的线程睡眠,让CPU可以去启动另一个处于就绪的线程,但是该方法在睡眠期间被唤醒会抛出InterruptedException异常。(上文停止线程也用到该原理)
  • 当时sleep时间结束后、调用的IO方法等待返回结果时、获取同步锁对象时、挂起的线程调用了resume方法时该线程就出于就绪状态,等待CPU调度。
  • 主线程执行完之后,不影响其他线程的运行,每一个线程运行起来后,级别相同。
  • 线程分守护线程普通线程,守护线程时JVM自己使用的线程,如垃圾回收线程,用户创建的线程基本上为普通线程,也可以自动设置Thread对象的setDaemon(true)将线程改为守护线程(在start之前调用)。Thread对象的isDaemon方法可以查询是否时守护线程。
  • 用户创建的线程设置为守护线程后,不管是否执行完,当进程中所有普通线程执行完,整个进程也就结束。
public class ThreadTest {
    public static void main(String[] args) {
        FutureTask futureTask = new FutureTask(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                sleep(50000);
                System.out.println("FutureTask 等待50秒后返回100");
                return 100;
            }
        });
        Thread thread = new Thread(futureTask, "futureTask");
        thread.setDaemon(true);
        thread.start();
        System.out.println("主线程执行完毕");
    }

}

//执行结果:

主线程执行完毕

Process finished with exit code 0
  • Thread对象的setPriority(int newPriority)可以设置线程优先级,范围从0~10,值越大,级别越高,级别越高,执行的机会就越多,不设置的话,默认级别和其父进程级别相同。

线程sleep和yield方法区别

  • sleep方法会让线程进入阻塞状态,在其睡眠时间内,该线程不会被执行。
  • yield方法只是将线程处于就绪状态,可能调度器又立马调度了该线程。
  • yield方法暂停后,和该线程优先级相同或者高的线程优先执行。
  • sleep之后,其他线程执行的机会有JVM决定,不一定就是高优先级的。
  • yield不会抛出异常,sleep会抛出InterruptedException异常。
public class ThreadTest {
    public static void main(String[] args) {
        SellRunnable sellRunnable = new SellRunnable();
        Thread thread1 = new Thread(sellRunnable, "1");
        thread1.setPriority(1);

        Thread thread2 = new Thread(sellRunnable, "2");
        thread2.setPriority(5);

        Thread thread3 = new Thread(sellRunnable, "3");
        thread3.setPriority(5);
        thread2.start();
        thread1.start();
        thread3.start();
    }

}

class SellRunnable implements Runnable {
    //有十张票
    int index = 10;

    public synchronized void sell() {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(index>=1){
            index--;
            System.out.println("售货窗口:" + Thread.currentThread().getName() + "  卖出了一张票,剩余:" + index);
        }else{
            System.out.println("售货窗口:" + Thread.currentThread().getName() + " 买票时没票了");
        }

    }

    @Override
    public void run() {

        while (index > 0) {
            System.out.println("售货窗口:" + Thread.currentThread().getName() + " 开始买票");
            sell();
        }

    }
}

//执行结果:
售货窗口:2 开始买票
售货窗口:3 开始买票
售货窗口:1 开始买票
售货窗口:2  卖出了一张票,剩余:9
售货窗口:2 开始买票
售货窗口:1  卖出了一张票,剩余:8
售货窗口:1 开始买票
售货窗口:3  卖出了一张票,剩余:7
售货窗口:3 开始买票
售货窗口:3  卖出了一张票,剩余:6
售货窗口:3 开始买票
售货窗口:1  卖出了一张票,剩余:5
售货窗口:1 开始买票
售货窗口:2  卖出了一张票,剩余:4
售货窗口:2 开始买票
售货窗口:2  卖出了一张票,剩余:3
售货窗口:2 开始买票
售货窗口:2  卖出了一张票,剩余:2
售货窗口:2 开始买票
售货窗口:2  卖出了一张票,剩余:1
售货窗口:2 开始买票
售货窗口:2  卖出了一张票,剩余:0
售货窗口:1 买票时没票了
售货窗口:3 买票时没票了

Process finished with exit code 0  //可以看出,2、3执行的相对较多
    
    
//加入sleep 
@Override
    public void run() {

        while (index > 0) {
            try {
                Thread.sleep(100);
                System.out.println("售货窗口:" + Thread.currentThread().getName() + " 开始买票");
                sell();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

//执行结果:

售货窗口:1 开始买票
售货窗口:2 开始买票
售货窗口:3 开始买票
售货窗口:1  卖出了一张票,剩余:9
售货窗口:3  卖出了一张票,剩余:8
售货窗口:2  卖出了一张票,剩余:7
售货窗口:3 开始买票
售货窗口:2 开始买票
售货窗口:1 开始买票
售货窗口:3  卖出了一张票,剩余:6
售货窗口:1  卖出了一张票,剩余:5
售货窗口:2  卖出了一张票,剩余:4
售货窗口:3 开始买票
售货窗口:2 开始买票
售货窗口:1 开始买票
售货窗口:3  卖出了一张票,剩余:3
售货窗口:1  卖出了一张票,剩余:2
售货窗口:2  卖出了一张票,剩余:1
售货窗口:3 开始买票
售货窗口:2 开始买票
售货窗口:1 开始买票
售货窗口:3  卖出了一张票,剩余:0
售货窗口:1 买票时没票了
售货窗口:2 买票时没票了

Process finished with exit code 0  // sleep之后,各个窗口差不多
    

    
    
//加入yield
    
@Override
    public void run() {

        while (index > 0) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("售货窗口:" + Thread.currentThread().getName() + " 开始买票");
            sell();

            if ("2".equals(Thread.currentThread().getName())) {
                Thread.yield();
            }
        }
    }

//执行结果:

售货窗口:2 开始买票
售货窗口:3 开始买票
售货窗口:1 开始买票
售货窗口:2  卖出了一张票,剩余:9
售货窗口:2 开始买票
售货窗口:1  卖出了一张票,剩余:8
售货窗口:1 开始买票
售货窗口:3  卖出了一张票,剩余:7
售货窗口:1  卖出了一张票,剩余:6
售货窗口:3 开始买票
售货窗口:1 开始买票
售货窗口:3  卖出了一张票,剩余:5
售货窗口:3 开始买票
售货窗口:2  卖出了一张票,剩余:4
售货窗口:2 开始买票
售货窗口:3  卖出了一张票,剩余:3
售货窗口:3 开始买票
售货窗口:1  卖出了一张票,剩余:2
售货窗口:1 开始买票
售货窗口:3  卖出了一张票,剩余:1
售货窗口:3 开始买票
售货窗口:2  卖出了一张票,剩余:0
售货窗口:3 买票时没票了
售货窗口:1 买票时没票了

Process finished with exit code 0 // 2变少,相对3变多(执行太快,yield看不出效果)

以上!
进程与线程的创建和销毁
线程知识点总结
线程同步

    原文作者:AnonyPer
    原文地址: https://www.jianshu.com/p/2394317257ec
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞