入坑两个月,java.lang包下的Thread类

Posted kirito2924

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了入坑两个月,java.lang包下的Thread类相关的知识,希望对你有一定的参考价值。

Thread类API中的英文描述:

file:///D:/Java/JDK8/Docs/docs/api/index.html

技术分享图片

英语不好,大致的意思是:

线程是程序执行时的线程,java虚拟机(JVM)允许一个应用运行多个线程(并发)。

每一个线程都自己的优先权,优先级高的线程会比优先级低的线程优先执行。每一个线程可以被设置成守护线程(daemon),当一段代码运行一些线程时,会创建一个Thread类的对象,这个新的线程初始的优先级是创建这个线程对象时程序员设定好的,仅且仅当用户把这个线程设定成daemon,这个线程才是daemon

当java虚拟机(JVM)开启时,这儿通常有一个非daemon线程(一般的会调用某个类的main方法),java虚拟机(JVM)会一直执行这些线程,直到出现下面几种情况:

技术分享图片

第一种:程序调用Runtime类的退出方法,并且安全管理器允许该退出操作

第二种:除了daemon线程外的其他所有线程执行完毕(处于dead状态);线程run方法中遇到return语句;线程抛出一个run方法无法处理的异常

技术分享图片

有两种方法来创建一个新的线程。

第一种:声明一个类,该类继承Thread,该类应该重写Thread类的run方法,通过调用start方法来启动该线程,下面给出一个列子:

 1 class PrimeThread extends Thread {
 2   long minPrime;
 3   PrimeThread(long minPrime) {
 4     this.minPrime = minPrime;
 5   }
 6 
 7   public void run() {
 8     // compute primes larger than minPrime
 9     . . .
10   }
11 }

 

技术分享图片

第二种:声明一个类,实现Runnable接口,重写Runnable接口中的run方法,下面也给出一个例子:

 1 class PrimeRun implements Runnable {
 2     long minPrime;
 3     PrimeRun(long minPrime) {
 4         this.minPrime = minPrime;
 5     }
 6     public void run() {
 7         // compute primes larger than minPrime
 8         . . .
 9     }
10 }                

下面我们来看一下Thread的源代码部分:

1. Thread类实现了Runnable接口:

public class Thread implements Runnable

2. Thread类的构造器:

1 public Thread()
2 public Thread(Runnable target)
3 Thread(Runnable target, AccessControlContext acc)
4 public Thread(ThreadGroup group, Runnable target)
5 public Thread(String name)
6 public Thread(ThreadGroup group, String name)
7 public Thread(Runnable target, String name)
8 public Thread(ThreadGroup group, Runnable target, String name)
9 public Thread(ThreadGroup group, Runnable target, String name, long stackSize)

Thread类一共有9个构造器。其中第3个构造器没有public修饰,默认用default修饰,同一个包下可用,不多做说明。

通过上面9个构造器(除3)可以看出,用户在创建一个Thread类的对象时,可以设定的参数有:ThreadGroup、Runnable、name、stackSize

ThreadGroup:是java.lang包下的一个ThreadGroup类,ThreadGroup对象表示一个线程的集合,也可以包含另一个ThreadGroup对象。

Thread类init方法关于ThreadGroup部分源码:

 1         Thread parent = currentThread();
 2         SecurityManager security = System.getSecurityManager();
 3         if (g == null) {
 4             /* Determine if it‘s an applet or not */
 5 
 6             /* If there is a security manager, ask the security     
 7             manager what to do. */
 8             if (security != null) {
 9                 g = security.getThreadGroup();
10             }
11 
12             /* If the security doesn‘t have a strong opinion of the 
13             matter use the parent thread group. */
14             if (g == null) {
15                 g = parent.getThreadGroup();
16             }
17         }
18 
19         /* checkAccess regardless of whether or not threadgroup 
20         is explicitly passed in. */
21         g.checkAccess();

其中currentThread()方法是获取当前运行的线程,下面写段代码做个试验:

1 public class Demo7{
2     public static void main(String[] args){
3         Thread t = Thread.currentThread();
4         System.out.println(t.getName());
5     }
6 }

技术分享图片

