Thread和Runnable 的区别在面试当中是比较容易考的,自己又刚好在做这个的实验课题,于是乎,决定好好的学习这一部分知识,并把我查过的资料,通过自己的理解,给大家整理出来。
目录
面向小白学习法——Thread和Runnable,扩展Callable!
Thread和Runnable的区别 (Callable扩展)
面向小白学习法——Thread和Runnable,扩展Callable!
Thread和Runnable的区别 (Callable扩展)
在面试当中,面试官特别喜欢问:“开启线程的两种方式,Thread 和 Runnable有什么区别?”。然后很多人可能看了许多博客,大致的内容是这样的(Callable不常用,但我依然为大家扩展了出来)——
Java实现多线程的方法有三种,分别是继承thread类,实现Runnable接口,实现实现callable接口(call()方法有返回值,run()方法无返回值)。接下来对它们进行比较——
Thread:
- 继承Thread类;
- 重写run方法,并且没有返回值(run()方法无返回值);
- 每次new Thread都是独立的,资源不共享;
Runnable:
- 实现Runnable接口;
- 重写run方法并且没有返回值(run()方法无返回值);
- 资源共享,比Thread类更加灵活,无单继承的限制;
Callable:
- 实现Callable接口;
- 使用call()方法,并且有返回值(call()方法有返回值);
- 可以抛出受检查的异常,比如ClassNotFoundException(call()方法可以);
如果想回答是上面内容的话,那么恭喜你,你其实并不太了解Runnable和Thread!看完这篇文章,你或许就明白了。
线程
线程的引入:
线程,在网络或多用户环境下,一个服务器通常需要接收大量且不确定数量用户的并发请求,为每一个请求都创建一个进程显然是行不通的,——无论是从系统资源开销方面或是响应用户请求的效率方面来看。因此,操作系统中线程的概念便被引进了。
使用的范围:
1.服务器中的文件管理或通信控制
2.前后台处理
3.异步处理
多线程
-
线程是一个进程中的执行场景。一个进程可以启动多个线程
-
多线程作用不是为了提高执行速度,而是提高应用程序的使用率
-
线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈
-
由于多线程在来回切换,所以给现实世界中的人类一种错觉:感觉多个线程在同时并发执行。
Thread
构造一个thread类:
package my;
import java.util.Date;
public class myThread01 extends Thread
{
@Override
public void run()
{
System.out.println("创建一个线程" + new Date());
}
}
thread类的常用方法
String getName() | 返回该线程的名字 |
void setName(String name) |
改变线程名字,使之与参数name相同 |
int getPriority() | 返回线程优先级 |
void setPriority(int newPriority) | 更改线程优先级 |
boolean isDaemon() | 测试该线程是否为守护线程 |
void setDaemon(boolean on) | 将该线程标记为守护线程或用户线程 |
static void sleep(long millis) | |
void interrupt() | 中断线程 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void join() | 等待该线程终止 |
void run() | |
void start() |
sleep()方法:
Thread.sleep(times) 使当前线程从running状态放弃处理器进入Block状态,休眠times毫秒,再返回Runnable状态。
public class Test
{
public static void main(String[] args) throws InterruptedException {
Thread t1 = new myThread01();//创建线程
Thread t2 = new myThread01();
t1.start();//告诉JVM再分配一个新的栈给t线程,run不需程序员手动调用
Thread.sleep(5000);
t2.start();//系统线程启动后自动调用run方法
}
}
它的运行结果是:
创建第一个线程后,休眠5秒,再创建另一个线程,所以时间相隔5秒。
注意:当一个线程处于睡眠阻塞时,若被其他线程调用,interrupt方法中断,则sleep方法会抛出InterruptedException异常
public static void main(String[] args) {
final Thread thread1 = new Thread() {
public void run() {
System.out.println("1号线程准备进入睡眠状态...");
try {
Thread.sleep(1000000);
}catch(InterruptedException e) {
System.out.println("1号线程被interrupt方法中断了...");
}
}
};//注意分号
Thread thread2 = new Thread() {
public void run() {
System.out.println("2号线程场了!");
System.out.println("2号线程Running...");
try{
Thread.sleep(1000);
}catch(InterruptedException e) {
}
thread1.interrupt();
}
};//注意分号
thread1.start();
thread2.start();
}
结果:
注意:当一个方法中的局部内部类中需要引用该方法的其他局部变量,这个变量必须使用final。
后台线程:
也被称作:守护线程、精灵线程。
特点:用法与前台线程差不多,只是当一个进程的所有前台进程都结束后,后台进程将会被强制结束(正在运行中的也会结束),从而使得进程结束程序。
package my;
public class Test
{
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
System.out.println("前台线程运行");
try {
Thread.sleep(4000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束进程");
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 10; i ++) {
System.out.println(i + ":running");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
};
t2.setDaemon(true);//t2设置为后台进程
t1.start();
t2.start();
}
}
结果:
setPriority()线程优先级
优先级被划分为1-10,1最低10最高。优先级越高的线程被分配时间片的机会越多,那么被CPU执行的机会越高。
public static void main(String[] args) {
//设置最高优先级的线程
Thread t1 = new Thread() {
public void run() {
System.out.println("最低优先级线程t1");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束进程t1");
}
};
//设置较高优先级的线程
Thread t2 = new Thread() {
public void run() {
System.out.println("最高优先级线程t2");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束进程t2");
}
};
//设置最低优先级线程
Thread t3 = new Thread() {
public void run() {
System.out.println("较高优先级线程t3");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束进程t3");
}
};
t1.setPriority(1);
t2.setPriority(10);
t3.setPriority(6);
t1.start();
t2.start();
t3.start();
}
运行两次的结果
注意:两次运行结果不一样,是没有问题的。因为设置了优先级也不能100%控制线程调度。只是最大程度的告知线程调度以更多的几率分配时间片给优先级高的线程。
join()方法
此方法允许执行这个方法的线程在该方法所属线程后等待,直到该方法所属线程结束后方可继续运行,否则会一致处于 阻塞状态。
//执行两个命令,只有第一个命令执行完成,才能执行第二个命令。
//即第二个命令等待第一个命令执行完成,再执行...
private static boolean isFinish = false;//判断命令是否结束
public static void main(String[] args) {
final Thread download = new Thread() {
public void run() {
System.out.println("执行第一个命令:running...");
for(int i = 1; i <= 10; i++) {
System.out.println("running:" + i +"!");
try {
Thread.sleep(50);
}catch(InterruptedException e) {
}
}
System.out.println("完成该命令,停止!");
isFinish = true;
}
};
Thread show = new Thread() {
public void run() {
try {
System.out.println(">>>等待第一个命令结束...");
download.join();
System.out.println(">>>第一个命令结束,执行第二个命令...");
if(!isFinish) {
throw new RuntimeException("Error!命令执行错误!");
}
System.out.println(">>>执行第二个命令!");
}catch(InterruptedException e) {
}
}
};
download.start();
show.start();
}
结果:
同步运行:执行有先后顺序
异步运行:执行没有先后顺序,多线程就是异步运行的。
join()可以协调线程之间的同步运行。
yield()方法
放弃当前的CPU资源,将它让给其它任务。但放弃的时间不确定。
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run()
{
System.out.println("2号线程:线程1,命令5完成后等等我!");
System.out.println("1好线程:好的,我现在出发了...");
for(int i = 0; i < 10; i++) {
System.out.println("1号线程执行中..." + i);
if(i == 5) {
System.out.println("完成命令5,等待2号线程执行!");
this.yield();
}
}
System.out.println("1号线程执行结束。");
}
};
Thread t2 = new Thread() {
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println("2号线程执行中..." + i);
}
System.out.println("2号线程:已执行结束,1号线程你继续。");
}
};
t1.start();
t2.start();
}
结果:
每次执行可能是不一样的!
使用yield方法时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)。但是吧,其实有没有此方法对上述代码片段没有明显的区别。
Runnable
在使用Thread的时候只需要new一个实例出来,调用百start()方法即可以启度动一个线程。
Thread Test = new Thread();
Test.start();
在使用Runnable的时候需要先new一个继承Runnable的实例,之后用子类Thread调用。
Test impelements Runnable
Test t = new Test();
Thread test = new Thread(t);
class MyThread implements Runnable
{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run()
{
for(int i = 0; i < 100; i++) {
System.out.println("线程开始:" + this.name+",i ="+ i);
}
}
};
main:
public class Test
{
public static void main(String[] args) {
MyThread t1 = new MyThread("t1");
t1.run();
}
}
结果:
特别注意:
Java中真正能创建线程的只有Thread类对象
通过实现Runnable的方式,最终还是通过Thread类对象来创建线程
所以有些人把实现了Runnable接口的类,称为线程辅助类,而Thread类才是真正的线程类
面试官问“Thread和Runnable的区别?”这个问题中的所谓的用Runnable的形式,其实只不过是用Runnable调用Thread的构造方法,其实创建的还是Thread对象。
Runnable 只是一个接口定义,表示一个可执行的代码单元,Thread类只是实现了Runnable接口,并且Thread类有一个构造方法中是接收一个Runnable的接口的。
扩展——Callable
Callable可以返回执行结果,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果;
Callable接口的call()方法允许抛出异常;Runnable的run()方法异常只能在内部消化,不能往上继续抛。
class CallableThreadTest implements Callable<Integer>
{
public static void main(String[] args) throws ExecutionException,InterruptedException{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
new Thread(ft,"有返回值的线程").start();
System.out.println("子线程的返回值" + ft.get());
}
@Override
public Integer call() throws Exception
{
int i;
for( i = 0; i < 10; i += 1) {
System.out.println(Thread.currentThread().getName() + "" + i);
}
return i;
}
}
结果:
注:Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
Future:表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作。
FutureTask:一个可取消的异步计算。FutureTask提供了对Future的基本实现,可以调用方法去开始和取消一个计算,可以查询计算是否完成并且获取计算结果。
结语:我是刘小白,喜欢和大家一起分享,一起学习,共同进步。
关注博主链接 博主的个人主页