Java:多线程:查看堆栈 / FutureTask / 非阻塞 / wait()&notify() / Executor

更多
2021年09月20日 08点47分 作者:叶飞 修改

当前线程

还可以查看线程中的堆栈信息:
StackTraceElement[] stacks = current.getStackTrace();
for (int i = 0; i < stacks.length; i++) {
	System.out.println(stacks[i]);
}

另外,在调试的时候,可以直接输出当前线程的堆栈信息:

Thread.dumpStack();


坑:run() vs start()

new一个线程对象时传入的是Runnable对象,Thread本身也是实现了Runnable的。

注意不要混淆:

  • run():只是在当前线程上运行
  • start():才会开启一个新的线程。所谓“开启”,并非立即执行,而是线程状态就绪(ready),等待CPU调度。

演示:

  • 源代码:native start0()
  • 当前线程id和Name
    Thread.currentThread().getName()


获取线程运行结果

Callable和FutureTask

runnable是没有返回值的。

如果我们希望获得新线程方法运行后的返回值,需要:

  • 给Thread()一个FutureTask的对象。演示:FutureTask已实现了Runnable
  • 而FutureTask对象的生成,需要一个Callable对象
Callable<Integer> getAge = ()-> 23;		
FutureTask<Integer> ft = new FutureTask<>(getAge);
new Thread(ft).start();    //不要忘了调用start()启动新线程
最后,调用get()方法获得结果:
System.out.println(ft.get());

CompletableFuture

上述get()方法是线程阻塞的,即:每次执行get()方法,都会让线程停下来等待运行结果。