System.getSecurityManager()是System类中的一个静态方法,该方法是用来返回一个SecurityManager类的对象security,下面是System类中getSecurityManager():

    /**
     * Gets the system security interface.
     *
     * @return  if a security manager has already been established for the
     *          current application, then that security manager is returned;
     *          otherwise, <code>null</code> is returned.
     * @see     #setSecurityManager
     */
    public static SecurityManager getSecurityManager() {
        return security;
    }

注释说明,如果这个安全管理已经创建security对象,则返回这个security,如果没有,则返回null,其中System类的security初始值被设置为null。

    /* The security manager for the system.
     */
    private static volatile SecurityManager security = null;

再看Thread类中init方法关于ThreadGroup部分做了什么处理?

先创建两个对象parent和security:

Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();

接着对用户创建Thread类对象设置的参数做一个判断,如果用户没有设定ThreadGroup参数,则传递一个null值

init方法先判断这个参数是否为null,如果不为null,则判断security是否为空,如果不为空,则获取security所在的ThreadGroup赋值给用户创建的Thread对象,即g的值

接着再判断g是否为空,如果还为null,则将当前线程的ThreadGroup赋值给g。总之,如果用户未设置g值,就把security的g值赋值给g,如果security的g值也为空,就把parent的g值赋给g。

最后再调用g(ThreadGroup)的checkAccess方法,ThreadGroup类的checkAccess方法源码:

1     public final void checkAccess() {
2         SecurityManager security = System.getSecurityManager();
3         if (security != null) {
4             security.checkAccess(this);
5         }
6     }

可以看出该方法,其实是调用了SecurityManager对象的checkAccess(ThreadGroup g)方法:

 1     public void checkAccess(ThreadGroup g) {
 2         if (g == null) {
 3             throw new NullPointerException("thread group can‘t be null");
 4         }
 5         if (g == rootGroup) {
 6             checkPermission(SecurityConstants.MODIFY_THREADGROUP_PERMISSION);
 7         } else {
 8             // just return
 9         }
10     }

其中rootGroup应该是g的最高级parent(未必正确),看一下源码:

1 private static ThreadGroup rootGroup = getRootGroup();
2 private static ThreadGroup getRootGroup() {
3         ThreadGroup root =  Thread.currentThread().getThreadGroup();
4         while (root.getParent() != null) {
5             root = root.getParent();
6         }
7         return root;
8 }

checkPermission(SecurityConstants.MODIFY_THREADGROUP_PERMISSION)这个坑之后再填吧,挖不下去了。

Runnable:前面介绍了创建Thread类对象的两种方法,其中一种就是传入一个Runnable对象。

如果在创建一个线程时,没有传入Runnable对象,则init方法默认传入的是一个null值,来看一下源码:

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

 

在看看init方法对应参数代表什么意思:

private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
  init(g, target, name, stackSize, null, true);
}

 

从中还可以看出,如果在创建Thread类对象时,没有指定其姓名,会默认设置名字,即Thread-加上nextThreadNum()返回值:

private static int threadInitNumber;
private static synchronized int nextThreadNum() {
    return threadInitNumber++;
}

 

所以第一个线程的名字会默认为Thread-0,第二个线程的名字为Thread-1......

stackSize:栈的大小,待补充......

3. 线程的状态

Thread类有个内部枚举类State,该类就声明了线程的几种状态:

public enum State {
    NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;
}

 

NEW:线程刚创建时具有的状态;

public class Demo8{
    public static void main(String[] args){
        Thread t = new Thread();
        System.out.println(t.getState());
    }
}

 

技术分享图片

RUNNABLE:是指线程正在运行,线程获取CPU的时间片

public class Demo8{
    public static void main(String[] args){
        Thread t = new Thread();
        t.start();
        System.out.println(t.getState());
    }
}

 

技术分享图片

BLOCKED:是指程序进入阻塞状态,假如有两个线程,并且两个线程都是同步安全的(synchronized),当一个线程处于runnable状态时,则另一个线程处于blocked状态。

public class Demo8{
    public static void main(String[] args){
        Thread t1= new Thread(){
            public void run(){
                synchronized(Thread.class){
                    for(int i = 0; i < 100; i++){
    
                    }
                }
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                synchronized(Thread.class){
                    System.out.println(t1.getState());
                }
            }
        };
        t1.setPriority(1);
        t2.setPriority(10);
        t1.start();
        t2.start();
    }
}

 

技术分享图片

WAITING:程序处于等待状态,调用wait()、join()、await()、lock()等方法,都会使线程处于waiting状态,需要注意的是这些方法必须是无参数的。

