Java多线程实战系列笔记—多线程的实现方式

文章讲述了Java中Callable和Future的使用,强调了Callable配合FutureTask返回值的机制,以及Timer的单线程调度与ScheduledExecutorService的替代。还介绍了lambda表达式、StreamAPI和ForkJoinPool在多线程中的应用,以及线程池的工作原理和线程启动方法。
摘要由CSDN通过智能技术生成

public class ImplementCallable implements Callable {

@Override

public Integer call() throws Exception {

return new Random().nextInt();

}

public static void main(String[] args) throws ExecutionException, InterruptedException {

//创建线程池

ExecutorService service = Executors.newFixedThreadPool(1);

//提交任务,并用 Future提交返回结果

Future future = service.submit(new ImplementCallable());

Integer integer = future.get();

System.out.println(integer);

}

}

但是需要注意的是Callable是不能直接和Thread 使用的,需要配合线程池进行使用,我们也演示了如何通过Future对象获取结果。其实这里我们也可以将Callable配合FutureTask使用,后面我们在演示FutureTask的时候再演示

TimerTask

=========

public class TimerTaskDemo {

/**

  • 延迟100ms后,间隔1s打印出:hello world

  • @param args

  • @throws InterruptedException

*/

public static void main(String[] args) throws InterruptedException {

Timer t = new Timer();

t.scheduleAtFixedRate(new TimerTask() {

@Override

public void run() {

System.out.println(“hello world”);

}

}, 100, 1000);

}

}

这个看起来像是一种新的实现方式,但是当你去看TimerTask的实现的时候,你会发现这个类实际上是继承自Runnable也就是说这其实就是通过Runnable实现的方式,其实就是将TimerTask对象放入一个TaskQueue对象的队列中去,然后等到调度的时间到了,然后执行TimerTask的run 方法。

需要注意的实当我们使用Timer类的时候,提示我们使用ScheduledExecutorService来替代它,这是为什么呢,这是因为Timer没有使用线程池,而且整个Timer的调度是单线程的,所以我们还是要看一下Timer的具体实现原理

Javaå¤çº¿ç¨å®æç³»åç¬è®°âå¤çº¿ç¨çå®ç°æ¹å¼

我们下面具体梳理一下一个任务调度的流程

我们创建了一个Timer对象

public Timer() {

this(“Timer-” + serialNumber());

}

public Timer(String name) {

thread.setName(name);

thread.start();

}

我们看到这个构造函数里面启动了一个线程,这个线程就是TimerThread

使用了scheduleAtFixedRate方法,创建并调度了一个定时任务,其实只是将这个任务添加到了TaskQueue的队列中去

public void scheduleAtFixedRate(TimerTask task, Date firstTime,

long period) {

if (period <= 0)

throw new IllegalArgumentException(“Non-positive period.”);

sched(task, firstTime.getTime(), period);

}

private void sched(TimerTask task, long time, long period) {

if (time < 0)

throw new IllegalArgumentException(“Illegal execution time.”);

// Constrain value of period sufficiently to prevent numeric

// overflow while still being effectively infinitely large.

if (Math.abs(period) > (Long.MAX_VALUE >> 1))

period >>= 1;

synchronized(queue) {

if (!thread.newTasksMayBeScheduled)

throw new IllegalStateException(“Timer already cancelled.”);

synchronized(task.lock) {

if (task.state != TimerTask.VIRGIN)

throw new IllegalStateException(

“Task already scheduled or cancelled”);

task.nextExecutionTime = time;

task.period = period;

task.state = TimerTask.SCHEDULED;

}

// 将task 添加到了定时器的队列中去,

queue.add(task);

if (queue.getMin() == task)

queue.notify();

}

}

Timer 实现了它NB 的调度功能,这一部分我们是没有参与的,属于黑盒,其实也不黑,我们揭开看看,秘密就在TimerThread里面,我们知道这个线程对象在Timer创建的时候就启动了

private TaskQueue queue;

TimerThread(TaskQueue queue) {

this.queue = queue;

}

// 线程的入口, run 方法

public void run() {

try {

// 调用核心方法——循环

mainLoop();

} finally {

// Someone killed this Thread, behave as if Timer cancelled

synchronized(queue) {

newTasksMayBeScheduled = false;

queue.clear(); // Eliminate obsolete references

}

}

}

/**

  • The main timer loop. (See class comment.)

*/

