04 Thread的方法(源代码) 和 线程的状态
1 .Thread中重要的属性
1 publicclass Thread implements Runnable { //继承自Runnable接口private char name[]; // 以char数组保存线程的名字 2 private int priority; // 线程优先级别 3 /* Whether or not the thread is a daemon thread. */ 4 private boolean daemon = false; //是否为守护线程 5 /* What will be run. */ 6 private Runnable target; //构造方法中传递一个Runnable对象 最终由target指向 7 8 /* The group of this thread */ 9 private ThreadGroup group; //线程组 10 11 //预先定义好的优先级 12 public final static int MIN_PRIORITY = 1; 13 public final static int NORM_PRIORITY = 5; 14 public final static int MAX_PRIORITY = 10; 15 16 // 这个类是在ThreadLocal中定义 类似于Map的key-value的数据结构(后面会有该类的叙述) 17 // 特殊之处:key的值是固定的 就是当前线程 18 ThreadLocal.ThreadLocalMap threadLocals = null; 19 20 // 当不为线程命名的时候 默认名称是Thread-编号 编号从0开始增长 就是依靠这个 前面已经详细讲述 21 private static int threadInitNumber; 22 private static synchronized int nextThreadNum() { 23 return threadInitNumber++; 24 } 27 ... ... 26 }
2 构造方法
在Thread重载了很多构造方法 我们挑选几个常用的进行列举
1 public Thread() { 2 init(null, null, "Thread-" + nextThreadNum(), 0); 3 } 4 5 public Thread(String name) { 6 init(null, null, name, 0); 7 } 8 9 public Thread(Runnable target) { 10 init(null, target, "Thread-" + nextThreadNum(), 0); 11 } 12 13 public Thread(Runnable target, String name) { 14 init(null, target, name, 0); 15 }
可以看出Thread的构造方法最终都会调用init方法
1 2 //4个参数分别表示 线程所属组 Runnable对象 线程名字 线程栈大小 3 //一般只会用到第2、3个参数 4 private void init(ThreadGroup g, Runnable target, String name, long stackSize) { 5 6 Thread parent = currentThread(); //获取当前运行的线程为父线程 一些属性将会基础自parent 7 SecurityManager security = System.getSecurityManager(); 8 if (g == null) { 9 if (security != null) { 10 g = security.getThreadGroup(); 11 } 12 if (g == null) { 13 g = parent.getThreadGroup(); 14 } 15 } 16 g.checkAccess(); 17 18 if (security != null) { 19 if (isCCLOverridden(getClass())) { 20 security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); 21 } 22 } 23 g.addUnstarted(); 24 25 26 this.group = g; //指定要开启线程的组 27 this.daemon = parent.isDaemon();//指定要开启线程是否为守护线程 来自于parent 28 this.priority = parent.getPriority();//设置优先级的值 来自于parent 29 this.name = name.toCharArray();//设置线程名字 30 if (security == null || isCCLOverridden(parent.getClass())) 31 this.contextClassLoader = parent.getContextClassLoader(); 32 else 33 this.contextClassLoader = parent.contextClassLoader; 34 this.inheritedAccessControlContext = AccessController.getContext(); 35 this.target = target;//设置要执行的目标 Runnable对象 36 setPriority(priority);//设置优先级 37 if (parent.inheritableThreadLocals != null) 38 this.inheritableThreadLocals = 39 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); 40 /* Stash the specified stack size in case the VM cares */ 41 this.stackSize = stackSize; 42 43 /* Set thread ID */ 44 tid = nextThreadID(); 45 46 this.me = this; 47 }
3 线程的状态
1 /* Java thread status for tools, 2 * initialized to indicate thread 'not yet started' 3 */ 4 private int threadStatus = 0; //描述线程状态的属性
线程有四种状态
1. 新状态:线程已被创建但尚未执行(start() 尚未被调用)。
2. 可执行状态:线程可以执行,虽然不一定正在执行。CPU 时间随时可能被分配给该线程,从而使得它执行。
3. 死亡状态:正常情况下 run() 返回使得线程死亡。调用 stop()或 destroy() 亦有同样效果,但是不被推荐,前者会产生异常,后者是强制终止,不会释放锁。
4. 阻塞状态:线程不会被分配 CPU 时间,无法执行。
这些状态 Thread中是以枚举来描述的:
1 public enum State { 2 NEW, 3 RUNNABLE, 4 BLOCKED, 5 WAITING, 6 TIMED_WAITING, 7 TERMINATED; 8 }
Thread中的不同方法的执行 会使线程进入不同的状态 如图:
将blocked、waiting、time waiting统称为阻塞状态,这个也是可以的
只不过这里我想将线程的状态和Java中的方法调用联系起来,所以将waiting和time waiting两个状态分离出来。
4 Thread中的成员方法
1)start方法
start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
一个线程只能start 1次 以为一个只需要分配一次资源就够了 如果启动多次 就会出错:非法的线程状态异常 IllegalThreadStateException
1 public synchronized void start() { 2 if (threadStatus != 0 || this != me) //只有处于未开启状态的线程才可以继续执行 3 throw new IllegalThreadStateException(); 4 group.add(this); 5 start0(); 6 if (stopBeforeStart) { 7 stop0(throwableFromStop); 8 } 9 } 10 11 private native void start0(); 12 private native void stop0(Object o);
2)run方法
run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。
注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
如果我们覆盖了run方法 就会执行我们覆盖的方法
如果我们向Thread传递了一个Runnable对象 就会执行该对象的run方法
1 public void run() { 2 if (target != null) { //判断是否有Runnable对象传入 3 target.run(); 4 } 5 }
那么请思考下面程序的输出:
1 public static void main(String[] args) { 2 new Thread(new Runnable() { 3 4 @Override 5 public void run() { 6 System.out.println("run in Runnable"); 7 } 8 9 }) { 10 public void run() { 11 System.out.println("run in Thread"); 12 } 13 }.start(); 14 }
答案:run in Thread
这就表明既复写run方法又传递Runnable时执行的是覆盖的方法
因为执行原有的run方法已经失效 根本不会判断if (target != null) 更不会执行target.run();
3)sleep方法
sleep方法有两个重载版本:
sleep(long millis) //参数为毫秒
sleep(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。
当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。
4)yield方法
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。
但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
5)join方法join方法有三个重载版本:
join()
join(long millis) //参数为毫秒
join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
假如在main线程中,调用thread.join方法,则main方法会等待thread线程执行完毕或者等待一定的时间。
如果调用的是无参join方法,则等待thread执行完毕,如果调用的是指定了时间参数的join方法,则等待一定的时间。
6)interrupt方法
interrupt,顾名思义,即中断的意思。单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,
也就说,它可以用来中断一个正处于阻塞状态的线程直接调用interrupt方法不能中断正在运行中的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。
但是一般情况下不建议通过这种方式来中断线程,一般会在MyThread类中增加一个属性 isStop来标志是否结束while循环,然后再在while循环中判断isStop的值。
那么就可以在外面通过调用setStop方法来终止while循环。
1 class MyThread extends Thread{ 2 3 private volatile boolean isStop = false; 4 5 public void run() { 6 int i = 0; 7 while(!isStop){ 8 i++; 9 } 10 } 11 12 public void setStop(boolean stop){ 13 this.isStop = stop; 14 15 } 16 }
7)stop方法
stop方法已经是一个废弃的方法,它是一个不安全的方法。
因为调用stop方法会直接终止run方法的调用,并且会抛出一个ThreadDeath错误,如果线程持有某个对象锁的话,会完全释放锁,导致对象状态不一致。所以stop方法基本是不会被用到的。
8)destroy方法destroy方法也是废弃的方法。基本不会被使用到。
9)以下是关系到线程属性的几个方法:
1)getId 用来得到线程ID
2)getName和setName 用来得到或者设置线程名称。
3)getPriority和setPriority 用来获取和设置线程优先级。
4)setDaemon和isDaemon 用来设置线程是否成为守护线程和判断线程是否是守护线程。
守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。
举个简单的例子:
如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。
而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
(10)Thread类有一个比较常用的静态方法currentThread()用来获取当前线程。
(11)holdsLock 判断线程是否有锁 Returns true if and only if the current thread holds the monitor lock on the specified object.
参考:
http://www.cnblogs.com/dolphin0520/p/3920357.html