public class Demo8{
    public static void main(String[] args){
        Thread t1 = new Thread(){
            public void run(){
                try{
                    join();    
                }
                catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                System.out.println(t1.getState());
            }
        };
        t1.start();
        t2.start();
    }
}

 

技术分享图片

TIMED_WAITING:程序处于限时等待状态,调用wait()、join()、await()、lock()、sleep()等方法,都会使线程处于waiting状态,需要注意的是这些方法必须加入参数。

TERMINATED:终止状态,即线程结束

4. Thread类中部分方法源码解析

————this.start()方法

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) {
        }
    }
}

 

可以看出线程是在调用start方法时加入init方法中指定的线程池的,其次线程在创建时并不是把内部的枚举类State的NEW值给这个线程,而是定义一个int型的threadStatus

变量,并且这个变量初始值为0。

private volatile int threadStatus = 0;

 

并且start还调用了一个本地的start0()方法,由于没看过JVM相关的知识,所以对于native修饰的方法无能无力:

private native void start0();

 

不过IBM有一篇文章对此讲解的比较详细,这里附上链接:https://www.ibm.com/developerworks/cn/java/j-lo-processthread/#icomments

大概的意思是java的start方法会调用 JVM_StartThread方法,而 JVM_StartThread方法会创建一个与本地相关的线程,该线程与java创建的线程有着一一对应关系。

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) 
   …
    native_thread = new JavaThread(&thread_entry, sz); 
   …

 

这篇文章并没有详细讲解JVM的代码实现过程,而是给出了方法调用图,该图来自上面链接的那篇文章:

技术分享图片

技术分享图片

————setPriority(int)方法

这里先留坑,简单讲一下,线程的优先级可以理解为线程抢占cpu时间片的概率,因此并不能保证优先级高一定会先执行

 1 public class Demo2 {
 2     public static void main(String[] args) {
 3         Thread t1 = new Thread() {
 4             @Override
 5             public void run() {
 6                 for(int i = 0; i < 1000; i++) {
 7                     System.out.println("********");
 8                 }
 9             }
10         };
11         Thread t2 = new Thread() {
12             @Override
13             public void run() {
14                 for(int i = 0; i < 1000; i++) {
15                     System.out.println("--------");
16                 }
17             }
18         };
19         t1.setPriority(1);
20         t2.setPriority(10);
21         t1.start();
22         t2.start();
23     }
24 }

 

技术分享图片

其次,windows操作系统的优先级为7个级别(未验证),而java的优先级分为10个级别,所以java程序1~10几个优先级中,必定有几个优先级在windows操作系统下级别是一样的,真正决定优先级的应该是本地的setPriority0()方法。

setPriority0(priority = newPriority);

 

—————activeCount方法,获取当前线程池中的线程数量

 1 import java.lang.reflect.InvocationTargetException;
 2 import java.lang.reflect.Method;
 3 public class Demo3 {
 4     public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
 5         MyRunnable m = new MyRunnable();
 6         Thread t1 = Thread.currentThread();
 7         ThreadGroup tg = t1.getThreadGroup();
 8         Thread t2 = new Thread(m);
 9         Class c = ThreadGroup.class;
10         Method declaredMethod = c.getDeclaredMethod("add", Thread.class);
11         Method method = declaredMethod;
12         method.setAccessible(true);
13         method.invoke(tg,t2);
14         System.out.println(Thread.activeCount());
15         System.out.println(t1.getName());
16         System.out.println(tg.getName());
17     }
18 }
19 class MyRunnable implements Runnable{
20     public void run() {}
21 }

技术分享图片

这里是利用反射的机制,调用ThreadGroup类中的add(Thread t)方法,把线程添加到指定的线程池,这里要说明的是,创建的线程对象时,Thread类构造器调用的init初始化方法,并没有把线程加入指定的线程池,而是在start方法中调用了ThreadGroup的add方法。

未完待续......

 

以上是关于入坑两个月,java.lang包下的Thread类的主要内容,如果未能解决你的问题,请参考以下文章

49多线程创建的三种方式之继承Thread类

JDK源码阅读之java.lang.Object(第二章)

java中多线程的Thread类,怎么没有导入任何包就用呢?

Java进阶 - 特殊对象(Class类)

Java常用包都有哪些?

java三个类组合怎么运行