并发学习第二篇——Thread

一、类的声明

public class Thread implements Runnable {
}

Thread本身实现了Runnable接口,Runnable是一个task,Thread是执行它的载体

二、native方法

以下native方法直接与操作系统接触了,看名字应该就懂意思

/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
      registerNatives();
}

public static native Thread currentThread();-----返回执行当前代码的线程
public static native void yield();          -----当前线程会让步,释放当前cpu的执行权,把执行机会让给其他线程
public static native void sleep(long millis) throws InterruptedException;
private native void start0();
private native boolean isInterrupted(boolean ClearInterrupted);
public final native boolean isAlive();    ------判断当前线程是否存活
public static native boolean holdsLock(Object obj);

三、关键属性

1、volatile关键字修饰的

//volatile修饰的表明对所有线程可见
private volatile String name;
//线程状态值为0表示线程尚未started
private volatile int threadStatus = 0;
//高优先级的线程会抢占低优先级的线程的执行权,但只是被执行的机会和概率更大,并不是说高优先级的执行完了才会去执行低优先级的
private int priority;

 

2、其他属性

private boolean     daemon = false;
//需要运行的Runnable对象
private Runnable target;
//持有的ThreadLocalMap对象
ThreadLocal.ThreadLocalMap threadLocals = null;
//线程id
private long tid;
//指定优先级时最小值
public final static int MIN_PRIORITY = 1;
//分派给线程的默认优先级值
public final static int NORM_PRIORITY = 5;
//线程可以指定的优先级的最大值
public final static int MAX_PRIORITY = 10;

四、构造方法

构造方法之前,先看看init方法,因为所有的构造方法都调用了这个,这里忽略掉了非关键的一些代码逻辑

init主要做初始化线程名(如果未指定会默认分配成"Thread-" + nextThreadNum())

初始化待执行的target(一个Runnable)

分配一个线程ID

private void init(Runnable target, String name) {
     init(target, name);
}
private void init(Runnable target, String name) {
     if (name == null) {
         throw new NullPointerException("name cannot be null");
     }
     this.name = name;
     //被创建的这个线程是当前线程的子线程
     Thread parent = currentThread();
     //被创建的这个线程默认拥有当前线程的daemon和priority属性
     this.daemon = parent.isDaemon();
     this.priority = parent.getPriority();
     this.target = target;
     //分配线程ID
     tid = nextThreadID();
}

以下构造方法也都舍弃了一些非关键参数和代码逻辑

1、无参

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

2、仅指定任务对象作为参数

public Thread(Runnable target) {
    init(target, "Thread-" + nextThreadNum());
}

记笔记:当线程启动后,target的run()方法被调用,如果target==null,run()方法不做任何事情

3、仅指定线程名作为参数

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

4、指定任务对象和线程名

public Thread(Runnable target, String name) {
     init(target, name);
}

记笔记:当线程启动时将调用target的run()方法,如果target==nul,将调用Thread的run()方法

五、start()方法

源码注释

/**
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     * The result is that two threads are running concurrently: the
     * current thread (which returns from the call to the
     * <code>start</code> method) and the other thread (which executes its
     * <code>run</code> method).
     * <p>
     * It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
     *
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop()
     */

注释大意:调用start()方法将使线程开始执行,JVM会调用该线程的run()方法

结果是两个线程将并发运行:从调用start方法后返回的那个线程以及执行run方法的线程

同一个线程多次启动是不合法的,特别的,一个线程一旦执行完毕,将不会被重新启动

如果一个线程已经启动了,再次调用start()将抛出IllegalThreadStateException

public synchronized void start() {
       if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
              }
        }
    }

private native void start0();

这是一个synchronized方法,且判断了 threadStatus是否等于0,否则抛出异常 

在try里面调用了start0(),这是一个native的方法,前面讲了在static块中会执行registerNatives(),注册本地方法

这个时候JVM会执行真正的"创建线程","启动线程","回调线程run()方法"

关于JVM里发生了什么,这位大师兄讲的很好,戳这里:https://www.cnblogs.com/xiaofuge/p/14040715.html

另外划个重点:一个线程只能启动一次,连续调用两次start(),看看会发生什么!(代码手误造成的一次重大发现~~)

六、线程状态枚举

