我相信大多数人都多多线程有所了解,而我们的Java程序天生就是多线程的程序?为什么这么说呢?下面我就来证明一下为什么Java程序天生就是多线程的程序。
public static void main(String[] args) {
//这个ThreadMxBean就是为了获取我们当前程序(你也可以说是进程)当前运行过程中启动的执行的线程数
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
//获取到一个描述线程信息的数组
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
//打印线程的名称
for(ThreadInfo info : threadInfos){
System.out.println(info.getThreadName());
}
}
运行结果如下图所示:
通过代码可以看出,我们呢的main函数中并没有显式的开启任何子线程,那么为什么会出现6个线程呢?(这里的6个只是特定条件下的产物,可能会因main函数中代码的不同导致不同的结果,譬如你在main函数中创建了一个大数组,并且在其作用域外调用了System.gc()方法,可能就会看到我们的垃圾回收线程)。
这6个中有五个是我们的守护线程,gc线程也是我们的守护线程。这几个可以算是我们最常见的守护线程了吧。
下面,我简单给大家解释一下这几个线程的左右,以后如果有人问你什么是守护线程?为什么Java程序天生就是多线程的?...等等类似问题的时候,你一定能回答的非常漂亮!
1.gc(garbage collection):熟悉C/C++语言得朋友都知道malloc,calloc,realloc等内存分配函数,和free()内存空间释放函数,这些函数都是要成需要手动的调用的,如果忘记调用free()函数释放内存空间,有可能就会导致内存溢出。而Java中就没有类似的函数,程序员不需要关心内存的分配与回收,这就得益于我们的动态分配和垃圾收集技术,而垃圾收集就是我们的gc完成的,自动的将无法被引用到的对象(僵尸对象)所占用的内存释放掉。(我举上面这个例子是为了说明什么是gc和gc为什么会被设计出来,并不是说C/C++不如Java高级,两者各有千秋,在深入理解Java虚拟机中有这么一段话“Java于C++之间有一堵由动态分配和垃圾回收技术所围成的高墙,里面的人想出来,外面的人想进去”)。
2.Finalizer:在gc进行垃圾回收的时候,会首先枚举根节点,然后通过可达性分析来标记对象是否还会被引用到,如果可达性分析后,对象未被标记,说明对象已经成为了僵尸对象,那么在下一次gc来临的时候,它将会被清除。其实大致的流程就如上面描述的那样,但是因为有了Finalizer线程,这些即将被"干掉"的对象还可以捞自己一把。在经过第一次gc标记后,没被标记的对象会在进行依次检查,检查他们是否有必须执行的finalize()方法(如果重写了finalize()方法,且finalize()方法没有被执行过,则认为它必须被执行),如果有,就将该对象添加到F-Queue的队列之中,而Finalizer线程就是用来执行这个队列的,如果Finalizer线程执行完毕后,队列中的某个对象将自己重新连接到其它非僵尸对象的引用上,那么他将被移出该队列,逃过被下次gc回收的命运。(说明两点: 1.Finalizer线程优先级很低,2.Finalizer不保证F-Queue中的所有对象的finalizer()方法均被执行完毕)。
3.Reference Handler:负责对象引用清除的线程,以便于垃圾回收。
4.Attach Listener:负责观察当前项目资源使用情况的线程,比如堆栈的使用,等等 比如我们向虚拟机发出一个java -version,jhat,jstack,jmap等指令时Attach Listener就会收集这些指令然后交给Singal Dispatcher。等待返回值。
5.Signal Dispatcher:接收Attach Listener提交给它的指令,分发给不同的模块,并将结果返回给调用者。
探究完Java程序为什么天生就是多线程这个问题,那么我们接下来来看看线程创建的几种方式。
1.继承自Thread类
2.实现Runnable接口
3.实现Callable接口
有些朋友这时就会提出问题了,为什么要有三种,有什么区别吗?
我下来就这三种实现方法的区别做一下简单的概述:
1>因为Java是单继承的,所以继承是非常宝贵的,如果你继承了Thread类以后,有需要继承别的类,那就很难受,所以接口的方式出现了,帮我们吧继承这一宝贵的资源省了下来。
2>实现Runnable接口后需要重写一个run()放啊,该方法就是线程启动后直接调用的方法,这个线程的执行逻辑都在这个方法中实现。
3>为什么有了Runnable还需要Callable接口呢?我给大家举一个例子,如果你是一个项目经理,你要做一个大项目,你将给手下的每一个程序员分配了不同的任务,然后在他们全部做完后,将做好的模块交给你,你再一进行组装,一个大项目就这么大而化小的完成了。在这个例子中,你手下的程序员就相当于一个个线程,你需要得到他们做完的小模块,而他们将小模块做好后交给你,这个模块就相当于这个"程序员线程"给你的返回值,而我们知道Runnable接口实现的run方法没有返回值,所以Callable接口就出现了。
在讲完为什么会有三个线程实现方式这一话题后,我们主要将焦点集中在Thread这个类上,因为它提供了很多操作当前线程的API,Java语言中的线程是协作式的,而这些API的存在就是为了协作线程,这些API的存在也恰恰证明了Java语言中的线程机制就是协作式的(还有另一种较多的是“抢占式”)。
我们今天就说一些常用的API但是不见得每个人都真的了解他们!
我今天并不是要说这张图,我只是想说与这张图相关的三个知识点:
1.Interrupt(),isInterrupted(),Interrupted()三个方法的区别与应用应该注意的事项。
1.Interrupt()方法可以理解为将Thread中一个名为“中断状态”的字段,从false设置为ture除此之外,毫无用处。Interrupt()这个方法并不会强制中断当前线程,而是告诉当前线程,你需要停止,至于线程听不听,那就不归它管了。
test1 测试如下:
public static void main(String[] args) throws InterruptedException {
new Runnable() {
@Override
public void run() {
//创建当前一个线程类
useThread ut = new useThread();
//ut.setDaemon(true);
//启动这个线程类
ut.start();
try {
//当前线程休息,将cpu执行时间让给别的线程
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打断ut线程
ut.interrupt();
}
}.run();
}
private static class useThread extends Thread{
//重写Thread实现的Runnable()接口中的run方法
@Override
public void run() {
try{
while(true){
System.out.println("真香,还在运行!");
}
//System.out.println("难受,我被终止了");
}finally {
System.out.println("抱歉,我也想低调啊,实力不允许!");
}
}
}
测试结果如下:
无限循环,所以证明了Interrupt()方法并不能打断线程。那么要interrupt()方法有什么用呢?
test2 测试如下:
看下面这个例子,main方法还是上面的main方法,但是线程类的代码有所修改:
private static class useThread extends Thread{
//重写Thread实现的Runnable()接口中的run方法
@Override
public void run() {
try{
/****
*
*
* 将这里的true换成了isInterrupted()方法
*
*/
while(!isInterrupted()){
System.out.println("真香,还在运行!");
}
//System.out.println("难受,我被终止了");
}finally {
System.out.println("抱歉,我也想低调啊,实力不允许!");
}
}
}
运行结果:
得出结论:
1.isInterrupted()方法是用于判断当前线程对象的中断状态是否为true。
2.Interrupt()放啊和isInterrupted()方法一起使用可以完成线程的打断。
test3 测试如下:
main()函数与上面相同,修改了线程实现类的部分代码:
private static class useThread extends Thread{
//重写Thread实现的Runnable()接口中的run方法
@Override
public void run() {
while(!isInterrupted()){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("真香,还在运行!");
}
System.out.println("难受,我被终止了");
}
}
运行结果如下:
这就很奇怪,我们明明已调用了Interrupt()方法将 中断状态为true,异常也被捕捉了,为什么while循环依然继续执行?
这里就有一个知识点需要注意了,在遇到InterruptedException异常时,会将 中断状态设置为false,所以while循环就不会停止了。
怎么证明上面说的是正确的呢?
test4 测试如下:
main()函数与上面相同,修改了线程实现类的部分代码:
private static class useThread extends Thread{
//重写Thread实现的Runnable()接口中的run方法
@Override
public void run() {
while(!isInterrupted()){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
//重新将是否打断当前线程的标志位设置为true
interrupt();
}
System.out.println("真香,还在运行!");
}
System.out.println("难受,我被终止了");
}
}
测试结果如下:
静态的Interrupted()方法就是判断当前线程是否处于中断状态,并在判断完成后将中断状态设置为false。在这里就不予证明了,大家下去可以自己测试一把,加深印象。
2.sleep与yeild()方法的区别:
这两个方法都可以让出当前的执行权限,将cup资源交给其它线程去使用。那么有什么不同呢?最大的不同就是sleep()方法在释放锁资源后在睡眠时间内不会重新获得cpu,而yeild()方法可能刚刚放弃cpu资源然后又立马获得了cpu的资源。
3.run()方法和start()方法的区别。
通过一个例子说明吧!
test5 测试如下:
修改了main()函数,实现类与test4相同:
public static void main(String[] args) throws InterruptedException {
new Runnable() {
@Override
public void run() {
useThread ut = new useThread();
ut.run();
}
}.run();
}
运行结果发现,无限循环。
得出结论:
1.直接调用run()方法,和调用其它类的方法没有什么不同,这时候run()方法退化成了普通方法。
2.调用线程类的start()方法,将当前创建的线程类实例映射到kernal Thread上。这时候start()方法启动了一个线程。