首页 > 代码库 > JAVA线程基础

JAVA线程基础

一、线程状态

由于参考的维度不一样,线程状态划分也不一样,我这里简单的分为5大类,并且会说明状态变迁的详细过程:

技术分享

1、新建(new):新创建了一个线程,但是并未执行start方法。

2、就绪(runnable):执行start方法后,该线程位于可运行的线程池中,等待被CPU选中执行。

3、运行(running):线程池中可运行的线程被CPU选中执行。

4、阻塞(BLOCKED):线程因为某种原因放弃了CPU的使用权,暂时停止运行。

5、死亡(dead):线程run()、main()方法结束。

下面我们来看下就绪、运行、阻塞这三种状态之间的变迁过程:

1、running---->runnable

线程所占有的时间片结束或者调用了Thread.yield()方法。

2、running---->blocked

进入阻塞状态的原因分为三种:

a、等待阻塞:运行的线程执行wait方法,JVM会把该线程放入等待队列,这个时候线程释放了原本占有的锁。

b、同步阻塞:运行的线程在竞争对象的同步锁时,若该同步锁被别的线程占用,JVM会把该线程放入锁池中。

c、其他阻塞:运行的线程执行sleep、join方法或者发出了I/O请求,JVM会把线程设置为阻塞状态。这种过程的线程不会释放版本占有的锁。

3、blocked--->runnable

a、等待阻塞:被其他线程用notify、notifyAll方法唤醒,唤醒之后该线程进入锁池,进行对象同步锁的竞争。

b、同步阻塞:竞争到对象的同步锁后进入到可运行状态。

c、其他阻塞:sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕,线程重新进入可运行状态。

二、守护线程与非守护线程

JAVA线程分为两类:守护线程(Daemon Thread)和用户线程(User Thread)。任何线程都可以是守护线程或者用户线程,唯一的区别就是虚拟机在退出时判断不一样。

虚拟机在所有非守护线程结束后自动离开,只要还有一个用户线程,虚拟机就不会提供运行。守护线程是用来服务用户线程的,如果没有用户线程在运行,守护线程也会结束。

守护线程最典型的使用场景就是GC(垃圾回收器)

public class DaemonTest
{
    public static void main(String[] args)
    {
        Thread thread = new Thread(new Runnable()
        {

            @Override
            public void run()
            {   
                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                finally
                {
                    System.out.println("DaemonThread finally run");
                }
            }});
     //设置为守护线程 thread.setDaemon(
true); thread.start();
     System.out.println("main Thread"); } }

 执行结果:

main Thread

这里可以看到,将线程thread设置为守护线程的时,这里只执行了用户线程,而没有执行被我们人工设置为守护线程的thread。这是由于用户线程先执行结束之后,没有其他的用户线程,JVM就退出了,守护线程也随之结束。

如果将一个线程设置为守护线程的时候,需要注意不要将执行关闭和清理资源等动作放在finally代码块里执行,因为在上述这种场景,finally块中的代码并没有如我们认为的那样一定会执行。

三、等待/通知机制

等待/通知的相关方法是任意java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,方法和描述如下:

技术分享

等待/通知已经被提炼出来一个经典范式,分别针对等待方(消费者)和通知方(生产者),原则如下:

1、等待方

a、获取对象锁

b、如果条件不满足,那么调用对象的wait方法,被通知后仍要检查条件。

c、条件满足则执行对应的逻辑

2、通知方

a、获得对象的锁

b、改变条件

c、通知所有等待在对象上的线程。

 

import java.text.SimpleDateFormat;
import java.util.Date;

public class WaitNotifyTest
{
    static Object lock = new Object();
    
    static boolean flag = false;
    
    public static void main(String[] args)
    {
        Thread waitThread = new Thread(new Wait(), "WaitThread");
        waitThread.start();
        Thread notifyThread = new Thread(new Notify(), "notifyThread");
        notifyThread.start();
    }
    
    static class Wait implements Runnable
    {
        
        @Override
        public void run()
        {
            synchronized (lock)
            {
                while (!flag)
                {
                    System.out.println(Thread.currentThread()
                        + "flag is false,wait@"
                        + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    try
                    {
                        lock.wait();
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
                // 满足条件时,完成工作
                System.out.println(Thread.currentThread()
                    + "flag is true,running@"
                    + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            }
        }
    }
    
    static class Notify implements Runnable
    {
        @Override
        public void run()
        {
            synchronized (lock)
            {
                System.out.println(Thread.currentThread() + "hold lock,notify@"
                    + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                lock.notify();
                flag = true;
            }
            synchronized (lock)
            {
                System.out.println(Thread.currentThread()
                    + "hold lock again,sleep@"
                    + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            }
        }
    }
}

 执行结果:

Thread[WaitThread,5,main]flag is false,wait@09:55:09
Thread[notifyThread,5,main]hold lock,notify@09:55:09
Thread[notifyThread,5,main]hold lock again,sleep@09:55:09
Thread[WaitThread,5,main]flag is true,running@09:55:09

线程waitThread 首先获取对象的锁,判断条件不满足之后,调用了对象的wait方法,从而放弃了对象锁进入了对象的等待队列waitQueue中,变成了等待状态。由于waitThread 释放的锁随即被线程notifyThread 所占有,线程notifyThread 在处理逻辑的同时调用了对象的notify方法,将waitThread从waitQueue转移到了SynchronizedQueue中,此时waitThread变成了阻塞状态。NotifyThread执行完毕释放锁之后,WaitThread获取对象锁之后从wait方法返回继续执行。

这里需要重点关注两个地方:

1、线程notifyThread调用兑现的notify方法后,waitThread线程并不是立即就从wait方法返回了,它必须要等线程notifyThread执行完毕释放锁之后,才能竞争上岗。

2、线程waitThread从wait方法返回后,理论上从wait方法后面的代码开始执行,但是如果有判断条件,则必须重新进行判断。

四、Thread.join()的使用

public class JoinTest
{
    public static void main(String[] args) throws Exception
    {
        Thread thread = new Thread(new Runnable()
        {

            @Override
            public void run()
            {
                for(int i=0; i<5; i++)
                {
                    System.out.println(i);
                }
                
            }});
        thread.start();
        thread.join();
        System.out.println("hello world");
        
    }
}

 执行结果:

0
1
2
3
4
hello world

Thread.join的含义是等待该线程终止。

程序中如果不调用线程thread的join方法,打印的结果应该是hello world在最前面。在调用thread.join方法后,main线程要等待thread执行结束才能继续。

join方法的源码片段:

while (isAlive()) {
        wait(0);
        }

这个就是运用等待/通知的经典范式来实现的,首先判断条件不满足,调用线程的wait方法。当线程终止时,会调用线程自身的notifyAll方法,通知所有等待在该线程对象上的线程。

JAVA线程基础