线程的启动
1. 线程与进程
进程:进程就是在系统中允许的一个个程序,例如QQ.exe 这种就是一个进程。进程是资源拥有的单位。
线程:线程是进程中的一个执行流程,就像你使用QQ音乐一边听歌一边浏览歌单一样。线程是CPU进行调度的单位。
1.1 为什么会有线程?
在系统运行时,CPU会根据时间片来运行各种程序,譬如每个程序执行0.1ms,然后切换到下一个调度单位执行。在最开始的时候是只有进程,CPU调度的单位也是进程,但是发现在CPU时间片切换导致进程的上下文切换时,需要将前一个进程的虚拟内存、栈、全局变量等用户空间的资源和内核堆栈、寄存器等内核空间的资源保存下来,然后将下一个要执行的进程的各种信息再赋值过去开始执行,开销比较大。所以才有了线程这个更小的且能独立运行的基本单位。
1.2 线程的优缺点
线程的优点:
一个进程可以拥有多个线程
各个线程之间可以并发执行
各个线程之间可以共享地址空间和文件等资源
线程的缺点
当进程中的一个线程崩溃时,会影响到其所属进程的其他线程
1.3 线程与进程的比较
线程和进程的比较:
进程是资源分配的单位,线程是CPU调度的单位
进程拥有一个完整的资源平台,而线程值独享必不可少的资源,比如寄存器和栈
线程和进程都有就绪,阻塞,执行三种基本状态,也具有状态之间的转换关系
线程能减少并发执行的时间和空间开销
1.4 线程为什么会减少开销
线程的创建时间比进程快,因为在进程在创建的过程中,还需要分配资源,并保存资源管理信息, 比如内存管理信息,文件管理信息,而线程在创建过程中,不会涉及到这些资源管理信息,而是共享他 们
线程的终止时间比进程快,因为线程释放的资源比进程少很多,只释放他自己独享的资源
同一个进程内的线程切换比进程块。
线程的进行上下文切换时,如果是在同一个进程中,只需要切换线程的私有数据、寄存器等不共享的数据;如果不在同一个进程,则切换过程和进程的上下文切换一样。
2. 查看Java中的线程
使用ThreadMXBean
可以看到当前正在执行的线程,不同版本的JDK可能结果不一样
package com.cxyxh.self.thread;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
public class L01_LookThread {
public static void main(String[] args) {
//[6]Monitor Ctrl-Break
//[5]Attach Listener
//[4]Signal Dispatcher 分发处理发送给JVM信号的线程
//[3]Finalizer 调用对象finalizer()方法的线程
//[2]Reference Handler 清除reference的线程
//[1]main 主线程
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName());
}
}
}
执行结果:
我们可以看到在main()
方法执行的时候一共起了6个线程,其中Main
线程是我们main()
方法的这个线程,而其他的有执行垃圾回收的线程Finalizer
和Reference Handler
。
3. 线程的启动
在Java中我们可以自己创建线程并执行,创建线程的方式有两种,一种是继承Thread
类,另一种是实现Runnable
接口。但是最终的担子都交到了Thread
类上了。
package com.cxyxh.self.thread;
public class L02_newThread {
public static void main(String[] args) {
Thread thread1 = new Thread(new OneThread());
Thread thread2 = new Thread(new TwoThread());
//线程2还可以这样创建:TwoThread twoThread = new TwoThread();
thread1.start();
thread2.start();
}
/**
* 实现Runnable接口并时间run()方法
*/
static class OneThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
/**
* 继承Thread类并重写Run方法
*/
static class TwoThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
}
还可以在构造方法中指定线程的名字:
Thread thread1 = new Thread(new OneThread(), "线程1");
Thread thread2 = new Thread(new TwoThread(), "线程2");
还有其他同理但是写法不一样的:
匿名内部类写法:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}, "线程3");
thread.start();
//匿名内部类的lambda表达式写法
Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()), "线程3");
4. run()方法和start()方法
在创建出线程之后调用run()
方法的话,就只是相当于掉了一下普通的方法一样直接就执行了,并没有重新启动一个线程。而调用start()
方法,会将线程的状态修改为执行中,然后会交给CPU进行调度执行,而不是直接执行。
package com.cxyxh.self.thread;
public class L02_newThread {
public static void main(String[] args) {
Thread thread1 = new Thread(new OneThread(), "线程1");
//分别执行start()方法和run()方法
//thread1.start();
thread1.run();
}
static class OneThread implements Runnable {
@Override
public void run() {
//输出当前线程的名字
System.out.println(Thread.currentThread().getName());
}
}
}
执行结果:
run()方法的执行结果:
我们可以很清晰的看到执行run()
方法时,实际上还是main
线程而不是重新新建了另一个线程。
start()
方法的执行结果:
可以看到调用start()
方法之后是是重新启动了一个线程
5. 连续调用两次start
现在我们知道线程的创建就是新建一个Thread对象然后调用他的start()
方法就行。那么我们连续调用两次start()
方法会发生什么呢?
package com.cxyxh.self.thread;
public class L02_newThread {
public static void main(String[] args) {
Thread thread1 = new Thread(new OneThread(), "线程1");
thread1.start();
thread1.start();
}
static class OneThread implements Runnable {
@Override
public void run() {
//输出当前线程的名字
System.out.println(Thread.currentThread().getName());
}
}
}
执行结果:
我们可以看到两个start()
方法,其中一个成功了,另一个在执行时抛出了IllegalThreadStateException
异常,表示异常的线程状态
我们可以看一下start()
方法的源码
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>
* 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.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @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".
*/
/**
* 这里判断了线程的状态如果是0的话抛出异常
* 如果当前下次的状态不是"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 */
}
}
}
在start()
方法的文档注释上明确的写着将一个线程启动不止一次是不合法的。特别是,一个线程在完成执行后可能不会重新启动。
在start()开始执行之后,会判断当前线程的状态是否为刚刚创建,如果不是的话就抛出异常,如果是刚刚创建的,那么会调用native方法start0()
。
参考资料