并发学习第二篇——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线程就是守护线程,心跳检测,事件监听的线程,均适合设置为守护线程,为用户线程服务