个人简介
👨🏻💻个人主页:陈橘又青
🏃🏻♂️博客记录心情,代码编写人生。
🌟如果文章对你有用,麻烦关注点赞收藏走一波,感谢支持!
🌱欢迎订阅我的专栏:
【Java进阶之路】带你入门Java,从0到1。
【Java全栈编程】带你项目实战,掌握核心。
前言
Java多线程实现方式主要有四种:
① 继承Thread类、实现Runnable接口
② 实现Callable接口通过FutureTask包装器来创建Thread线程
③ 使用ExecutorService、Callable
④ Future实现有返回结果的多线程
其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。
目录
3、实现Callable接口通过FutureTask包装器来创建Thread线程
4、使用ExecutorService、Callable、Future实现有返回结果的线程
2. 如何启动一个新线程、调用 start 和 run 方法的区别?
一、四种方式实现多线程
1、继承Thread类创建线程
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
2、实现Runnable接口创建线程
如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口,如下:
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:
public void run() {
if (target != null) {
target.run();
}
}
3、实现Callable接口通过FutureTask包装器来创建Thread线程
Callable接口(也只有一个方法)定义如下:
public interface Callable<V> {
V call() throws Exception; }
public class SomeCallable<V> extends OtherClass implements Callable<V> {
@Override
public V call() throws Exception {
// TODO Auto-generated method stub
return null;
}
}
Callable<V> oneCallable = new SomeCallable<V>();
//由Callable<Integer>创建一个FutureTask<Integer>对象:
FutureTask<V> oneTask = new FutureTask<V>(oneCallable);
//注释:FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。
//由FutureTask<Integer>创建一个Thread对象:
Thread oneThread = new Thread(oneTask);
oneThread.start(); //至此,一个线程就创建完成了。
4、使用ExecutorService、Callable、Future实现有返回结果的线程
ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。而且自己实现了也可能漏洞百出。
可返回值的任务必须实现Callable接口。类似的,无返回值的任务必须实现Runnable接口。
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。
注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。
下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:
import java.util.concurrent.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
/**
* 有返回值的线程
*/
@SuppressWarnings("unchecked")
public class Test {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
System.out.println("----程序开始运行----");
Date date1 = new Date();
int taskSize = 5;
// 创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 执行任务并获取Future对象
Future f = pool.submit(c);
// System.out.println(">>>" + f.get().toString());
list.add(f);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从Future对象上获取任务的返回值,并输出到控制台
System.out.println(">>>" + f.get().toString());
}
Date date2 = new Date();
System.out.println("----程序结束运行----,程序运行时间【"
+ (date2.getTime() - date1.getTime()) + "毫秒】");
}
}
class MyCallable implements Callable<Object> {
private String taskNum;
MyCallable(String taskNum) {
this.taskNum = taskNum;
}
public Object call() throws Exception {
System.out.println(">>>" + taskNum + "任务启动");
Date dateTmp1 = new Date();
Thread.sleep(1000);
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println(">>>" + taskNum + "任务终止");
return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
}
}
二、多线程相关知识
1.Runnable 和 Callable 的区别?
· 主要区别 Runnable 接口 run 方法无返回值;
· Callable 接口 call 方法有返回值,支持泛型 Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;
· Callable 接口 call 方 法允许抛出异常,可以获取异常信息
2. 如何启动一个新线程、调用 start 和 run 方法的区别?
· 线程对象调用 run 方法不开启线程。仅是对象调用方法。
· 线程对象调用 start 开启线程,并让 jvm 调用 run 方法在开启的线程中执行调用 start 方法可以启动线程,并且使得线程进入就绪状态,而 run 方法只是 thread 的一 个普通方法,还是在主线程中执行。
3.线程相关的基本方法?
线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等
· 线程等待(wait) 调用该方法的线程进入 waiting状态,只有等待另外线程的通知或被中断才会返回,需要注意的是调用 wait()方法后,会释放对象 的锁。因此,wait 方 法一般用在同步方法或同步代码块中。
· 线程睡眠(sleep) sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占 有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法 会导致当前线程进入 WATING 状态.
· 线程让步(yield) yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对 线程优先级并不敏感。
· 线程中断(interrupt) 中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的 一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)
· Join 等待其他线程终止 join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方 法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变 为就绪状态,等待 cpu 的宠幸.
· 线程唤醒(notify) Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如 果所有线程都在此对象上等待,则会选择唤醒其中一个线 程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视 器上等待,直到当前的线程放弃此对象上的锁 定,才能继续执行被唤醒的线程, 被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类 似的方法还有 notifyAll() ,唤醒再 次监视器上等待的所有线程。
5.wait()和 sleep()的区别?
· ① 来自不同的类 wait():来自 Object 类; sleep():来自 Thread 类;
· ② 关于锁的释放: wait():在等待的过程中会释放锁; sleep():在等待的过程中不会释放锁
· ③ 使用的范围: wait():必须在同步代码块中使用; sleep():可以在任何地方使用;
· ④ 是否需要捕获异常 wait():不需要捕获异常; sleep():需要捕获异常;
6.多线程原理
多线程原理:多线程是通过并发的方式进行。对于一个CPU它在某个时间点上,只能执行一个程序,即同一时间只能运行一个进程,CPU会不断地在这些进程之间切换,每个线程执行一个时间。因为CPU的执行速度相对我们的感觉实在太快了,虽然CPU在多个进程之间轮换执行,但我们自己感到好像多个进程在同时执行。
CPU会在多个进程之间做着切换,如果我们开启的程序过多,CPU切换到每一个进程的时间也会变长,我们也会感觉机器运行变慢。所以合理的使用多线程可以提高效率,但是大量使用,并不能给我们带来效率上的提高。
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。
🥰以上就是本篇博客的全部内容,如果对你有用麻烦关注点赞收藏三连走一波!
👋如有问题欢迎留言,如有错误烦请指正,期待你的评论!