系统掌握并发编程系列(一)精准理解线程的创建和停止

什么是线程

很多人在接触多线程的时候,听的最多可能是“线程是操作系统进行调度的最小单位”,这可能又要问“什么是调度的最小单位”了,这样理解很抽象难懂。既然线程是跟编程有关,那我们就从代码的角度去理解。假设main方法中有三个methodA、methodB、methodC方法,在main方法中依次调用执行,main()->methodC()->methodB()->methodC(),main方法启动后主线程调用methodA、methodB、methodC方法的过程就是一条代码执行流。所以从代码的角度来讲,线程是一条代码执行流,完成一个特定的任务

代码示例

public class MyThread {
    public static void main(String[] args) {
        methodA();
        methodB();
        methodC();
    }
    private static void methodC() {
    }
    private static void methodB() {
    }
    private static void methodA() {
    }
}

在这里插入图片描述

线程的创建方式

关于线程的创建方式,网上也是有不同的版本。我的理解是,本质上创建线程的方式只有一种,就是创建一个Thread对象,并调用Thread对象的start()方法启动线程,只是创建线程时重写run()方法实现特定任务代码的方式有多种方式而已。从Thread类的构造函数,理解线程的创建方式,下图是Thread类的相关构造函数。

有多少个构造函数就有多少中创建Thread对象的方式,但用不同的构造函数创建线程本质都是一样:创建Thread对象并调用start()方法来启动线程。接下来我们来看看java.util.concurrent.ThreadPoolExecutor中的线程池是如何创建线程的,如果对线程池还有没有了解,没关系,在这里我们只想了解jdk里面创建线程的方式,加深对线程创建方式的理解,避免上来就是死记硬背”继承Thread类或者实现Runnable接口“。

ThreadPoolExecutor类中有个addWorker的方法,主要是是往线程池中添加任务。我们摘取主要代码进行分析。

private boolean addWorker(Runnable firstTask, boolean core) {
    ...
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        //传入任务对象,创建Worker对象
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            ...
            if (workerAdded) {
                //启动线程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

Worker是其中的一个内部类,封装了线程对象和任务对象。

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    ...
    /** worker运行的线程对象 */
    final Thread thread;
    /** worker运行的任务 */
    Runnable firstTask;
	...
    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
        //从线程工厂中创建线程
        this.thread = getThreadFactory().newThread(this);
    }

    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this);
    }
	...
}

从上面的代码中可以看出,线程池的线程对象是从线程工厂中获取的。那么线程工厂又是如何创建线程的,我们接着往下看。
在这里插入图片描述

从ThreadPoolExecutor的构造函数中可以看出,创建线程池时可以指定线程工厂,如果不指定将会采用默认的线程工厂。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

调用Executors的defaultThreadFactory()方法创建线程工厂。

public static ThreadFactory defaultThreadFactory() {
    return new DefaultThreadFactory();
}

DefaultThreadFactory是Executors中的一个内部类,实现了线程工厂接口ThreadFactory。

static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        //创建线程
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

ThreadFactory结果只有一个创建线程方法。

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

从上面的代码中可以看出,线程工厂接口只有一个创建线程的方法,创建线程的具体过程由子类去实现,也是通过new Thread()的方式去创建线程的。这采用了面向对象设计中的一个核心原则:单一职责原则。也就是一个类应该只有一个职责。花了这么长的篇幅去了解线程池中创建线程的过程,目的就是理解创建线程的本质方式,相信大家对线程的创建方式有了更深的了解了。

实现任务代码的方式

理解了创建线程的本质方式,还是有必要来了解下实现任务代码的方式,毕竟这才是实现具体的业务逻辑的方式,下面列举了几种场景的实现任务代码的方式。

1、创建Thread匿名内部类并重写run方法执行任务逻辑

public class MyThread {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("execute MyThread job");
            }
        };
        t.start();
    }
}

执行t.start()该行代码就会去执行Thread类的run()方法中的代码。

2、创建Thread子类并重写run方法执行任务逻辑

如要执行自己的代码,则需要重写new一个Thread的子类,然后重写run()方法,如上也可以将上面匿名内部类的写法改成下面更直观的写法:

public class MyThread2 extends Thread{
    @Override
    public void run() {
        System.out.printf("execute MyThread2 job");
    }
    public static void main(String[] args) {
        Thread t = new MyThread2();
        t.start();
    }
}

可见,两种写法都是创建Thread的子类并重写run方法。

