Java多线程系列之一Thread类的学习

       首先学习线程之前要弄懂几个基础概念,以及博主的另外一篇博客 https://blog.csdn.net/xzjayx/article/details/95061408

CPU的多级缓存这块,如果现在不是很理解也没关系,学完线程之后在回过头

一 基础概念

     1 CPU核心数 与 线程数的关系

             一般来说一个核心数对应一个线程 (1:1),在inter引入了超线 程技术之后 ,核心数和对应的线程数变成了 1:2 (比如常说的四核八线程)

    2 线程和进程的关系

              进程:进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例,程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

              线程: 线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的栈(但是堆和方法区线程共享),线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。

    3 CPU时间片轮转机制

        当进程分配了资源之后,就会把进程放入一个就绪队列,新来的进程就会加到就绪队列的末尾,当执行进程调度的时(从就绪队列首端选中一个进程),让CPU分配一个运行的时间片,时间片是一个小的时间单位,通常为10-100ms,当该进程用完分配给它的时间片后,系统的计时器发出时钟中断,调度程序便停止该进程的运行,把它放入就绪队列的末尾;然后,把CPU分给就绪队列的队首进程,同样也让它运行一个时间片,如此往复。(如果在该进程用完CPU分配的时间片时,还没有执行完的话,此时会做一个标志,并且释放出处理机给下一个就绪的进程,然后该进程重新加入就绪队列等待下一次分配,假使等待完毕,又拿到相应的时间片,该进程首先会进行上下文切换,上下文切换也需要消耗时间片,切换到上次运行的标志接着运行下去 )

     4 并行和并发

并行:在单核的CPU上,一个核只能在特定的时间点上处理一个线程,但是多核的CPU就使得在特定的时间点上处理多个线程。那么问题来了,4核同时只能运行4个程序,而且只能是单线程?显然不对。你可以打开你电脑的资源管理器,可以看到很多进程,而且每个进程大多是多线程的。那这是为什么呢?那得先理解一个词“并发”。

并发: 是指在某个微小的时间段内,处理多个事情。具体意思就是,单核CPU在一个时间段内,先处理完了A线程,马上切换到B线程并且处理它,处理完B线程马上切换到C线程并且处理它…由于切换时间很快,所以基本上可以看做是同时处理,就像是光,当他频率到达一定的高度时,你肉眼根本分不清楚它快速的闪烁。

二 Java的程序天生都是多线程的,最普通的main()有多少线程

   public static void main(String[] args){
    //返回Java虚拟机系统管理的线程bean
    ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(true, true);
    for (ThreadInfo threadInfo : threadInfos) {
        System.out.println("id="+threadInfo.getThreadId()+"---------name="+threadInfo.getThreadName());
    }
}

    下面用jconsole看一下  

相关的线程解释下:

  • Monitor Ctrl-Break:这是IDEA特有的监控线程
  • Attach Listener:该线程是负责接收到外部的命令,执行该命令,并且把结果返回给发送者。通常我们会用一些命令去要求jvm给我们一些反馈信息,如:java -version、jmap、jstack等等。如果该线程在jvm启动的时候没有初始化,那么则会在用户第一次执行jvm命令时得到启动。
  • Signal Dispatcher:前面我们提到第一个Attach Listener线程的职责是接收外部jvm命令,当命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。signal dispather线程也是在第一次接收外部jvm命令时,进行初始化工作。
  • Finalizer: JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收。
  • Reference Handler:主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。
  • main:主线程,用户程序入口

三 Java的常见创建线程的3种方式

        1 extends Thread            2 implements Runnable            3 implements Callable<V> (可以拿到返回值)

    

