JAVA进阶系列 - 并发编程 - 第4篇 Thread

目标

  1. 线程的命名

  2. 线程的父子关系

  3. Runnable

内容

1. 线程的命名

在构造线程的时候可以为线程指定一个有意义的名称,这是一种比较好的做法。尤其是在一个线程比较多的程序中,为线程赋予一个包含特殊意义的名称有助于问题的排除与线程的跟踪,方便开发者在程序出错时进行回溯。

1.1. 线程的默认命名

在 Thread 的构造函数中有几个方法并没有为线程提供命名参数,具体方法如下:

  • Thread()

  • Thread(Runnable)

  • Thread(Runnable target, AccessControlContext acc)

  • Thread(ThreadGroup group, Runnable target)

那么此时的线程会有一个怎么的命名呢?打开 JDK 的源码,我们可以在这些方法中看到这样一段代码:

public Thread() {    init(null, null, "Thread-" + nextThreadNum(), 0);}

源码中我们可以看到,如果我们没有为线程显式的指定一个名称,那么线程将会以 "Thread-" 作为前缀与一个自增的数字进行组合,这个自增数字在整个 JVM 进程中将会不断自增,以便于我们对线程进行区分,代码如下:

/* For autonumbering anonymous threads. */private static int threadInitNumber;private static synchronized int nextThreadNum() {    return threadInitNumber++;}

1.2. 自定义线程名称

Thread 中同样也提供了为带线程命名参数的构造函数,如:

  • Thread(Runnable target, String name)

  • Thread(String name)

  • Thread(ThreadGroup group, Runnable target, String name)

  • Thread(ThreadGroup group, Runnable target, String name, long stackSize)

  • Thread(ThreadGroup group, String name)

具体的代码实现如下:

public Thread(String name) {    init(null, null, name, 0);}

可以看到,在该构造方法中,新创建的线程名称直接按我们传入的参数来进行命名。

除此之外,不管你是使用默认的命名还是自定义线程名称,只要线程还没有调用 start() 方法启动线程,我们就可以对其进行修改,使用 Thread 的 setName 方法:

public final synchronized void setName(String name) {    checkAccess();    if (name == null) {        throw new NullPointerException("name cannot be null");    }     this.name = name;    if (threadStatus != 0) {        setNativeName(name);    }}

2. 线程的父子关系

我们看 Thread 的所有构造函数,都是通过一个 init() 方法来实现的,通过对代码的跟踪,不难发现新创建的任何一个线程都会有一个父线程:

private void init(ThreadGroup g, Runnable target, String name,                  long stackSize, AccessControlContext acc,                  boolean inheritThreadLocals) {    if (name == null) {        throw new NullPointerException("name cannot be null");    }    this.name = name;    Thread parent = currentThread();    SecurityManager security = System.getSecurityManager();    if (g == null) {        /* Determine if it's an applet or not */        /* If there is a security manager, ask the security manager what to do. */        if (security != null) {            g = security.getThreadGroup();        }        /* If the security doesn't have a strong opinion of the matter use the parent thread group. */        if (g == null) {            g = parent.getThreadGroup();        }    }    /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */    g.checkAccess();    /* Do we have the required permissions? */    if (security != null) {        if (isCCLOverridden(getClass())) {            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);        }    }    g.addUnstarted();    this.group = g;    this.daemon = parent.isDaemon();    this.priority = parent.getPriority();    if (security == null || isCCLOverridden(parent.getClass()))        this.contextClassLoader = parent.getContextClassLoader();    else        this.contextClassLoader = parent.contextClassLoader;    this.inheritedAccessControlContext =        acc != null ? acc : AccessController.getContext();    this.target = target;    setPriority(priority);    if (inheritThreadLocals && parent.inheritableThreadLocals != null)        this.inheritableThreadLocals =        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);    /* Stash the specified stack size in case the VM cares */    this.stackSize = stackSize;    /* Set thread ID */    tid = nextThreadID();}

上面的代码中,currentThread() 是获取当前线程的,在上一篇的生命周期讲解中,我们说过线程的最初状态为 NEW ,没有执行 start 方法之前,它只能算是一个 Thread 实例,并没有将一个新的线程创建出来。因此 currentThread() 所得到的将会是创建它的那个线程,也就是执行该构造函数的线程,因此我们可以得出如下结论:

  • 一个线程一定是由另一个线程来创建的;

  • 被创建的线程其父线程是创建它的线程;

3. Runnable

在 Thread 这个类中,我们看到他实现了一个接口:

public class Thread implements Runnable {    // ...}

Runnable 接口定义了一个无参无返回值的 run 方法:

public interface Runnable {    /**     * When an object implementing interface <code>Runnable</code> is used     * to create a thread, starting the thread causes the object's     * <code>run</code> method to be called in that separately executing     * thread.     * <p>     * The general contract of the method <code>run</code> is that it may     * take any action whatsoever.     *     * @see     java.lang.Thread#run()     */    public abstract void run();}

在通过 Thread 类来定义子线程时,由于 Java 的单继承特性,我们无法在该子类中继续基础其它类来复用其它类的代码。而且通过继承 Thread 类和重写其 run 方法来定义业务逻辑的这种做法,将业务逻辑与 Thread 类耦合在了一起。

为了解决这两个问题,Java 提供了 Runnable 接口来实现 Thread 类和业务逻辑的解耦。有了 Runnable 接口之后,不用再以通过创建 Thread 类的子类的方式来定义执行逻辑。同时,也可以利用 Java 接口支持多继承的特性,可以在 Runnable 接口的实现类中继续继承其它类或接口来添加更多其它功能。

无论是 Runnable 的 run 方法,还是 Thread 类的 run 方法(实际上 Thread 类也是实现了 Runnable 接口) 都是想将线程的控制本身和业务逻辑的运行分离开来,达到职责分明,功能单一的原则。

Thread 的 run 方法和 Runnable 接口的 run 方法还有一个很重要的不同,那就是 Thread 类的 run 方法是不能共享的,也就是说 A 线程不能把 B 线程的 run 方法当作自己的执行单元,而使用 Runnable 接口则很容易就能实现这一点,使用同一个 Runnable 的实例构造不同的 Thread 实例。

public class ThreadDemo {    public static void main(String[] args) {        MyRunnable myRunnable = new MyRunnable();         Thread thread1 = new Thread(myRunnable, "线程1");        Thread thread2 = new Thread(myRunnable, "线程2");        Thread thread3 = new Thread(myRunnable, "线程3");        Thread thread4 = new Thread(myRunnable, "线程4");         thread1.start();        thread2.start();        thread3.start();        thread4.start();    }     static class MyRunnable implements Runnable {        private int index = 1;        private static final int MAX = 10;         @Override        public void run() {            while(index < MAX){                System.out.println(Thread.currentThread().getName() + ": " + index++);                try {                    Thread.sleep(100);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }         public MyRunnable() {        }    }}

运行结果:


通过该结果我们可以看到,4个线程公用同一个 Runnable,实现了共享功能。

总结

在该篇中,我们学习了 Thread 的命名,了解了线程间的父子关系,还通过讲解 Runnable 学到了线程共享的一个小技巧。后面还有很多更加有意思的内容等着我们一起去挖掘,大家加油吧!

今天的文章到这里就结束了,小伙伴们有什么建议或者意见请联系我改进哦,你们的支持是我最大的动力!!!

推荐阅读更多精彩内容