首先学习线程之前要弄懂几个基础概念,以及博主的另外一篇博客 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 */
}
}
}