欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-11-30 16:21:22
Thread类API中的英文描述: file:///D:/Java/JDK8/Docs/docs/api/index.html 英语不好,大致的意思是: 线程是程序执行时的线程,java虚拟机(JVM)允许一个应用运行多个线程(并发)。 每一个线程都自己的优先权,优先级高的线程会比优先级低的线程优先 ......

Thread类API中的英文描述:

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

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

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

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

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

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

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

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

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

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

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

第一种:声明一个类,该类继承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 }

 

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

第二种:声明一个类,实现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 }

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

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());
    }
}

 

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

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

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

 

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

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();
    }
}

 

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

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();
    }
}

 

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

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有一篇文章对此讲解的比较详细,这里附上链接:

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

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

 

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

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

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

————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 }

 

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

其次,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 }

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

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

未完待续......