private void mainLoop() {

while (true) {

try {

TimerTask task;

boolean taskFired;

synchronized(queue) {

// 任务队列为空则一直等到

while (queue.isEmpty() && newTasksMayBeScheduled)

queue.wait();

if (queue.isEmpty())

break; // Queue is empty and will forever remain; die

// Queue nonempty; look at first evt and do the right thing

long currentTime, executionTime;

// 从任务队列里获取任务

task = queue.getMin();

synchronized(task.lock) {

if (task.state == TimerTask.CANCELLED) {

queue.removeMin();

continue; // No action required, poll queue again

}

currentTime = System.currentTimeMillis();

executionTime = task.nextExecutionTime;

if (taskFired = (executionTime<=currentTime)) {

if (task.period == 0) { // Non-repeating, remove

queue.removeMin();

task.state = TimerTask.EXECUTED;

} else { // Repeating task, reschedule

queue.rescheduleMin(

task.period<0 ? currentTime - task.period
executionTime + task.period);

}

}

}

if (!taskFired) // Task hasn’t yet fired; wait

queue.wait(executionTime - currentTime);

}

// 执行任务,我们看到这里不是启动了新的线程,而是阻塞式执行

if (taskFired) // Task fired; run it, holding no locks

task.run();

} catch(InterruptedException e) {

}

}

}

到这里我们就知道了,Timer 是单线程执行定时任务的,也就是说可能你的任务时间到了还是没有执行,因为上一个任务还没有执行结束,这里我们写个例子看一下,就是上面那个例子,简单改改

public static void main(String[] args) throws InterruptedException {

timer();

}

// 整体来说我们还是希望1s 执行一次

