Java高并发编程-1

本文最后更新于:2020年12月4日 凌晨

什么是线程?
线程是程序执行的一个路径,每一个线程都有自己的局部变量表、程序计数器以及各自的生命周期。

线程的生命周期
线程的生命周期包括以下5个阶段:
**
NEW
RUNNABLE
RUNNING
BLOCKED
TERMINATED
**
NEW 状态:
在没有执行start之前的状态,表示线程对象被创建,仅此而已。和创建一个其他对象没有区别。
RUNNABLE状态:
调用start方法后,JVM进程创建一个线程,但是仅仅是创建一个线程,并不代表线程被执行,此时的线程在等待CPU的调度。
RUNNING状态:
此时的线程开始真正的执行逻辑代码,有一个知识点是一个处于RUNNING状态的线程事实上也是一个RUNNABLE状态的线程,反过来不成立。(我的理解是,正在执行的线程可以是在单核情况下运行的,这就可以说是线程在RUNNABLE和RUNNING之间切换,所以可以说是处于RUNNABLE。但是一个处于RUNNABLE的线程本身就就没有被运行,就不可以说是处于RUNNING状态。)RUNNING状态下的线程可以说是一个想成的必经状态,除非线程意外终止。否则无论是进入BLOCKED状态还是进入TERMINATED状态,都需要经过RUNNING状态。
BLOCKED状态:
从RUNNING状态进入BLOCKED状态的几种情况:
1、调用了sleep或者wait方法,进入了waitSet中;
2、进行了阻塞的IO操作;
3、获取某个锁资源,从而加入了该锁的阻塞队列;
TERMINATED状态:
TERMINATED状态是线程的最终状态,该状态下不会切换到其他任何状态,因为此章台代表线程的生命周期已经结束。

线程的start方法——模板模式在Thread中的应用
start方法的源码:

publi c  synchronized void start() {
     if (threadStatus != 0) #检查线程状态
     throw new IllegalThreadStateException(); 
     group.add(this); 
     boolean started = false; 
     try { 
         startO();
          started = true; 
          } finally { 
              try {
                   if (!started) {
                        group.threadStartFailed(this); 
                        } 
                        catch (Throwable ignore) {
                        }
              }
          }

核心函数start0是一个本地方法:

private native void start0();

从上面的代码可以看出,一个线程是不可以start两次的,不然会报非法线程异常。同时一个生命周期结束的线程在调用start方法是也会报出相同的异常。同时还可以知道线程的真正逻辑在run方法中,这里的run函数就相当于模板模式中需要重写的函数,而start函数就是模板模式中的模板函数。这样做的好处是将线程的控制和业务的执行分离开来。

Runnable和Thread的关系及使用
网上看了好多资料,最后还是没有找到一个比较有说服力的答案,最后没有办法。我打开了Thread的源码一探究竟。

//开头是一些版权信息,接下来是一大段介绍摘抄几句比较重要的:

 Each thread
 * may or may not also be marked as a daemon. When code running in
 * some thread creates a new <code>Thread</code> object, the new
 * thread has its priority initially set equal to the priority of the
 * creating thread, and is a daemon thread if and only if the
 * creating thread is a daemon.
 (貌似说的是线程被创建的时候会被设置默认优先级,默认的优先级是和创建线程的优先级是一样的)

 * When a Java Virtual Machine starts up, there is usually a single
 * non-daemon thread (which typically calls the method named
 * <code>main</code> of some designated class). The Java Virtual
 * Machine continues to execute threads until either of the following
 * occurs:
 * <ul>
 * <li>The <code>exit</code> method of class <code>Runtime</code> has been
 *     called and the security manager has permitted the exit operation
 *     to take place.
 * <li>All threads that are not daemon threads have died, either by
 *     returning from the call to the <code>run</code> method or by
 *     throwing an exception that propagates beyond the <code>run</code>
 *     method.
 * </ul>
 (JVM启动时只有一个非守护线程,这些线程一直被JVM执行直到一下几种情况:
    *安全退出
    *非守护线程全部死亡
    *引起异常)

* There are two ways to create a new thread of execution. One is to
 * declare a class to be a subclass of <code>Thread</code>. This
 * subclass should override the <code>run</code> method of class
 * <code>Thread</code>. An instance of the subclass can then be
 * allocated and started. For example, a thread that computes primes
 * larger than a stated value could be written as follows:
 * <hr><blockquote><pre>
 *     class PrimeThread extends Thread {
 *         long minPrime;
 *         PrimeThread(long minPrime) {
 *             this.minPrime = minPrime;
 *         }
 *
 *         public void run() {
 *             // compute primes larger than minPrime
 *             &nbsp;.&nbsp;.&nbsp;.
 *         }
 *     }
 * </pre></blockquote><hr>
 * <p>
 * The following code would then create a thread and start it running:
 * <blockquote><pre>
 *     PrimeThread p = new PrimeThread(143);
 *     p.start();
 * </pre></blockquote>
 * <p>
 (有两种方法可以创建一个线程,一是继承Thread类,重写run方法并给出了实例函数)

 * The other way to create a thread is to declare a class that
 * implements the <code>Runnable</code> interface. That class then
 * implements the <code>run</code> method. An instance of the class can
 * then be allocated, passed as an argument when creating
 * <code>Thread</code>, and started. The same example in this other
 * style looks like the following:
 * <hr><blockquote><pre>
 *     class PrimeRun implements Runnable {
 *         long minPrime;
 *         PrimeRun(long minPrime) {
 *             this.minPrime = minPrime;
 *         }
 *
 *         public void run() {
 *             // compute primes larger than minPrime
 *             &nbsp;.&nbsp;.&nbsp;.
 *         }
 *     }
 * </pre></blockquote><hr>
 * <p>
 * The following code would then create a thread and start it running:
 * <blockquote><pre>
 *     PrimeRun p = new PrimeRun(143);
 *     new Thread(p).start();
 * </pre></blockquote>
 * <p>
 * Every thread has a name for identification purposes. More than
 * one thread may have the same name. If a name is not specified when
 * a thread is created, a new name is generated for it.
 * <p>
 * Unless otherwise noted, passing a {@code null} argument to a constructor
 * or method in this class will cause a {@link NullPointerException} to be
 * thrown.
 (另外一方法是实现Runnable接口,并给出了实现方法,需要注意的是两种方法的启动线程的方式是不一样的。)

其实从上面的介绍就可以看出来,Thread和Runnable是没有本质区别的,只不过按照OOP来说一个是具体的实现,一个是接口的规范。只是两种实现线程的不同操作。
接下来是几个比较重要的函数:

//用来生成线程ID的整型变量,为了保证唯一性添加synchronized同步锁。
 /* For autonumbering anonymous threads. */
private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

//可以看到前面说到的start两次线程失败是因为有这个参数,同时volatile参数是来保持其可见性,当其被改变时调用此线程的对象可以立即感知到。
private volatile int threadStatus = 0;

//属性中有target,解释为将要被run的对象。
/* What will be run. */
    private Runnable target;

//Thread的构造函数太多了,不一一罗列。其中有Runnable target的上面的target属性就会被赋值,结合下面的run方法就可以知道Thread执行的也是Runnable的方法。

@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

一圈看下来,发现Thread是一个完整的类,提供了丰富的接口函数实现,通过继承Thread实现的线程对象可以有丰富的功能。利用Runnable实现的线程最终还是要放进Thread中去启动,只不过可以设计自己的业务逻辑而不必继承Thread的方法。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!