四 Java创建线程的源码分析

        这才是本文中的重点,会给大家说出一些可能没有关注的过的细节问题,首先分析Thread类的构造方法

  Thread()里面调用  init()方法,看一下init方法()

 public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
}
/**
     * Initializes a Thread with the current AccessControlContext.
     * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext,boolean)
*/
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
}
 /**
     * Initializes a Thread.
     *
     * @param g the Thread group //线程组后面几篇介绍
     * @param target the object whose run() method gets called 
     * @param name the name of the new Thread //线程组名字
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     * @param acc the AccessControlContext to inherit, or
     *            AccessController.getContext() if null
     * @param inheritThreadLocals if {@code true}, inherit initial values for
     *            inheritable thread-locals from the constructing thread
*/
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */
            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }
            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();
        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        g.addUnstarted();
        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;
        /* Set thread ID */
        tid = nextThreadID();
    }

  1 首先关注的第一点 线程的名字,由于线程在创建的时候会初始化 init,而名字如果不写的话默认调用的是nextThreadNum()方法 ,会有一个static的成员属性,初始化会赋值0  则如果不赋值name,那么Thread会给一个初始名字 Thread-0 。

 /* For autonumbering anonymous threads. */
 private static int threadInitNumber;
 private static synchronized int nextThreadNum() {
        return threadInitNumber++;
 }

  2  关注一下init()具体方法   this.daemon = parent.isDaemon();  this.priority = parent.getPriority(); 子线程是否是守护线程取决于父线程的守护线程,那怎么知道父线程是否是守护线程,一般来说有一个 daemon的属性 ,默认为false 。如果还不确定可以用 isDaemon() 来判断,那么如何设置一个线程成守护线程呢? 这个isDaemon()方法会参考 设置setDaemon(boolean)

/**
     * Tests if this thread is a daemon thread.
     *
     * @return  <code>true</code> if this thread is a daemon thread;
     *          <code>false</code> otherwise.
     * @see     #setDaemon(boolean)
 */
public final boolean isDaemon() {
        return daemon;
}
/**
     设置这个Thread线程为守护线程,当所有的运行线程都是守护线程时候JVM会退出
     * Marks this thread as either a {@linkplain #isDaemon daemon} thread
     * or a user thread. The Java Virtual Machine exits when the only
     * threads running are all daemon threads.
     *
        这个方法必须在调用started()方法之前调用才生效
     * <p> This method must be invoked before the thread is started.
     *
     * @param  on
     *         if {@code true}, marks this thread as a daemon thread
     *
     * @throws  IllegalThreadStateException
     *          if this thread is {@linkplain #isAlive alive}
     *
     * @throws  SecurityException
     *          if {@link #checkAccess} determines that the current
     *          thread cannot modify this thread
*/
public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
}

  3 线程的优先级问题,线程的优先级在代码中会有这样一行  this.priority = parent.getPriority();setPriority(priority); 可知线程的优先级其实是拿父线程的。线程的默认优先级是5 这个优先级。(线程的优先级不完全保证真实有用,这和操作系统相关)setPriority()方法里面可知,线程优先级1-10之间,不然就会抛出异常 IllegalArgumentException

  4 至于上面有一些线程组 还有  checkAccess();  SecurityManager 都会在后面博客中再来介绍 ,最后看一下start()方法

 

/**
     使这个线程开始执行,这个JVM就会调用run()方法来具体执行,其实Thread只是一个普通的类
     当调用start()方法时候才会执行一个线程。
     * Causes this thread to begin execution; the Java Virtual Machine
     * calls the <code>run</code> method of this thread.
     * <p>
     这个返回时两个正在运行的线程,当前线程(哪一个线程调用start()方法的线程)
     另外一个线程(执行run()的线程)
     * 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.
     特别的是,一个线程一旦完成执行了,就不会被重新调用 (这里指的是同一运行应用,比如main()中启动了一次statr ,在启动就会报下方异常,但是防止有些人说重启main()不就好了)
     * In particular, a thread may not be restarted once it has completed
     * execution.
     
     如果这个线程已经启动再次调用start()方法就会抛出异常
     * @exception  IllegalThreadStateException  if the thread was already
     *               started.
     * @see        #run()
     * @see        #stop() 该方法已经过期,不建议使用,博主会在后面博客中介绍如何停止一个线程
*/
public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值