你也许不知道的Java多线程知识

我相信大多数人都多多线程有所了解,而我们的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()方法启动了一个线程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值