3、创建Runnable匿名内部类并重写run方法

在Thread的构造函数中,有一个构造函数接受一个Runnable对象,当Runnable对象不为空的时候,将会执行Runnable对象的run方法。

public class MyThread3 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("execute MyThread3 job");
            }
        });
        t.start();
    }
}

线程是一个对象,线程要运行的代码也是一个对象,封装在Runnable对象里面,更符合面向对象的思维。

4、创建Runnable子类并重写run方法执行任务逻辑

也可以将上面匿名内部类的写法改成下面更直观的写法:

public class MyThread4 implements Runnable {
    @Override
    public void run() {
        System.out.println("execute MyThread3 job");
    }
    public static void main(String[] args) {
        MyThread4 r = new MyThread4();
        Thread t = new Thread(r);
        t.start();
    }
}

可见,两种方式都是创建Runnable对象并传递到Thread构造函数。

正确停止线程

上面的例子中,我们只管线程的创建,却不需要用代码显示的停止线程,这又是为什么?如何停止线程?代码执行完会自动销毁线程吗?如何正确停止线程?Thread类中有没有方法获取线程启动到销毁整个过程的运行状态?答案是有的。通过Thread类的getState()方法我们可以获取线程的运行状态。

    /**
     * Returns the state of this thread.
     * This method is designed for use in monitoring of the system state,
     * not for synchronization control.
     *
     * @return this thread's state.
     * @since 1.5
     */
    public State getState() {
        // get current thread state
        return sun.misc.VM.toThreadState(threadStatus);
    }

我们先通过一个例子来观察线程的运行过程的状态变化。

public class MyThread {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("execute MyThread job,thread state:"+getState());
            }
        };
        System.out.println("before start thread,thread state:"+ t.getState());
        t.start();
        try {
            //让主线程睡眠1秒,等待子线程t先执行完
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("after start thread,thread state:"+ t.getState());
    }
}

运行结果

before start thread,thread state:NEW
execute MyThread job,thread state:RUNNABLE
after start thread,thread state:TERMINATED

从运行结果可以看出,程序运行完或者说run()方法运行完成后,线程进入TERMINATED状态,由此我们可以得出结论:run()方法执行完线程进入停止状态,这是线程自然终止的过程。关于线程的运行状态,我们将在后续的文章中学习,在此我们主要了解线程的终止后的状态。Thread类中有start()方法启动线程,那肯定有停止线程的相关方法,我们也可以通过Thread类提供的一些方法来终止线程。

在这里插入图片描述

从上图可以看出,画横线的方法都被废弃了,其中包括stop()方法。

/**
     * Forces the thread to stop executing.
     * ...
     * @deprecated This method is inherently unsafe.  Stopping a thread with
     *       Thread.stop causes it to unlock all of the monitors that it
     *       has locked (as a natural consequence of the unchecked
     *       <code>ThreadDeath</code> exception propagating up the stack).  If
     *       any of the objects previously protected by these monitors were in
     *       an inconsistent state, the damaged objects become visible to
     *       other threads, potentially resulting in arbitrary behavior.  Many
     *       uses of <code>stop</code> should be replaced by code that simply
     *       modifies some variable to indicate that the target thread should
     *       stop running.  The target thread should check this variable
     *       regularly, and return from its run method in an orderly fashion
     *       if the variable indicates that it is to stop running.  If the
     *       target thread waits for long periods (on a condition variable,
     *       for example), the <code>interrupt</code> method should be used to
     *       interrupt the wait.
     *       For more information, see
     *       <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why
     *       are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
     */
    @Deprecated
    public final void stop() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            checkAccess();
            if (this != Thread.currentThread()) {
                security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);
            }
        }
        // A zero status value corresponds to "NEW", it can't change to
        // not-NEW because we hold the lock.
        if (threadStatus != 0) {
            resume(); // Wake up thread if it was suspended; no-op otherwise
        }

        // The VM can handle all thread states
        stop0(new ThreadDeath());
    }

从stop()方法的注释可以看出,其被废弃的主要原因是简单粗暴的终止线程,让程序执行结果具有不确定性,固有的天生的不安全。注释中也提到停止线程的另一个方法Thread.suspend 同样被废弃。

