Java多线程的4种实现方式详解以及案例演示
Posted 刘Java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程的4种实现方式详解以及案例演示相关的知识,希望对你有一定的参考价值。
Java中多线程的实现方式很多,本文介绍四种方法,分别是继承Thread、实现Runnable、实现Callable、使用线程池。在最后章节给出了全部方式的演示。
文章目录
1 继承Thread类
1.1 步骤
- 创建一个类,继承Thread类,该类具备多线程的特征。
- 重写Tread类的run方法,在run方法当中定义线程任务。Run()方法体又叫线程执行体。
- 创建Tread类的子类的实例,即创建了线程对象。通过调用该对象的start方法开启一条新线程,调用start方法之后java虚拟机会自动调用该线程的run方法。
class Thread1 extends Thread
@Override
public void run()
//线程任务....
System.out.println("继承thread");
Thread1 thread1 = new Thread1();
thread1.start();
1.2 为什么要继承Thread类?为什么不直接创建Thread对象?
new Thread类得到一个线程对象是没有任何问题的,调用start()开启了一个新的线程,此时JVM会自动去调用run方法,但是run方法当中没有任何的内容。
所以需要定义一个类,继承Thread类,此时就可以重写run方法,把线程任务定义在run方法当中即可。
1.3 Thread相关方法
//获得当前线程对象;
//当一个类继承Tread类时,run方法中直接使用this关键字即可获取当前线程对象,而this关键字可以省略。
static Thread currentThread()
//获得线程的名称:run方法中直接getName();
//线程的名称:默认情况是Thread-0开始,数字依次往后增加,当然可以自定义线程的名称;每个线程都有一个标识名,并且多个线程可以同名。
//主线程默认名称: main 主线程的执行体:main方法
String getName()
//自定义线程名称,run方法中直接setName(String name);
void setName(String name)
//测试线程是否处于活动状态。就绪态和运行态就是活动状态.
boolean isAlive()
1.4 名称为什么是:Thread-? 编号
这个简单,我们进入Thread类看看源码就知道了。
public class Thread implements Runnable
//定义名字的变量
private volatile String name;
//定义线程计数器
private static int threadInitNumber;
private static synchronized int nextThreadNum()
//调用该方法,计数器自增1
return threadInitNumber++;
//构造器
Thread(Runnable target, AccessControlContext acc)
//调用构造器,此处将名字设为"Thread-",调用计数器方法
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
//init
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals)
if (name == null)
throw new NullPointerException("name cannot be null");
//给名字初始化
this.name = name;
//.....省略了代码
2 实现Runnable接口
2.1 步骤
- 创建一个类A:实现Runnable接口,并重写run方法,在run方法内定义线程任务。
- 创建Runnable接口实现类的实例并且作为一个target传递给Thread类的构造器创建一个Thread类对象,此时Runnable接口和Thread类就具有联系,而该Thread对象才是真正的线程对象。
- 使用Thread对象的start方法开启一个新的线程
注意:当类实现Runnable接口时,只能用Thread.currentThread()方法获取当前线程对象。
2.2 Runnable接口
Runnable为非Thread子类的类提供了一种激活方式。激活的意思是说某个线程已启动并且尚未停止。通过实例化某个 Thread 实例并将自身作为运行目标,就可以运行实现 Runnable 的类而无需创建 Thread 的子类。
大多数情况下,如果只想重写run()方法,而不重写其他Thread 方法,那么应使用 Runnable 接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。Java应该慎用继承。
唯一的方法: void run(); 要想实现该接口必须重写该方法!
3 实现Callable接口
3.1 概述
Java5使用Callable和Future创建线程,一般和ExecutorService连用。
和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大:
- call()方法可以有返回值。
- call()方法可以声明抛出异常。
Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。
3.2 相关方法
//试图取消该Future里面关联的Callable任务
boolean cancel(boolean mayInterruptIfRunning)
//返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值。
V get()
//返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException
V get(long timeout, TimeUnit unit)
//若Callable任务完成,返回True
boolean isDone()
//如果在Callable任务正常完成前被取消,返回True
boolean isCancelled()
3.3 步骤
- 创建一个类A实现Callable接口,重写call方法定义线程任务。
- 新建FutureTask类,传入类A,该类可以获取返回值。
- 新建Thread类传入FutureTask对象,使用start方法开启线程。
- 使用FutureTask类的get方法获取返回值。
3.4 三种实现方式的比较
3.4.1 继承Thread类
- 实现简单,直接继承Thread即可。并且在run()方法内获取当前线程直接使用this
就可以了,无须使用Thread.currentThread() 方法; - java当中的继承只能是单继承,一个类只能继承一个直接父类。如果一个类继承了Thread类,就不能有其他的父类。
- 创建的对象,即封装了线程任务,又封装了线程对象的特点(线程对象),不同的东西封装到了一个对象之中,不符合java面向对象的特点。
3.4.2 实现Runnable接口
- run()方法内获取当前线程不能使用使用this,必须使用Thread.currentThread() 方法;
- 将线程任务单独的封装到一个接口实现类当中,将线程任务和线程对象进行了分离:Runnable接口实现类当中定义线程任务,Thread类封装了线程对象,将不同的功能封装到不同的对象之中,这种思想更加的符合面向对象的特点。
- 避免了单继承带来的局限性。
- 适合多条线程处理同一个资源的情况,很容易的实现资源共享。需要多个线程完成一个任务时,比如:多个线程共同卖100张票,他们需要共享100张票这个资源。
3.4.3 实现Callable接口
具有实现Runnable接口的特点,同时还能获取返回值,并且可以抛出异常。
4 使用线程池工具
线程池提供了一个线程队列,队列中保存着所有等待状态的线程,避免了创建与销毁额外开销,提高了响应的速度,建议大家使用这种方法创建、管理线程。
体系结构:
java.util.concurrent.Executor : 负责线程的使用与调度的根接口
|–ExecutorService 子接口: 线程池的主要接口
|–ThreadPoolExecutor 线程池的实现类
|–ScheduledExecutorService 子接口:负责线程的调度
|–ScheduledThreadPoolExecutor :继承 ThreadPoolExecutor,实现 ScheduledExecutorService
工具类 : Executors
ExecutorService newFixedThreadPool() : 创建固定大小的线程池
ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
ExecutorService newSingleThreadExecutor() : 创建单个线程池。线程池中只有一个线程
ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务。
5 多线程创建方式演示
public class ThreadCreate
public static void main(String[] args) throws ExecutionException, InterruptedException
//main线程
System.out.println("main: " + Thread.currentThread().getName());
//方式一
Thread1 thread1 = new Thread1();
thread1.start();
//方式二
Thread thread = new Thread(new Thread2());
thread.start();
//方式三
FutureTask<String> stringFutureTask = new FutureTask<String>(new Thread3());
Thread thread2 = new Thread(stringFutureTask);
thread2.start();
//获取返回值
String s = stringFutureTask.get();
System.out.println(s);
//方法四:线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(() -> System.out.println("线程池方式: " + Thread.currentThread().getName()));
executorService.shutdown();
class Thread1 extends Thread
@Override
public void run()
System.out.println("继承THread: " + this.getName());
class Thread2 implements Runnable
@Override
public void run()
System.out.println("实现Runnable: " + Thread.currentThread().getName());
class Thread3 implements Callable
@Override
public Object call()
System.out.print("实现Callable: ");
return Thread.currentThread().getName();
开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系如果有什么不懂或者需要交流,各位可以留言。另外,希望收藏、关注一下,我将不间断更新Java各种教程!
以上是关于Java多线程的4种实现方式详解以及案例演示的主要内容,如果未能解决你的问题,请参考以下文章
[数据库事务与锁]详解八:底理解数据库事务乐观锁的一种实现方式——CAS