for (i = 0; i < 10; i++) {
	Callable<Integer> getAge = ()-> {
		System.out.println(Thread.currentThread().getId() + "-" + i);
		return i;
	} ;	
	FutureTask<Integer> ft = new FutureTask<>(getAge);
	new Thread(ft).start();    //不要忘了调用start()启动新线程
	
	System.out.println(ft.get());	//阻塞	


演示:上述代码运行之后会形成类似于“同步”的结果

这样不利于发挥多线程异步并发的优势,所以Java 8开始,推出了非阻塞的CompletableFuture:

CompletableFuture<Integer> cf = CompletableFuture
		.supplyAsync(() -> {
			System.out.println(
					Thread.currentThread().getId() + "-" + i);
			return i;
		});

//cf运行完成之后会执行该lambda表达式
//r代表cf的运行结果
cf.thenAccept(r -> {
	System.out.println(Thread.currentThread().getId() + "*" + r);
});

System.out.println("-------------");
演示:没有阻塞出现,结果又“乱七八糟”了,^_^



继承Thread

我们可以声明一个类继承自Thread,重写run()方法

class greet extends Thread{
	@Override
	public void run() {

此后,调用该类实例对象的start()方法时,就会开启一个新线程,在新线程中运行其run()方法。

但我们不推荐这种方法,首先是有点怪异,其次是不能很方便的让线程返回一个值。

@想一想@:能不能这样?

在Thread子类里面:

public int age;

public void run() {
	age = 23;
然后,在外面这样调用:
greet r = new greet();
r.start();
System.out.println(r.age);
行的!因为多线程的异步执行。


线程同步

synchronized还可以直接修饰方法,之前的写法可以直接写成:

public synchronized void run() {
        i = 0;

虽然synchronized标注在方法前面,但锁住的仍然是当前对象。

方法还可以是静态的,比如:

public synchronized static void recover() {
这时候,锁住的是整个类,等同于:
public static void recover() {
	synchronized (Student.class) {

本质上,类锁还是由对象锁实现的(*.class仍然是一个对象)。且JVM中类对象始终只有一个,所以类锁会阻塞所有视图通过类来访问当前资源的线程。

但类锁不影响对象锁。

wait()和notify()

在Object中还定义了两个方法。但这两个方法不是任何类都可以调用的,

演示:运行报错:

必须在 synchronized 块中才能调用wait()和notify()方法。因为:

  • synchronized 块中才有锁
  • 调用wait(),会阻塞当前线程,释放线程锁,让其他/调用线程可以使用当前资源(继续执行);notify()是wait()的逆操作
public void run() {
	synchronized (this) { // 锁住了当前对象
		System.out.println("before wait");
		this.wait();
		System.out.println("after wait");
public synchronized void recover() {
	this.notify();
Student atai = new Student();
Thread thread = new Thread(atai);
thread.start();
System.out.println("--------");
//atai.recover();    
对比演示:假如不调用recover

对比sleep()和join():

  • sleep()不涉及锁,就是阻塞当前线程一段时间
  • join()通过调用线程的wait方法来达到同步的目的(演示:源代码,理解的关键是知道是谁在调用wait()方法


线程池

首先创建了一个固定长度为5的线程池

ExecutorService service = Executors.newFixedThreadPool(5);

然后就可以用service调用execute()方法:

for (i = 0; i < 10; i++) {
	service.execute(()->{
		System.out.println(Thread.currentThread().getId()+ ":" +i);
	});		
}

Executors是一个线程池工厂,提供了很多的工厂方法,可以创建的线程池包括:

  • newFixedThreadPool(int nThreads):固定数量的线程池,无空闲线程时任务在队列中等待
  • newCachedThreadPool():带缓存的线程池,线程池大小无限制 ,可以智能的添加新线程/回收部分空闲的线程
  • newScheduledThreadPool(int corePoolSize):线程池大小无限制,定时调度的线程池
  • newSingleThreadExecutor():单一线程的线程池
  • newWorkStealingPool():当某个线程的任务队列中没有可执行任务的时候,从其他线程的任务队列中窃取任务来执行(ForkJoinPool

演示:不同的线程池使用了不同的线程……

submit()

如果要执行的是有返回值的方法,就需要:

  • 调用submit()方法,
  • 传入Callable对象,
  • 拿到返回的Future对象
Future<Integer> task = service.submit(()->{
	System.out.println(Thread.currentThread().getId()+ ":" +i);
	return i;
});	
再调用task.get()方法,就能拿到运行结果。

invokeXXX()

一次性的、并发的使用多个线程,执行多个任务。

多个任务用Collection<? extends Callable<T>> tasks封装:

Collection<Callable<String>> tasks = new ArrayList<>();
tasks.add(()->"源栈欢迎你");
tasks.add(()->"大神小班,拎包入住");
invokeAll:执行所有方法,返回Future列表:
List<Future<String>> fs = service.invokeAll(tasks);
invokeAny:执行任意一个callable,返回其结果:
String any = service.invokeAny(tasks);


作业

按以下要求完成J&C:多线程中的作业:
  1. 尝试非阻塞的读取newEmail.txt文件(参考:Java NIO)关键字:AsynchronousFileChannel / CompletionHandler等
  2. 尽可能的练习所学知识,比如:
    1. 根据具体情况,分别使用lambda、实现Runnable或继承Thread自定义类构建新开线程
    2. 既有手动的开线程,也可以使用线程池


java 多线程
赞: 0 踩: 0

打赏
已收到打赏的 帮帮币

你的 打赏 非常重要!
为了保证文章的质量,每一篇文章的发布,都已经消耗了作者 1 枚 帮帮币
没有“帮帮币”,作者无法发布新的文章。

全系列阅读
评论 / 0

后台开发


其他:WebForm和WebApi

其他ASP.NET框架,如WebForm、WebApi……

RazorPages(Core)

微软推荐的、最新的、基于Razor页面和.NET core的新一代Web项目开发技术,包括Razor Tag Helper、Model绑定和Validation、Session/Cookie、内置依赖注入等……

MVC(Framework)

过去两年间最流行的、基于.NET Framework和MVC模式的ASP.NET MVC框架,主要用于讲解安全、性能、架构和各种实战功能演示……

C#语法

从入门的变量赋值、分支循环、到面向对象,以及更先进的语言特性,如:泛型、Lambda、Linq、异步方法等…………

Java语法

面向过程的变量赋值、分支循环和函数封装;面向对象的封装、继承和多态;以及更高阶的常用类库(集合/IO/多线程……)、lambda等

Java Web开发

分层架构和综合实战

J&C

Java和C#共有的语法

全部
关键字



帮助

反馈