看看源码注释

    /**
     * A thread state.  A thread can be in one of the following states:
     * {#NEW}:A thread that has not yet started is in this state.
     *    
     * {#RUNNABLE}:A thread executing in the Java virtual machine is in this state.
     *     
     *{#BLOCKED}:A thread that is blocked waiting for a monitor lock is in this state.
     *     
     * {#WAITING}:A thread that is waiting indefinitely for another thread to
     *     perform a particular action is in this state.
     *     
     * {#TIMED_WAITING}:A thread that is waiting for another thread to perform an action
     *     for up to a specified waiting time is in this state.
     *    
     * { #TERMINATED}:A thread that has exited is in this state.
     * 
     * A thread can be in only one state at a given point in time.
     * These states are virtual machine states which do not reflect
     * any operating system thread states.
     */
    public enum State {
        /**
         * a thread which has not yet started.
* 一个尚未启动的线程处于这个状态
*/ NEW, /** * A thread in the runnable state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system such as processor.
* 正在JVM中运行,也可能正在等待操作系统中的其他资源来调度它时处于这个状态
*/ RUNNABLE, /**
* A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling {Object.wait}.
* 当线程被调用了Object.wait()方法后,正在等待获得一个监视器锁来进入或重入一个synchronized方法/块时的状态
*/ BLOCKED, /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * <ul> * <li>{@link Object#wait() Object.wait} with no timeout</li> * <li>{@link #join() Thread.join} with no timeout</li> * <li>{@link LockSupport#park() LockSupport.park}</li> * </ul> * 意思是说:当调用wait(),未设置超时的join(),LockSupport.park()时 * <p>A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called <tt>Object.wait()</tt> * on an object is waiting for another thread to call * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on * that object. A thread that has called <tt>Thread.join()</tt> * is waiting for a specified thread to terminate.
* 例如:线程正在等待其他线程的特定操作,比如当前线程在一个Object a上调用了a.wait(),然后等待其他线程在a上调用Object.notify()或notifyAll()
*/ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: * <ul> * <li>{@link #sleep Thread.sleep}</li> * <li>{@link Object#wait(long) Object.wait} with timeout</li> * <li>{@link #join(long) Thread.join} with timeout</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul>
* 这个和WAITING状态类似,只是增加了等待时长
*/ TIMED_WAITING, /** * The thread has completed execution. */ TERMINATED; }

需要特别注意的是:这个State和前面提到的threadStatus属性是相关联的,在VM.class中是这么对应的

public static State toThreadState(int var0) {
        if ((var0 & 4) != 0) {
            return State.RUNNABLE;
        } else if ((var0 & 1024) != 0) {
            return State.BLOCKED;
        } else if ((var0 & 16) != 0) {
            return State.WAITING;
        } else if ((var0 & 32) != 0) {
            return State.TIMED_WAITING;
        } else if ((var0 & 2) != 0) {
            return State.TERMINATED;
        } else {
            return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
        }
    }

而threadStatus的值的更改,不是在java代码中做到的,而是在JVM中做的

七、其他重要方法

1、sleep(网上有个说法叫"抱锁入睡"很形象)

    /**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds, subject to
     * the precision and accuracy of system timers and schedulers. The thread
     * does not lose ownership of any monitors.
     */
    public static native void sleep(long millis) throws InterruptedException;

sleep时只按指定毫秒时间休眠,不释放锁,时间到后重新排队抢占执行权

2、join

/**
     * Waits at most millis milliseconds for this thread to die. 
* A timeout of
0 means to wait forever. * * This implementation uses a loop of {this.wait} calls conditioned on {this.isAlive}.
* As a thread terminates the {
this.notifyAll} method is invoked.
* It is recommended that applications not use
wait,notify,or notifyAll on Thread instances. */ public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }

翻译下上面注释:等待最多mills毫秒,直到this thread死亡,0意味着一直等待

实现方式是在while(isAlive())条件下循环调用wait()

这个地方说实话,头几次自己琢磨没看懂,有点绕,跟了几次论坛的讨论帖才大体明白:

上伪代码:

//这里用run代替start,为了方便理解
threadA.run(){
   ...;
   //join是synchronized的,这里需要获取threadB的对象锁
   //对象锁之前讲,实际上就像有一个BlockingQueue,尝试获取锁的线程都在里面排队等待
   synchronized threadB.join(){
      ...;
      while(threadB.isAlive()){
//注意,这里是Object.wait()的调用,表示调用这句代码的线程(A)进入等待,而不是B
//threadB只要仍然存活,threadB就调用wait()
//这里插播一个,wait会使得当前线程(就是A)释放锁,就是说threadA会释放开头获取到的threadB的对象锁
//然后threadA进入等待,直到B死亡时调用B.notifyAll,来wakeup threadA
threadB.wait(); } } }

最后这句:当线程terminated时会调用notifyAll(),这个也是在JVM中执行的

也就是,当threadB执行结束,jvm会自动唤醒阻塞在threadB对象上的线程,让他们继续执行

八、遗弃的方法

stop(),destory(),suspend(),resume()

这些方法对正在运行的线程侵入(干扰)过大,容易造成无法预期的后果,抛弃之

最后来个辨析:为什么wait(),notify(),notifyAll()设计成Object类的方法,而不是Thread独有的

几个角度

1、锁是很通用的需求,java提供的锁是对象级的而不是线程级的,

每个对象都可以有锁状态(对象头MarkWord中)因此理应属于对象的方法

2、wait和notify都是对对象的锁进行的操作

3、从观察者和被观察者的角度讲,对象是被观察者,线程是观察者,发生状态变化的一方(对象)理应去

通知(notify,notifyAll)观察的人(线程);否则假设notify由观察者调用,他又怎么知道什么时候调用呢,

如果观察者有一批,又怎么保证同时去notify呢

4、由3考虑到观察者模式,以及一对多的关系中应该是"多"记录"一"的标识,这里因为有个队列的原因,是反的,

对象要记下正在等待获取锁的线程

 九、守护线程和用户线程

public class DemonThreadTest {

    /*public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
                System.out.println("我是用户线程");
            }
        }).start();
        Thread.sleep(3000);
        System.out.println("main线程执行完毕");
    }*/

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                }
                System.out.println("我是用户线程");
            }
        });
        thread.setDaemon(true);
        thread.start();
        Thread.sleep(3000);
        System.out.println("main线程执行完毕");
    }
}

以上两种情况的执行结果:

 

说明:上面第一种创建的线程默认是用户线程,主线程执行完毕后,用户线程还会继续执行,JVM仍然是活跃

下面设置为守护线程后,主线程(main)执行结束后,这个守护线程也随之结束

如果所有的用户线程都结束了,只剩下守护线程,那么JVM也会结束,同时,守护线程也会跟着结束

设置守护线程的方法:在调用start()之前调用 setDemon(true);

守护线程的使用场景:JVM中的GC线程就是守护线程,心跳检测,事件监听的线程,均适合设置为守护线程,为用户线程服务

 

 

 

posted @ 2021-02-23 21:12  鼠标的博客  阅读(74)  评论(0编辑  收藏  举报