/**
 * Suspends this thread.
 ...
 * @deprecated   This method has been deprecated, as it is
 *   inherently deadlock-prone.  If the target thread holds a lock on the
 *   monitor protecting a critical system resource when it is suspended, no
 *   thread can access this resource until the target thread is resumed. If
 *   the thread that would resume the target thread attempts to lock this
 *   monitor prior to calling <code>resume</code>, deadlock results.  Such
 *   deadlocks typically manifest themselves as "frozen" processes.
 *   For more information, see
 *   <a href="{@docRoot}/../technotes/guides/concurrency/threadPrimitiveDeprecation.html">Why
 *   are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
 */
@Deprecated
public final void suspend() {
    checkAccess();
    suspend0();
}

从suspend()方法的注释可以看出,其被废弃的主要原因是固有的天生的容易造成死锁。由于停止线程涉及到锁、监视器、共享资源等知识,在此不深入这些内容,我们先从官方注释来理解其被废弃的原因即可,等后面学习到这块的知识后再回过头来理解就更容易了。

一眼就看出是停止线程的stop()方法,suspend()方法,却不是正确停止线程的方法,那么哪个方法才是正确停止线程的方法?先说结论,是interrupt()方法。通常通过interrupt()方法来终止处于阻塞状态或等待状态的线程

/**
 * Interrupts this thread.
 *
 * <p> Unless the current thread is interrupting itself, which is
 * always permitted, the {@link #checkAccess() checkAccess} method
 * of this thread is invoked, which may cause a {@link
 * SecurityException} to be thrown.
 *
 * <p> If this thread is blocked in an invocation of the {@link
 * Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
 * Object#wait(long, int) wait(long, int)} methods of the {@link Object}
 * class, or of the {@link #join()}, {@link #join(long)}, {@link
 * #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
 * methods of this class, then its interrupt status will be cleared and it
 * will receive an {@link InterruptedException}.
 *
 * <p> If this thread is blocked in an I/O operation upon an {@link
 * java.nio.channels.InterruptibleChannel InterruptibleChannel}
 * then the channel will be closed, the thread's interrupt
 * status will be set, and the thread will receive a {@link
 * java.nio.channels.ClosedByInterruptException}.
 *
 * <p> If this thread is blocked in a {@link java.nio.channels.Selector}
 * then the thread's interrupt status will be set and it will return
 * immediately from the selection operation, possibly with a non-zero
 * value, just as if the selector's {@link
 * java.nio.channels.Selector#wakeup wakeup} method were invoked.
 *
 * <p> If none of the previous conditions hold then this thread's interrupt
 * status will be set. </p>
 *
 * <p> Interrupting a thread that is not alive need not have any effect.
 *
 * @throws  SecurityException
 *          if the current thread cannot modify this thread
 *
 * @revised 6.0
 * @spec JSR-51
 */
public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

上面interrupt()的注释,主要描述了线程因为各种原因(如调用wait()方法、sleep()方法等)进入阻塞状态或等待状态时,调用interrupt()方法时线程的中断状态将被修改并抛出异常从而终止线程的运行。我们通过代码来验证这一结论。

public class InterruptThread {
    public static void main(String[] args) throws Exception{
        Thread t1 =new Thread(()-> {
            try {
                System.out.println("t1 thread sleep ");
                //子线程t1调用sleep()方法使其进入等待状态
                Thread.sleep()方法进入等待状态(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();

        //主线程睡眠一会等待子线程t1执行
        Thread.sleep(500L);
        System.out.println("t1 thread before interrupt state: " + t1.getState());
        System.out.println("main thread interrupt t1 thread");
        //中断子线程t1
        t1.interrupt();

        Thread.sleep(500L);
        System.out.println("t1 thread after interrupted state: " + t1.getState());
    }
}

运行结果

t1 thread sleep 
t1 thread before interrupt state: TIMED_WAITING
main thread interrupt t1 thread
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.demo.service.concurrent.InterruptThread.lambda$main$0(InterruptThread.java:10)
	at java.lang.Thread.run(Thread.java:748)
t1 thread after interrupted state: TERMINATED

上面的运行结果印证了上述结论,线程进入TIMED_WAITING等待状态后,调用interrupt()会抛出异常并终止线程的运行。停止线程的方式还有其他方式,后面我们学到相关知识后再回过来补充。

总结

本文从官方jdk源码的角度去分析理解创建线程的本质方式和正确停止线程的方式,希望能他大家带来更深刻的理解而不是单纯的死记硬背。

如果文章对您有帮助,不妨“帧栈”一下,关注“帧栈”公众号,第一时间获取推送文章,您的肯定将是我写作的动力!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值