public static void timer(){

Timer t = new Timer();

t.scheduleAtFixedRate(new TimerTask() {

@Override

public void run() {

System.out.println(Thread.currentThread().getName()+ “: hello world”);

// 我们在这个任务里面进行了等待

try {

TimeUnit.SECONDS.sleep(1000000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}, 100, 1000);

}

其实你执行了之后就会发现,并不能保证你的任务TimerTask对象1s 执行一次,因为上一次的任务还没有执行结束。这个也就是为什么idea 建议你使用ScheduledExecutorService,它本质上还是属于线程池的范畴,我们在学习线程池实现的时候再将。

FutureTask

==========

在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。

只有当运算完 成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。

一个FutureTask对象可以对Callable和Runnable的对象进行包装,由于FutureTask也是实现了了Runnable接口所以它可以提交给Executor来执行,也可以和Thread 搭配使用,但是这里有个问题那就是我们知道Runnable是没有返回值的,那FutureTask是怎么做到返回值的呢,我们下面看一下

public class FutureTaskDemo {

public static void main(String[] args) throws ExecutionException, InterruptedException {

callableDemo();

runableDemo();

}

public static void callableDemo() throws ExecutionException, InterruptedException {

Callable call = new Callable() {

@Override

public Integer call() throws Exception {

System.out.println(“正在计算结果…”);

Thread.sleep(3000);

return 1;

}

};

FutureTask task = new FutureTask<>(call);

Thread thread = new Thread(task);

thread.start();

Integer result = task.get();

System.out.println(“callableDemo的结果是:” + result);

}

public static void runableDemo() throws ExecutionException, InterruptedException {

Runnable run = new Runnable() {

@SneakyThrows

@Override

public void run() {

System.out.println(“正在计算结果…”);

Thread.sleep(3000);

}

};

// 返回值是我们自己预先定义的

FutureTask task = new FutureTask(run,1);

Thread thread = new Thread(task);

thread.start();

Integer result = task.get();

System.out.println(“runableDemo的结果是:” + result);

}

}

高级实现方式

======

Java8 lambda 表达式

================

这其实就是个语法糖,本质上还是老套路,下面我们还是简单看一下,这到底是是个啥玩意,本质上是函数式接口

public class LambdaDemo {

public static void main(String[] args) {

lambdaThread();

lambdaRunable();

}

public static void lambdaThread() {

Thread t = new Thread(() -> {

System.out.println(“lambdaThread 的实现方式”);

});

t.start();

}

public static void lambdaRunable() {

Runnable r = () -> {

System.out.println(“lambdaRunable 的实现方式”);

};

Thread t1 = new Thread®;

Thread t2 = new Thread(() -> {

r.run();

});

t1.start();

t2.start();

}

}

Java8 stream

============

这主要是用到了Java8 中的stream api

public class StreamDemo {

public static void main(String[] args) {

Stream.of(1,2,3,4,5,6,7,8,9,10).parallel().forEach(ele->{

System.out.println(Thread.currentThread().getName()+“:”+ele);

});

}

}

输出:我们看到启动了多个线程

ForkJoinPool.commonPool-worker-1:3

ForkJoinPool.commonPool-worker-1:4

ForkJoinPool.commonPool-worker-5:5

ForkJoinPool.commonPool-worker-5:10

ForkJoinPool.commonPool-worker-4:1

ForkJoinPool.commonPool-worker-2:9

ForkJoinPool.commonPool-worker-3:2

ForkJoinPool.commonPool-worker-5:6

ForkJoinPool.commonPool-worker-1:8

main:7

并发流使用的默认线程数等于你机器的处理器核心数。通过这个方法可以修改这个值,这是全局属性,System.setProperty("

java.util.concurrent.ForkJoinPool.common.parallelism", “12”);

fork/join框架是jdk1.7引入的,java8的stream多线程并非流的正是以这个框架为基础的,所以想要深入理解并发流就要学习fork/join框架。fork/join框架的目的是以递归方式将可以并行的任务拆分成更小的任务,然后将每个子任务的结果合并起来生成整体结果。它是ExecutorService接口的一个实现,它把子任务分配线程池(ForkJoinPool)中的工作线程。要把任务提交到这个线程池,必须创建RecursiveTask的一个子类,如果任务不返回结果则是RecursiveAction的子类。

线程池

===

这里我们就不详细解释和线程池有关的东西,我们就说线程池是如何实现多线的,也就是线程池是如何创建线程的,我们知道使用线程池的目的就是避免手动创建大量线程,将控制权交给线程池从而达到线程重用的目的。

首先我们看一下我们是怎么使用线程池的

public class ThreadPoolDemo {

public static void main(String[] args) throws InterruptedException {

ExecutorService executorService = Executors.newCachedThreadPool();

while (true) {

executorService.submit(() -> {

while (true) {

System.out.println(Thread.currentThread().getName());

try {

TimeUnit.SECONDS.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

});

TimeUnit.SECONDS.sleep(1);

}

}

}

从这里我们可以看到,我们其实只要向线程池提交一个Runnable对象即可,其实根据我们前面通过Runnable实现线程的方式我们大概能猜到,线程池就是利用我们提交上去的Runnable对象,为我们创建了线程。在我们创建线程池的时候其实有这样一个参数,就是用来创建线程的工厂,

Executors.newCachedThreadPool() newCachedThreadPool 方法其实只是java 为我们提供的一个便捷方法,其实最终都会调用下面这样一个构造函数。

public ThreadPoolExecutor(int corePoolSize,

int maximumPoolSize,

long keepAliveTime,

TimeUnit unit,

BlockingQueue workQueue) {

this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);

}

下面我们可以看一下这个工厂了

/**

  • The default thread factory

*/

static class DefaultThreadFactory implements ThreadFactory {

private static final AtomicInteger poolNumber = new AtomicInteger(1);

private final ThreadGroup group;

private final AtomicInteger threadNumber = new AtomicInteger(1);

private final String namePrefix;

DefaultThreadFactory() {

SecurityManager s = System.getSecurityManager();

group = (s != null) ? s.getThreadGroup() :

Thread.currentThread().getThreadGroup();

namePrefix = “pool-” +

poolNumber.getAndIncrement() +

“-thread-”;

}

// 下面就是我们创建线程的方法

public Thread newThread(Runnable r) {

Thread t = new Thread(group, r,

namePrefix + threadNumber.getAndIncrement(),

0);

if (t.isDaemon())

t.setDaemon(false);

if (t.getPriority() != Thread.NORM_PRIORITY)

t.setPriority(Thread.NORM_PRIORITY);

return t;

}

}

对于线程池而言,本质上是通过线程工厂创建线程的,默认采用 DefaultThreadFactory ,它会给线程池创建的线程设置一些默认值,比如:线程的名字、是否是守护线程,以及线程的优先级等。但是无论怎么设置这些属性,最终它还是通过 new Thread() 创建线程的 ,只不过这里的构造函数传入的参数要多一些,由此可以看出通过线程池创建线程并没有脱离最开始的那两种基本的创建方式,因为本质上还是通过 new Thread() 实现的。

线程的启动与状态

========

start 方法和 run 方法

================

这个其实是一个非常老生常谈的问题了,就是说我们只有调用start 方法才会帮我们启动一个线程,如果你是直接调用run 方法的话,那其实就是同步调用。

多次启动

====

我们先看一下多次启动会出现什么

public class ThreadStartTimes {

public static void main(String[] args) {

Runnable target;

Thread thread = new Thread(()->{

System.out.println(Thread.currentThread().getName());

});

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
在这里插入图片描述
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-5Dso678w-1713136677705)]

[外链图片转存中…(img-L6j1yMSH-1713136677706)]

[外链图片转存中…(img-7F8u4AhK-1713136677706)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书+2021年最新大厂面试题。
[外链图片转存中…(img-g5GwpRxD-1713136677706)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值