Java并发编程之线程的应用操作,助你深化学习核心技能(1)

本文详细介绍了Java中线程的创建方式(继承Thread,Runnable,Callable),展示了如何使用FutureTask包装Callable并获取返回值,探讨了线程间通信、volatile和synchronized的关键字作用,以及控制线程的join、sleep、yield和中断机制。适合深入理解Java并发编程的开发者学习。

int sum = 0;

for (int i = 0; i < 10; i++) {

System.out.println(Thread.currentThread().getName()+“执行了”+i);

sum+=i;

}

return sum;

}

}

public class Demo_02_02_1_ThreadCreateWays {

public static void main(String[] args) {

// 用FutureTask包一层

FutureTask futureTask = new FutureTask<>(new MyThreadByCallable());

new Thread(futureTask).start();

try {

// 调用futureTask的get能拿到返回的值

System.out.println(futureTask.get());

} catch (InterruptedException e) {

e.printStackTrace();

} catch (ExecutionException e) {

e.printStackTrace();

}

}

}

这是最复杂的一种方式,他可以有返回值,归纳一下步骤:

  1. 搞一个类实现Callable接口,重写call方法,在call执行任务

  2. FutureTask包装实现Callable接口类的实例

  3. FutureTask的实例作为Thread构造参数

  4. 调用FutureTask实例的get拿到返回值,调这一句会阻塞父线程

Callable也是函数式接口,所以也能用lamba

为啥Thread构造里边能放Runnable,也能放FutureTask? 其实FutureTask继承RunnableFuture,而RunnableFuture继承Runnable和Future,所以FutureTask也是Runnable

三种方式比较

======

| 方式 | 使用简易程度 | 是否可以共享任务代码 | 是否可以有返回值 | 是否可以声明抛出异常 | 是否可以再继承别的类 |

| — | — | — | — | — | — |

| 继承Thread | 简单 | 不能 | 不能 | 不能 | 不能 |

| Runnable | 中等 | 可以 | 不能 | 不能 | 可以 |

| Callable | 复杂 | 可以 | 可以 | 可以 | 可以 |

继承Thread是最容易的,但是也是最不灵活的

使用Callable时最复杂的,但是也是最灵活的

这里说的共享任务代码举个例子:

还是上面那个MyThreadByRunnable

MyThreadByRunnable myThreadByRunnable = new MyThreadByRunnable();

Thread thread = new Thread(myThreadByRunnable);

thread.start();

// 再来一个,复用了任务代码,继承Thread就不行

Thread thread2 = new Thread(myThreadByRunnable);

thread2.start();

线程间通信

=====

线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码一步一步地执行,直到终止。但是,每个运行中的线程,如果仅仅是孤立地运行,那么没有一点儿价值,或者说价值很少,如果多个线程能够相互配合完成工作,这将会带来巨大的价值。

volatile和synchronized关键字

Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝(虽然对象以及成员变量分配的内存是在共享内存中的,但是每个执行的线程还是可以拥有一份拷贝,这样做的目的是加速程序的执行,这是现代多核处理器的一个显著特性),所以程序在执行过程中,一个线程看到的变量并不一定是最新的。

关键字volatile可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。

通过使用javap工具查看生成的class文件信息来分析synchronized关键字的实现细节,代码如下

public class Synchronized {

public static void main(String[] args) {

synchronized (Synchronized.class){

m();

}

}

public static synchronized void m(){

}

}

对于同步块的实现使用了monitorenter和monitorexit指令,而同步方法则是依赖方法修饰符上的ACC_SYNCHRONIZED来完成。无论采用哪种方式,其本质是对一个对象的监视器进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。

任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器(执行该方法)的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。

控制线程

====

join


主线程join一个线程,那么主线程会阻塞直到join进来的线程执行完,主线程继续执行, join如果带超时时间的话,那么如果超时的话主线程也会不再等join进去的线程而继续执行.

join实际就是判断join进来的线程存活状态,如果活着就调用wait(0),如果带超时时间了的话,wait里边的时间会算出来

while (isAlive()) {

wait(0);

}

API

  • public final void join() throws InterruptedException

  • public final synchronized void join(long millis, int nanos)

  • public final synchronized void join(long millis)

例子

public class Demo_02_06_1_join extends Thread {

@Override

public void run() {

for (int i = 0; i < 10; i++) {

System.out.println(this.getName() + " " + i);

}

}

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

Demo_02_06_1_join joinThread = new Demo_02_06_1_join();

for (int i = 0; i < 100; i++) {

if (i == 10) {

joinThread.start();

joinThread.join();

}

// 打到9就停了,然后执行joinThread这里边的代码,完事继续从10打

System.out.println(Thread.currentThread().getName()+" "+i);

}

}

}

sleep


睡觉方法,使得线程暂停一段时间,进入阻塞状态。

API

  • public static native void sleep(long millis) throws InterruptedException

  • public static void sleep(long millis, int nanos) throws InterruptedException

示例

public class Demo_02_06_2_sleep extends Thread {

@Override

public void run() {

for (int i = 0; i < 10; i++) {

if (i == 5) {

try {

Thread.sleep(5000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

// 输出到4停止, 5秒后继续

System.out.println(this.getName() + " " + i);

}

}

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

Demo_02_06_2_sleep sleepThread = new Demo_02_06_2_sleep();

sleepThread.start();

}

}

yield


也是让线程暂停一下,但是是进入就绪状态,让系统重新开始一次新的调度过程,下一次可能运气好被yield的线程又被选中。

Thread.yield()

中断

Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。

前面有一些方法声明了InterruptedException, 这意味者他们可以被中断,中断后把异常抛给调用方,让调用方自己处理.

被中断的线程可以自已处理中断,也可以不处理或者抛出去。

public class Demo_02_06_3_interrupt extends Thread {

static class MyCallable implements Callable {

@Override

public Integer call() throws InterruptedException {

for (int i = 0; i < 5000; i++) {

if (Thread.currentThread().isInterrupted()) {

System.out.println(“3333”);

throw new InterruptedException(“中断我干嘛,关注 微信号 大雄和你一起学编程 呀”);

}

}

return 0;

}

}

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

FutureTask futureTask = new FutureTask<>(new MyCallable());

Thread thread = new Thread(futureTask);

thread.start();

for (int i = 0; i < 100; i++) {

if (i == 3) {

thread.interrupt();

}

}

try {

futureTask.get();

} catch (ExecutionException e) {

// 这里会捕获到异常

e.printStackTrace();

}

}

最后

文章中涉及到的知识点我都已经整理成了资料,录制了视频供大家下载学习,诚意满满,希望可以帮助在这个行业发展的朋友,在论坛博客等地方少花些时间找资料,把有限的时间,真正花在学习上,所以我把这些资料,分享出来。相信对于已经工作和遇到技术瓶颈的朋友们,在这份资料中一定都有你需要的内容。

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值