JSP 中的JavaBean问题 大神请进对于你这个代码来说,用不用bean的意义不大。 用bean的话, 创建一个bean将n这个数设置进去。 将bean对象保存到request.setAttribute 中 在页面
使用实现Callable接口的方式创建线程时,必须实现()方法
java中Runnable和Callable的区别
在Java多线程开发中,Runnable接口与Callable接口各自扮演着核心角色。随着Java 1.5版本的更新,Callable接口作为增强版本加入。本文深入探讨了Runnable与Callable的差异。
从接口定义出发,Runnable接口需要实现run()方法,而Callable接口则需要实现call()方法。创建线程时,Runnable通常通过继承Thread类或实现Runnable接口实现,而Callable则只能通过ExecutorService实现。
关键区别在于Callable可以返回值,而Runnable则不能。通过ExecutorService提交任务后,Callable任务返回Future对象,而Runnable任务返回的Future对象不包含任何值。
在异常处理方面,Runnable的run()方法不抛出任何异常,因此所有Checked Exception必须在run()实现中自行处理。相比之下,Callable的call()方法抛出Exception,允许在外部捕获Checked Exception。在Callable中抛出的异常会被包含在Future对象中,调用Future.get()方法时会抛出ExecutionException,通过e.getCause()获取具体异常信息。
本文示例代码可参阅
https://github.com/ddean2009/le...
,更多教程请访问
https://blog.flydean.com/...
。
老生常谈线程基础的几个问题
实现线程只有一种方式我们知道启动线程至少可以通过以下四种方式:实现Runnable接口
继承Thread类
线程池创建线程
带返回值的Callable创建线程
但是看它们的底层就一种方式,就是通过newThread()实现,其他的只不过在它的上面做了层封装。
实现Runnable接口要比继承Thread类的更好:
结构上分工更明确,线程本身属性和任务逻辑解耦。
某些情况下性能更好,直接把任务交给线程池执行,无需再次newThread()。
可拓展性更好:实现接口可以多个,而继承只能单继承。
有的时候可能会问到启动线程为什么是start()方法,而不是run()方法,这个问题很简单,执行run()方法其实就是在执行一个类的普通方法,并没有启动一个线程,而start()方法点进去看是一个native方法。
当我们在执行java中的start()方法的时候,它的底层会调JVM由c++编写的代码Thread::start,然后c++代码再调操作系统的create_thread创建线程,创建完线程以后并不会马上运行,要等待CPU的调度。CPU的调度算法有很多,比如先来先服务调度算法(FIFO),最短优先(就是对短作业的优先调度)、时间片轮转调度等。如下图所示:
线程的状态
在Java中线程的生命周期中一共有6种状态。
NEW:初始状态,线程被构建,但是还没有调用start方法
RUNNABLE:运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为运行中
BLOCKED:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU使用权
WAITING:等待状态
TIMED_WAITING:超时等待状态,超时以后自动返回
TERMINATED:终止状态,表示当前线程执行完毕
当然这也不是我说的,源码中就是这么定义的:
publicenumState{/***Threadstateforathreadwhichhasnotyetstarted.*/NEW,/***Threadstateforarunnablethread.Athreadintherunnable*stateisexecutingintheJavavirtualmachinebutitmay*bewaitingforotherresourcesfromtheoperatingsystem*suchasprocessor.*/RUNNABLE,/***Threadstateforathreadblockedwaitingforamonitorlock.*Athreadintheblockedstateiswaitingforamonitorlock*toenterasynchronizedblock/methodor*reenterasynchronizedblock/methodaftercalling*{@linkObject#wait()Object.wait}.*/BLOCKED,/***Threadstateforawaitingthread.*Athreadisinthewaitingstateduetocallingoneofthe*followingmethods:*
- *
- {@linkObject#wait()Object.wait}withnotimeout *
- {@link#join()Thread.join}withnotimeout *
- {@linkLockSupport#park()LockSupport.park} *
Athreadinthewaitingstateiswaitingforanotherthreadto*performaparticularaction.**Forexample,athreadthathascalledObject.wait()*onanobjectiswaitingforanotherthreadtocall*Object.notify()orObject.notifyAll()on*thatobject.AthreadthathascalledThread.join()*iswaitingforaspecifiedthreadtoterminate.*/WAITING,/***Threadstateforawaitingthreadwithaspecifiedwaitingtime.*Athreadisinthetimedwaitingstateduetocallingoneof*thefollowingmethodswithaspecifiedpositivewaitingtime:*
- *
- {@link#sleepThread.sleep} *
- {@linkObject#wait(long)Object.wait}withtimeout *
- {@link#join(long)Thread.join}withtimeout *
- {@linkLockSupport#parkNanosLockSupport.parkNanos} *
- {@linkLockSupport#parkUntilLockSupport.parkUntil} *
下面是这六种状态的转换:
New新创建
New表示线程被创建但尚未启动的状态:当我们用newThread()新建一个线程时,如果线程没有开始调用start()方法,那么此时它的状态就是New。而一旦线程调用了start(),它的状态就会从New变成Runnable。
Runnable运行状态
Java中的Runable状态对应操作系统线程状态中的两种状态,分别是Running和Ready,也就是说,Java中处于Runnable状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配CPU资源。
如果一个正在运行的线程是Runnable状态,当它运行到任务的一半时,执行该线程的CPU被调度去做其他事情,导致该线程暂时不运行,它的状态依然不变,还是Runnable,因为它有可能随时被调度回来继续执行任务。
在Java中Blocked、Waiting、TimedWaiting,这三种状态统称为阻塞状态,下面分别来看下。
Blocked
从上图可以看出,从Runnable状态进入Blocked状态只有一种可能,就是进入synchronized保护的代码时没有抢到monitor锁,jvm会把当前的线程放入到锁池中。当处于Blocked的线程抢到monitor锁,就会从Blocked状态回到Runnable状态。
Waiting状态
我们看上图,线程进入Waiting状态有三种可能。
没有设置Timeout参数的Object.wait()方法,jvm会把当前线程放入到等待队列。
没有设置Timeout参数的Thread.join()方法。
LockSupport.park()方法。
Blocked与Waiting的区别是Blocked在等待其他线程释放monitor锁,而Waiting则是在等待某个条件,比如join的线程执行完毕,或者是notify()/notifyAll()。
当执行了LockSupport.unpark(),或者join的线程运行结束,或者被中断时可以进入Runnable状态。当调用notify()或notifyAll()来唤醒它,它会直接进入Blocked状态,因为唤醒Waiting状态的线程能够调用notify()或notifyAll(),肯定是已经持有了monitor锁,这时候处于Waiting状态的线程没有拿到monitor锁,就会进入Blocked状态,直到执行了notify()/notifyAll()唤醒它的线程执行完毕并释放monitor锁,才可能轮到它去抢夺这把锁,如果它能抢到,就会从Blocked状态回到Runnable状态。
TimedWaiting状态
在Waiting上面是TimedWaiting状态,这两个状态是非常相似的,区别仅在于有没有时间限制,TimedWaiting会等待超时,由系统自动唤醒,或者在超时前被唤醒信号唤醒。
以下情况会让线程进入TimedWaiting状态。
设置了时间参数的Thread.sleep(longmillis)方法。
设置了时间参数的Object.wait(longtimeout)方法。
设置了时间参数的Thread.join(longmillis)方法。
设置了时间参数的LockSupport.parkNanos(longnanos)。
LockSupport.parkUntil(longdeadline)方法。
在TimedWaiting中执行notify()和notifyAll()也是一样的道理,它们会先进入Blocked状态,然后抢夺锁成功后,再回到Runnable状态。当然,如果它的超时时间到了且能直接获取到锁/join的线程运行结束/被中断/调用了LockSupport.unpark(),会直接恢复到Runnable状态,而无需经历Blocked状态。
Terminated终止
Terminated终止状态,要想进入这个状态有两种可能。
run()方法执行完毕,线程正常退出。
出现一个没有捕获的异常,终止了run()方法,最终导致意外终止。
线程的停止interrupt
我们知道Thread提供了线程的一些操作方法,比如stop(),suspend()和resume(),这些方法已经被Java直接标记为@Deprecated,这就说明这些方法是不建议大家使用的。
因为stop()会直接把线程停止,这样就没有给线程足够的时间来处理想要在停止前保存数据的逻辑,任务戛然而止,会导致出现数据完整性等问题。这种行为类似于在linux系统中执行kill-9类似,它是一种不安全的操作。
而对于suspend()和resume()而言,它们的问题在于如果线程调用suspend(),它并不会释放锁,就开始进入休眠,但此时有可能仍持有锁,这样就容易导致死锁问题,因为这把锁在线程被resume()之前,是不会被释放的。
interrupt
最正确的停止线程的方式是使用interrupt,但interrupt仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。
下面我们来看下例子:
publicclassInterruptExampleimplementsRunnable{//interrupt相当于定义一个volatile的变量//volatilebooleanflag=false;publicstaticvoidmain(String[]args)throwsInterruptedException{Threadt1=newThread(newInterruptExample());t1.start();Thread.sleep(5);//Main线程来决定t1线程的停止,发送一个中断信号,中断标记变为truet1.interrupt();}@Overridepublicvoidrun(){while(!Thread.currentThread().isInterrupted()){System.out.println(Thread.currentThread().getName()+"--");}}}
执行一下,运行了一会就停止了
主线程在调用t1的interrupt()之后,这个线程的中断标记位就会被设置成true。每个线程都有这样的标记位,当线程执行时,会定期检查这个标记位,如果标记位被设置成true,就说明有程序想终止该线程。在while循环体判断语句中,通过Thread.currentThread().isInterrupt()判断线程是否被中断,如果被置为true了,则跳出循环,线程就结束了,这个就是interrupt的简单用法。
阻塞状态下的线程中断
下面来看第二个例子,在循环中加了Thread.sleep1000秒。
publicclassInterruptSleepExampleimplementsRunnable{//interrupt相当于定义一个volatile的变量//volatilebooleanflag=false;publicstaticvoidmain(String[]args)throwsInterruptedException{Threadt1=newThread(newInterruptSleepExample());t1.start();Thread.sleep(5);//Main线程来决定t1线程的停止,发送一个中断信号,中断标记变为truet1.interrupt();}@Overridepublicvoidrun(){while(!Thread.currentThread().isInterrupted()){try{Thread.sleep(1000000);}catch(InterruptedExceptione){//中断标记变为falsee.printStackTrace();}System.out.println(Thread.currentThread().getName()+"--");}}}
再来看下运行结果,卡主了,并没有停止。这是因为main线程调用了t1.interrupt(),此时t1正在sleep中,这时候是接收不到中断信号的,要sleep结束以后才能收到。这样的中断太不及时了,我让你中断了,你缺还在傻傻的sleep中。
Java开发的设计者已经考虑到了这一点,sleep、wait等方法可以让线程进入阻塞的方法使线程休眠了,而处于休眠中的线程被中断,那么线程是可以感受到中断信号的,并且会抛出一个InterruptedException异常,同时清除中断信号,将中断标记位设置成false。
这时候有几种做法:
直接捕获异常,不做处理,e.printStackTrace();打印下信息
将异常往外抛出,即在方法上throwsInterruptedException
再次中断,代码如下,加上Thread.currentThread().interrupt();
@Overridepublicvoidrun(){while(!Thread.currentThread().isInterrupted()){try{Thread.sleep(1000000);}catch(InterruptedExceptione){//中断标记变为falsee.printStackTrace();//把中断标记修改为trueThread.currentThread().interrupt();}System.out.println(Thread.currentThread().getName()+"--");}}
这时候线程感受到了,我们人为的再把中断标记修改为true,线程就能停止了。一般情况下我们操作线程很少会用到interrupt,因为大多数情况下我们用的是线程池,线程池已经帮我封装好了,但是这方面的知识还是需要掌握的。感谢收看,多多点赞~
作者:小杰博士
Java | 线程的三种创建和启动方式
了解Java中线程的创建与启动方式,对提高程序性能至关重要。本文将探讨三种主流的创建与启动线程方法。首先,通过继承`Thread`类是最直接的方式。创建新类继承`Thread`,重写`run()`方法。随后,实例化该类并调用`start()`方法启动线程。
其次,推荐使用实现`Runnable`接口的方法。创建新类实现`Runnable`,实现`run()`方法。将该类实例传递给`Thread`对象,调用`start()`方法启动线程。
最后,利用`Executor`框架和`Callable`接口,实现更高级的线程管理。`Callable`接口与`Runnable`相似,拥有`call()`方法,可在执行任务时返回结果或抛出异常。`Executor`框架引入于Java 5,管理线程池,允许提交`Runnable`或`Callable`任务,无需直接管理线程。
具体实现示例包括:
1. 继承`Thread`类实现任务执行。
2. 实现`Runnable`接口并调用`Thread`的`start()`方法启动线程。
3. 使用`Executor`框架与`Callable`接口,创建线程池,提交任务并获取结果。
这三种方式均能创建与启动线程,其中实现`Runnable`接口的方法更为灵活,`Executor`框架提供高级线程管理功能,便于控制线程池大小和生命周期。
本文分享至此,希望对读者有所启发。欢迎关注,更多编程知识等你探索。感谢支持,期待更多交流。我是影子,一名热爱Java与AI编程的程序猿。
创建线程的几种方式
Java中创建线程主要有三种方式:一、继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
二、通过Runnable接口创建线程类
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
三、通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
线程的四种创建方式
以下是线程创建的四种常见方式的概述:1. 继承 Thread 类:通过创建一个继承自 Thread 类的自定义类,如 ThreadDemo,并重写其 run() 方法。首先创建 ThreadDemo 对象,然后调用 start() 方法启动线程,run() 中包含线程操作。
2. 实现 Runnable 接口:创建一个实现 Runnable 接口的类 RThread,重写 run() 方法。创建 RThread 对象后,用它作为参数创建 Thread 对象并调用 start() 方法启动线程。
3. 使用 ExecutorService 和 Callable:当需要在主线程外并发执行任务并处理返回结果时,用 Callable 接口实现类,重写 call() 方法。首先创建线程池,然后提交 Callable 对象,接收并处理返回结果,最后关闭线程池。
4. 基于线程池的 execute():利用线程池的缓存策略,通过调用线程池的 execute() 方法,创建匿名内部类的 Runnable 对象并执行run() 方法,这种方式适用于临时线程任务。
以上方法各有其适用场景,选择合适的方式取决于具体的需求和任务特性。
相关文章
- 详细阅读
-
JAVA程序设计多态编程语言详细阅读
Java程序如何实现多态多态,就是重载和重写.重载发生在一个类中.重写发生在子类,意思就是子类重写父类相同名称的方法.刚学语言有的东西,不必搞得那么清楚,只有知道怎么用就
-
单机练习信息技术模拟学籍号输不进详细阅读
全国学生学籍号,在平台中注册完后输入秘密不正确怎么办首先,平台号不是手机号,要注意。 然后,忘记了只能找班主任。 找班主任,班主任教育平台,登陆,应用里有重置学生密码。 找班主
- 详细阅读
-
在Java编译器中,输人某个符号后会详细阅读
JAVA初学问题!Java新手入门的30个基本概念 在我们学习Java的过程中,掌握其中的基本概念对我们的学习无论是J2SE,J2EE,J2ME都是很重要的,J2SE是Java的基础,所以有必要对其中的
-
请编程序将输入的字符串译成密码,详细阅读
c语言编写:将用户输入的字符串翻译成密码串:规则如下?a b c d e f g h i j 例子里输入ab,cd a是第1个字符,那么换成a后面第1个,也就是b b是第2个字符,那么换成b后面第2个,也就是d
-
helloword编程无人机放不了块怎么详细阅读
我的世界突然放不了方块怎么办最近我在玩我的世界游戏的时候,突然发现无法放置方块了。这让我感到非常困惑和烦躁,因为这意味着我无法继续建造我的世界。
在经过一番调查和研 -
Java静态方法編程详细阅读
Java中的静态方法是什么?静态方法是在类中使用staitc修饰的方法,在类定义的时候已经被装载和分配。静态方法为类所有,可以通过对象来使用,也可以通过类来使用。我们之前一直写的
-
scratch在线编程平台怎么进入详细阅读
怎么进入scratch编程?想要在scratch编程,首先你要安装scratch软件。 软件分离线版和在线版。 离线版,我们安装安装包在我们的笔记本电脑上 在线版,我们可以使用网页版的scratch
-
java提示脚本缺少)是否运行,无论点击详细阅读
电脑操作程序问题,某JAVA软件,无响应后通过任务管理器强制关闭,之后打开后自动退出,即打不开。软件bug,具体要找开发商,强制杀死进程会导致数据不完整,应该是临时生成的数据错误造
