Java Thread解析

Java多线程基础知识

Thread初体验:
public class Thread extends Object implements Runnable

创建第一个线程:

文档内容:When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main of some designated class)
翻译:当Java虚拟机启动时,通常有一个非守护进程线程(它通常调用某个指定类的main方法)。

验证:

public class FirstThread {
    public static void main(String[] args) {
        try {
            Thread.sleep(1000*100L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

打开cmd,使用jps查看运行的java进程pid,使用jconsole连接这个进程pid
在这里插入图片描述

从上图可以看到JVM启动时有已有多个线程存在

创建线程并启动线程:

public class UseThread {
	public static void main(String[] args) {
        Thread thread = new Thread(UseThread::print,"Hello-Thread");
        thread.start();
    }
    private static void print() {
        System.out.println("currentThreadName:" + Thread.currentThread().getName());
    }
}
  1. start()方法:
    源码注释翻译:

    1. 使该线程开始执行;Java虚拟机调用这个线程的{@code run}方法。
    2. 结果是两个线程并发运行:当前线程(它从对{@code start}方法的调用返回)和另一个线程(它执行它的{@code run}方法)。
    3. 多次启动一个线程是不合法的。特别是,线程在完成执行后可能不会重新启动。
    4. 主方法线程或“系统”不调用此方法,将VM创建/设置的线程分组。将来添加到此方法中的任何新功能可能也必须添加到VM中。零状态值对应于状态“NEW”。
    

    执行流程:

  2. run()方法:

    public void run() {
        if (target != null) {target.run();}
    }
    

    源码注释翻译:

    1. 如果这个线程是使用一个单独的{@code Runnable} run对象构造的,那么这个{@code Runnable}对象的{@code run}方法就会被调用;否则,此方法不执行任何操作并返回。
    2. {@code Thread}的子类应该覆盖此方法。
    
Thread的实例化:
  1. private Thread(ThreadGroup g, Runnable target, String name, long stackSize,AccessControlContext acc,boolean inheritThreadLocals)
参数 说明
ThreadGroup g 线程组
Runnable target 调用其run()方法的对象
String name 新线程的名称
long stackSize 新线程所需的堆栈大小,零表示忽略此参数。
AccessControlContext acc 要继承的AccessControlContext,如果为空,则为AccessController.getContext()
boolean inheritThreadLocals 如果{@code true},则从构造线程继承可继承的线程局部变量的初始值
  1. String name分析

    public Thread() {
        this(null, null, "Thread-" + nextThreadNum(), 0);
    }
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }
    
    由上可知Thread会提供默认的线程名:前缀:Thread- ,后缀:从0开始,每创建一个threadInitNumber数值就会加一
    
  2. Runnable target分析

    public Thread(Runnable target) {
        this(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    在0中有this.target = target;而通过run()方法我们知道如果target为空就什么也不做
    
  3. ThreadGroup group分析

    在0中有if (g == null) { g = parent.getThreadGroup();}那他的parent是什么
    
    //0中
    Thread parent = currentThread();
    //返回当前执行的线程对象的引用。
    @HotSpotIntrinsicCandidate
    public static native Thread currentThread();
    
    问:谁是当前执行的线程?答:创建此线程的线程。(还是调用此线程start()方法的线程?)
    

    验证:

    Thread thread1 = new Thread();
    Thread thread2 = new Thread(thread1::start);
    thread2.start();
    System.out.println(thread1.getThreadGroup());
    System.out.println(thread2.getThreadGroup());
    System.out.println(Thread.currentThread().getThreadGroup());
    

    输出:

    java.lang.ThreadGroup[name=main,maxpri=10]
    java.lang.ThreadGroup[name=main,maxpri=10]
    java.lang.ThreadGroup[name=main,maxpri=10]
    

    总结:如果构造线程时没有指定ThreadGroup,那么子线程会默认使用父线程的ThreadGroup,此时子线程和父线程在同一个ThreadGroup中

    问:下面ThreadGroup中有多少个线程?猜测:两个
    
    Thread thread1 = new Thread();
    thread1.start();
    Thread[] threads = new Thread[thread1.getThreadGroup().activeCount()];
    thread1.getThreadGroup().enumerate(threads);
    Arrays.asList(threads).forEach(System.out::println);
    

    输出:

    Thread[main,5,main]
    Thread[Monitor Ctrl-Break,5,main]
    Thread[Thread-0,5,main]
    

    实际上会有三个,多了个Thread[Monitor Ctrl-Break,5,main]

  4. long stackSize分析
    jdk文档:

    stackSize:指定线程堆栈大小。堆栈大小是虚拟机要为此线程的堆栈分配的地址空间的大概字节数。 
    参数的作用(如果有stackSize)在很大程度上取决于平台。在某些平台上,stackSize参数的值 可能没有任何作用。
    

    验证:

    private static int count;
    public static void main(String[] args) {
    	//运行时每次调用一个,否则值被覆盖
        testThreadStackSize("Thread1",0);
        //testThreadStackSize("Thread2",1024*1024);
    }
    public static void testThreadStackSize(String name,long stackSize) {
        new Thread(null,()-> {
            try {
                add(0);
            }catch (Error e) {
                System.out.println(Thread.currentThread().getName() + " StackDepth" + count);
            }
        },name,stackSize).start();
    }
    private static void add(int i) {
        ++ count;
        add(i+1);
    }
    

    输出:

    Thread1 StackDepth:22564
    Thread2 StackDepth:40182
    
  5. AccessControlContext acc 分析

  6. boolean inheritThreadLocals分析

Thread的部分参数:
  1. **private boolean daemon = false;**该线程是否为守护进程线程。
    守护线程:Java中线程分为两类:用户线程和守护线程,守护线程的作用就是守护用户线程,如果没有可守护的人(线程)了,那他的存在就没了意义,他会自己结束生命(线程结束,无论他自己在干什么),JVM退出,程序结束。

     在0中有this.daemon = parent.isDaemon();所以如果新线程没有设置daemon ,那他默认的继承父线程的daemon。
    

    方法:public final void setDaemon(boolean on)

     将此线程标记为{@linkplain #isDaemon daemon}线程或用户线程。当惟一运行的线程都是守护进程线程时,Java虚拟机退出。
     必须在线程启动之前调用此方法。
    
  2. **private int priority;**线程的优先级,可以设置线程的优先级,但不一定管用

  3. **private final long tid;**线程的ID

    //在0中
    this.tid = nextThreadID();
    //用于生成线程ID
    private static long threadSeqNumber;
    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }
    
     可见线程的id值为程序启动后依次启动的线程数量
    
发布了12 篇原创文章 · 获赞 3 · 访问量 2321

猜你喜欢

转载自blog.csdn.net/lhg1714538808/article/details/105059608