当前位置: 首页 > 科技观察

一个Java多线程问题,颠覆了我多年的认知!

时间:2023-03-13 00:03:03 科技观察

遇到奇怪的多线程问题不要害怕。你可以看懂今天的文章😁。在最近的学习过程中,我遇到了这样一个问题:Java如何创建多线程?哪一种?你可能会说,没那么简单吧:好像继承Thread类实现Runnable接口一样。如果让我回答这个问题,我好像是这样回答的,顶多再回答一个callable的方式,但是啊,最近看到这样一个说法,让我陷入了深思。“在Java中创建多线程只有一种方式,那就是newThread的方式,嗯?这是怎么回事?这有点颠覆性,我不敢相信,所以这是怎么回事?”看到这个回答,我觉得我应该深入探讨一下这个问题。你一般是怎么问这个问题的?关于上面提到的问题,不是什么高深的问题,我们大多数人都能回答,但回答的可能不全面.我这里带大家找找,这个问题怎么出现在面试题中呢?这个问题让你回答,你会怎么回答,它说的是四种,哪四种?这里希望大家的重点应该是它怎么问的。这里说的是线程的实现方法。记住,是实现方法.让我们继续寻找其他面试问题:在这个version,这道题是这样问的,注意线程的创建,我们上面说的就是实现线程,没错,是另一种说法,但其实是一样的?如果在面试中被问到这样的问题,到底是问我们创建线程的方式还是线程的实现方式。我们的答案肯定是围绕继承Thread类和实现Runnable接口。相信没有多少人会上去说:创建线程的方法只有一种。一,就是newThread的方式。估计你的问题和答案肯定会被反问,为什么?是啊,为什么,其实看到这个回答之后,认真思考之后,我觉得这个说法并没有错。会不会是之前学的全错了,大家一起来分析一下。在Java中,有这样一个笑话,没有女朋友怎么办,那就换一个吧。学过Java的人都知道是怎么回事。在Java中,万物皆对象,创建对象一般是一种新的方式。在Java中,Thread是一个线程类。按理说我们创建一个thread对象,应该是newTread的方式。先来看看我们平时是怎么创建线程的。通常,我们建议实现接口。方法,是Java的单继承多实现派生出来的,我们一起来看代码:写完上面的代码之后,我们需要停下来思考下面的问题。我们在这里创建了一个线程吗?我们这里好像只创建了一个实现了Runnbale接口的类。好像没有地方反映我们创建了线程。来做个简单的测试:publicclassTest{publicstaticvoidmain(String[]args){//获取线程数ThreadGroupthreadGroup=Thread.currentThread().getThreadGroup();while(threadGroup.getParent()!=null){threadGroup=threadGroup.getParent();}inttotalThread=threadGroup.activeCount();System.out.println("Currentthreadcount:"+totalThread);}}我这里写了一个简单的程序,就是获取线程中有多少个线程当前默认的线程组,这段代码你不用管,你只需要知道它有什么用,我们运行一下试试:这里是6,然后我们把之前实现的Runnbale的类加入进去,我们拿一起看看:publicclassTest{publicstaticvoidmain(String[]args){//获取线程数ThreadGroupthreadGroup=Thread.currentThread().getThreadGroup();while(threadGroup.getParent()!=null){threadGroup=threadGroup.getParent();}inttotalThread=threadGroup.activeCount();System.out.println("当前线程数:"+totalThread);}}classMyThreadimplementsRunnable{@Overridepublicvoidrun(){System.out.println("Runnbale的实现方式...");}}main方法MyThread的体现,可想而知当前线程数还是6,我们一般怎么使用这个MyThread?是这样吗?publicclassTest{publicstaticvoidmain(String[]args){Threadthread=newThread(newMyThread());thread.start();//获取线程数ThreadGroupthreadGroup=Thread.currentThread().getThreadGroup();while(threadGroup.getParent()!=null){threadGroup=threadGroup.getParent();}inttotalThread=threadGroup.activeCount();System.out.println("当前线程数:"+totalThread);}}熟悉了,我们通常这样操作这个,在座的各位肯定都知道,真正启用线程需要调用start。我们再运行一??下看看:看,线程数增加了1,相关数据也打印出来了。这创建了一个线程,因为我们写了这样的代码:Threadthread=newThread(newMyThread());thread.start();foundNothing,重点来了,这里是newThread,我们接下来看这段代码:publicclassTest{publicstaticvoidmain(String[]args){newThread();//获取线程数.get线程组();while(threadGroup.getParent()!=null){threadGroup=threaddGroup.getParent();}inttotalThread=threadGroup.activeCount();System.out.println("当前线程数:"+totalThread);}}猜猜,当前线程数是多少?会不会有人说是7😂,你知道为什么吗,是因为你没有调用start,我们再看看:publicclassTest{publicstaticvoidmain(String[]args){newThread().start();1//获取线程数ThreadGroupthreadGroup=Thread.currentThread().getThreadGroup();while(threadGroup.getParent()!=null){threadGroup=threadGroup.getParent();}inttotalThread=threadGroup.activeCount();System.out.println("Currentthreadcount:"+totalThread);}}这个属于线程的基础知识。顺便说一句,你知道为什么调用start而不是run吗?上面解释的问题是什么?真正的线程创建还是通过newThread,然后调用start启动线程,看这个:Threadthread=newThread(newMyThread());thread.start();同样是newThread的方式,然后构造函数传入一个Runnbale,我们再看看Thread的构造函数:看,这里可以传入一个Runnable,我们继续思考为什么要创建线程?想想看。我们为什么要创建线程?简而言之,我们需要这个线程为我们工作吗?怎么做?简而言之,这是运行方法吗:@Overridepublicvoidrun(){System.out.println("ThewaytoimplementRunnbale...");}我们在这个运行方法中执行一些任务。其实Thread类中也有这个run方法。可以看一下:@Overridepublicvoidrun(){if(target!=null){target.run();}}Thread类中的run方法并没有具体执行某些任务,而是在target中执行run。这个目标是什么:privateRunnabletarget;是一个Runnbale,可以看看我们实现Runnbale的MyThread的类:=newThread(newMyThread());线程。开始();我想你应该明白了,这么大的一个圈就是执行MyThread中的run方法,因为这是我们新创建的线程会做的事情。可能之前我们真的错了,我们再看另一种长话短说,即继承Thread类的形式:classAextendsThread{@Overridepublicvoidrun(){System.out.println("继承Thread类的线程...");这个我们知道在Thread类中有这个run方法,上面已经给大家展示过了,所以这里要重写run方法,如果我们要启动这个线程,需要这样做:newA()。开始();这里newA的本质还是newThread,不用解释了,接下来我们再看看其他的方式,比如匿名内部类的方式:newThread(newRunnable(){@Overridepublicvoidrun(){System.out.println("匿名内部类创建线程的方式");}}).start();多么明显,还是new了一个Thread,然后继续看实现callable的方式:);返回1024;然后我们还需要这个:FutureTaskfutureTask=newFutureTask<>(newC());Threadthread=newThread(futureTask);thread.start();System.out。println(futureTask.get());这是创建线程或新线程的真正方法。那么经过上面简单的分析,我们之前的理解可能真的是错误的。我们常说创建线程的方式是继承Thread类,它还可以实现Runnable接口等,但是现在看来这似乎是错误的,正确答案应该是:有一个和创建线程只有一种方式,那就是newThread()方式。盘点之前的错误答案说到这里,我觉得有必要盘点一下我们之前的错误答案,因为很多人即使按照之前的答案,答案要么不完整,要么不够好。首先,让我们看看我们最完整的答案应该包括以下方法:继承Thread类实现Runnable接口。匿名内部类实现可调用接口。上面5个关于使用线程池的回答比较完整。一般来说,我们推荐实现接口,接口是java单继承多实现派生出来的。另外,实现callable和使用线程池在实践中应用更为广泛。那么有些人可能会有疑惑。既然你说创建线程的方式只有一种,那就是newThread方式,那么以上五种方式是干什么用的呢?总结是的,那我们之前脱口而出的目的是什么?经过我们上面的分析,我想你应该已经看到了,无论是继承Thread类还是实现Runnbale,或者其他方式,看起来目的都是为了实现run方法(不可调用),准确的说,它就是执行我们真正想做的任务,也就是执行任务,也就是说,我们创建线程只有一种方式,那就是newThread方式,但是你想想,我们创建一个线程让他干活,那干嘛干嘛,我们可以继承Thread类,然后重写run方法告诉线程干什么,也可以搞一个Runnable,然后在里面实现run方法,然后把这个Runnable丢给Thread,告诉线程做什么,其他的也是一样。那么我们是不是可以这么理解:这些是线程执行任务的方式,或者说是真正实现线程任务的方式,但是不管怎样,说是创建线程的方式是不是有点不对?