Java线程-01

Java 8

Eclipse Version: 2021-03 (4.19.0)

---

 

目录

概述

程序1、创建线程-继承Thread

程序2、多线程-修改静态变量

程序3、synchronized关键字

程序4、Callable接口

程序5、FutureTask类

程序6、线程池

示例程序1:Executors.newSingleThreadExecutor()

示例程序2:Executors.newFixedThreadPool(int)

示例程序3:ThreadPoolExecutor

示例程序4:ForkJoinPool

程序7、wait、notify

程序8、ThreadLocal类

参考文档

 

概述

三种实现:

1、Runnable接口

2、Thread类

3、Callable接口 & FutureTask类

关系:

 

线程池:

AbstractExecutorService、ExecutorService、Executor、Executors

 

interface Lock 及其 实现类:

 

程序1、创建线程-继承Thread

java.lang.Thread的构造函数:

public class Test073101 {

	public static void main(String[] args) throws InterruptedException {
		Thread th0 = new ThreadSync();
		th0.start();
		System.out.println(Thread.currentThread().getThreadGroup().activeCount());
		
		System.out.println("inum=" + ThreadSync.inum);
	}

}

class ThreadSync extends Thread {

	public static int inum;
	
	@Override
    public void run() {
		addInum();
	}
	
	public static void addInum() {
		for (int i=0; i<10000; i++) {
			inum++;
			System.out.println("i" + i + ", inum=" + inum + ", th=" + Thread.currentThread().getName());
		}
	}
	
}

期待 main函数中的 inum=10000,结果却是:

2
inum=1
i0, inum=1, th=Thread-0
i1, inum=2, th=Thread-0
i2, inum=3, th=Thread-0
...
i2, inum=10000, th=Thread-0

说明,其实,去掉main函数的 System.out.println(Thread.currentThread().getThreadGroup().activeCount()); 一句后,输出的是 inum=0,打印语句 也会对结果产出影响的——执行时间。

 

原因:启动线程th0(start) 后,主线程继续执行其后面的语句,比子线程提前结束。

解决:使用join()函数——来自Thread类

在th0启动后,执行 th0.join():

		// ...略
        th0.join();
		
		System.out.println("inum=" + ThreadSync.inum);
	}

}

更改后的输出结果:

...
i9997, inum=9998, th=Thread-0
i9998, inum=9999, th=Thread-0
i9999, inum=10000, th=Thread-0
inum=10000

 符合预期。

 

说明:join()函数

位于Thread类中。

# 等待线程终止
Waits for this thread to die.
# 维持顺序流执行——实际上不是
An invocation of this method behaves in exactly the same
way as the invocation

更多join()函数的前面:

// 一直等
public final void join() throws InterruptedException;
// 限定等待多少毫秒
public final synchronized void join(final long millis) throws InterruptedException;
// 限定等待多少毫秒+多少纳秒
public final synchronized void join(long millis, int nanos) throws InterruptedException;

 

程序2、多线程-修改静态变量

在上面的示例中,使用一个线程修改静态变量inum。本节增加一个线程,一起来修改inum。

示例代码:

public class Test073101 {

	public static void main(String[] args) throws InterruptedException {
		Thread th0 = new ThreadSync();
		Thread th1 = new ThreadSync();
		
		th0.start();
		System.out.println(Thread.currentThread().getThreadGroup().activeCount());
		th1.start();
		System.out.println("started...");
		
		th0.join();
		th1.join();
		
		System.out.println("inum=" + ThreadSync.inum);
	}

}

class ThreadSync extends Thread {

	public static int inum;
	
	@Override
    public void run() {
		addInum();
	}
	
	public static void addInum() {
		for (int i=0; i<10000; i++) {
			inum++;
			System.out.println("i" + i + ", inum=" + inum + ", th=" + Thread.currentThread().getName());
		}
	}
	
}

输出结果:

2
i0, inum=1, th=Thread-0
started...
i1, inum=2, th=Thread-0
i0, inum=4, th=Thread-1
i1, inum=5, th=Thread-1
...
i9998, inum=19999, th=Thread-0
i9999, inum=20000, th=Thread-0
inum=20000

线程1、2交替执行,符合预期(?)

 

疑问

一直听说 int、Integer 的 ++、-- 操作不是原子性的,多线程修改时结果应该不符合预期。

为何在我的程序里面是“符合预期”的呢?

检查代码发现,addInum 中有一条打印语句!删除后再执行:

	public static void addInum() {
		for (int i=0; i<10000; i++) {
			inum++;
		}
	}

执行结果:

2
started...
inum=20000

还是符合预期!检查发现 main函数中 还有几条打印语句,全删掉测试(或者增加 10 000 到 100 000 也会出现 不符合预期的情况):

	public static void main(String[] args) throws InterruptedException {
		Thread th0 = new ThreadSync();
		Thread th1 = new ThreadSync();
		
		th0.start();
		th1.start();
		
		th0.join();
		th1.join();
		
		System.out.println("inum=" + ThreadSync.inum);
	}

执行结果:

# 第一次
inum=11914

# 第二次
inum=11975

不符合预期,没有等于20000!这才对嘛!

注:可见,打印语句对程序运行的影响还是挺大的,多线程操作时更是如此。那么,一条打印语句要花多少时间呢?

 

怎么解决呢?1、synchronized,2、更改int为AtomicInteger

解决1:synchronized

把 addInum() 函数改为 synchronized 的:

	public static synchronized void addInum() {
		for (int i=0; i<10000; i++) {
			inum++;
		}
	}

执行结果:符合预期

inum=20000

 

解决2:更改int为AtomicInteger

class ThreadSync extends Thread {

	// 此时的inum必须初始化,不能像int一样 有默认值
	public static AtomicInteger inum = new AtomicInteger();
	
	@Override
    public void run() {
		addInum();
	}
	
	public static void addInum() {
		for (int i=0; i<10000; i++) {
        	// 以下四个函数效果相同
//			inum.incrementAndGet();
//			inum.getAndIncrement();
			inum.addAndGet(1);
//			inum.getAndAdd(1);
		}
	}
	
}

执行结果:符合预期

inum=20000

 

注:除了解决方法1、2,另外就是使用 锁(Lock)了。

 

程序3、synchronized关键字

上面我们把关键字写到 方法签名 上了,同步到是整个静态方法。其实,可以把synchronized放到 inum++ 上,如下:

	public static void addInum() {
		for (int i=0; i<10000; i++) {
			synchronized(ThreadSync.class) {
				inum++;
			}
		}
	}

注意,这里是 synchronized(ThreadSync.class),而不是 单独的synchronized 或者 synchronized(this)!因为这个方法是静态方法,同步只能是类级别的!

synchronized(this) 是在 非静态方法 的同步代码块使用。

 

下面展示 synchronized 用于非静态方法:

package other;

public class Test073102 {

	public static void main(String[] args) throws InterruptedException {
		Salary sal = new Salary();
		sal.setMoney(0);
		
		// 1、实现Runnable接口
		Thread th0 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i=0; i<100_000; i++) {
					sal.addMoney();
				}
			}
		});
		
		// 2、lambda方式实现Runnable接口
		Thread th1 = new Thread(()->{
			for (int i=0; i<100_000; i++) {
				sal.addMoney();
			}
		});
		
		th0.start();
		th1.start();
		
		th0.join();
		th1.join();
		
		System.out.println(sal);
	}

}

class Salary {
	
	private int money;
	
	public synchronized void addMoney() {
		this.money += 1;
	}

	@Override
	public String toString() {
		return "Salary [money=" + money + "]";
	}

	public int getMoney() {
		return money;
	}

	public void setMoney(int money) {
		this.money = money;
	}
	
}

输出结果:

Salary [money=200000]

 

synchronized 用在 非静态方法的 代码块上:

	public void addMoney() {
		synchronized(this) {
			this.money += 1;
		}
	}

 

在 非静态方法 的 代码块中,可以使用 synchronized 锁定整个类对象,如下:

	public void addMoney() {
//		synchronized(this) {
		synchronized(Salary.class) {
			this.money += 1;
		}
	}

 

来看下面的示例:在Salary中添加 静态变量 gvar

class Salary {
	
	public static int gvar = 0;
	
	private int money;
	
	public void addMoney() {
		synchronized(this) {
			this.money += 1;
		}
		
		gvar = gvar + 1;
	}

main函数打印gvar的值:

		th0.join();
		th1.join();
		
		System.out.println(sal);
		System.out.println("gvar=" + Salary.gvar);
	}

执行结果:

Salary [money=200000]
gvar=199187

gvar的值会一直变化,但不会是期待的 200 000。

此时,就可以使用synchronized(Salary.class) 来解决问题了:

class Salary {
	
	public static int gvar = 0;
	
	private int money;
	
	public void addMoney() {
		synchronized(this) {
			this.money += 1;
		}

		synchronized(Salary.class) {
			gvar = gvar + 1;
		}
	}

输出:满足期待。

Salary [money=200000]
gvar=200000

 

synchronized还有 调用同步方法 不影响调用 非同步方法,看 参考文档1,可以获取更多内容。

 

程序4、Callable接口

有返回值的线程,需要配合 FutureTask使用,因为Callable只是一个函数式接口。

Callable对象 --> FutureTask对象 --> Thread对象,再start。

package java.util.concurrent;

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

示例程序:

public class Test073103 {

	public static void main(String[] args) {
		CallableWay call = new CallableWay();
		FutureTask<Integer> ft = new FutureTask<Integer>(call);
		
		// 把 FutureTask 放入 Thread,再执行
		Thread th = new Thread(ft);
		th.start();
		
		try {
			System.out.println("执行ft.get():");
			Integer result = ft.get();
			System.out.println("result=" + result);
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
	}

}

class CallableWay implements Callable<Integer> {

	@Override
	public Integer call() throws Exception {
		int result = 0;
		for (int i=0; i<10; i++) {
			Thread.sleep(500);
			System.out.println("线程:" + Thread.currentThread().getName() + ", i=" + i);
			result = i;
		}
		return result;
	}
	
}

 

上面的 ft不放入Thread,在调用 ft.get()后,程序一直阻塞。

 

一个ft建立多个线程:修改上面的main函数,实现把一个ft装到多个Thread对象中。

测试结果显示,想要建立6个线程,结果只有一个线程在执行:

	public static void main(String[] args) {
		CallableWay call = new CallableWay();
		FutureTask<Integer> ft = new FutureTask<Integer>(call);
		
		List<Thread> thlist = new ArrayList<>();
		for (int i=0; i<6; i++) {
			// 把 FutureTask 放入 Thread,再执行
			Thread th = new Thread(ft, "thread" + i);
			th.start();
			
			thlist.add(th);
		}
		System.out.println("thlist.size()=" + thlist.size());
		System.out.println(Thread.currentThread().getThreadGroup().activeCount());
		
		try {
			System.out.println("执行ft.get():");
			Integer result = ft.get();
			System.out.println("result=" + result);
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
	}

看来这个用法是错误的!

 

改正后的用法:

	public static void main(String[] args) {
		CallableWay call = new CallableWay();
		
		List<Thread> thlist = new ArrayList<>();
		List<FutureTask<Integer>> ftlist = new ArrayList<>();
		for (int i=0; i<6; i++) {
			FutureTask<Integer> ft = new FutureTask<Integer>(call);
			ftlist.add(ft);
			
			// 把 FutureTask 放入 Thread,再执行
			Thread th = new Thread(ft, "thread" + i);
			th.start();
			
			thlist.add(th);
		}
		System.out.println("thlist.size()=" + thlist.size());
		System.out.println(Thread.currentThread().getThreadGroup().activeCount());
		
		System.out.println("执行ft.get():");
		ftlist.forEach(ft->{
			try {
				System.out.println("ft.get=" + ft.get());
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace();
			}
		});
	}

这次输出结果正常了:加上main线程,总共7个线程在跑;最后自己建的6个线程都返回了结果。

测试结果
thlist.size()=6
7
执行ft.get():
线程:thread0, i=0
线程:thread2, i=0
线程:thread4, i=0
...
线程:thread3, i=8
线程:thread5, i=9
线程:thread2, i=9
线程:thread1, i=9
线程:thread0, i=9
ft.get=9
ft.get=9
ft.get=9
线程:thread4, i=9
线程:thread3, i=9
ft.get=9
ft.get=9
ft.get=9

 

看来,

一个callable对象,可以给 多个 FutureTask对象 使用;

一个FutureTask对象,只能给一个 Thread对象使用。

 

Callable是一个函数式接口,因此,可以用 lambda表达式 来建立 Callable对象。

FutureTask<Integer> ft = new FutureTask<Integer>(()->{
	int result = 0;
	for (int j=0; j<10; j++) {
		Thread.sleep(500);
		System.out.println("线程:" + Thread.currentThread().getName() + ", j=" + j);
		result = j;
	}
	return result;
});

 

程序5、FutureTask类

java.util.concurrent.FutureTask类 实现了 RunnableFuture 接口,而后者 继承了 Runnable接口,这才可以放到 Thread构造函数中 建立线程。

其构造函数除了使用 Callable外,还可以直接使用 Runnable。

public class FutureTask<V> implements RunnableFuture<V> {...}
public interface RunnableFuture<V> extends Runnable, Future<V> {...}

// 构造函数
public FutureTask(Runnable runnable, V result);
public FutureTask(Callable<V> callable);

 

上面介绍了 Callable接口的使用,本节介绍 Runnable + FutureTask类 的使用——传入对象,修改对象。

程序如下:

public class Test080101 {

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 1、基本类型
		Integer rt1 = 100;
		Runnable r1 = ()->{
			// 怎么改变rt1呢?
			// 无法改变
		};
		FutureTask<Integer> ft1 = new FutureTask<>(r1, rt1);
		new Thread(ft1).start();
		
		Integer grt1 = ft1.get();
		System.out.println("grt1=" + grt1);
		System.out.println();
		
		// 2、自定义类型
		User rt2 = new User("tom");
		Runnable r2 = ()->{
			// 怎么改变rt2呢?
			// 像下面一样 更改对象的属性
			rt2.setName("Jack");
			
			// 更改前后可以处理一些其它业务
		};
		System.out.println("改变前 rt2=" + rt2);
		FutureTask<User> ft2 = new FutureTask<>(r2, rt2);
		new Thread(ft2).start();
		
		User grt2 = ft2.get();
		System.out.println("改变后 grt2=" + grt2);
	}

}

class User {
	
	private String name;
	
	public User(String name) {
		this.name = name;
	}
    
    // getter, setter, toString
	
}

测试结果:

grt1=100

rt2=User [name=tom]
grt2=User [name=Jack]

 

更多内容,可以看源码:在源码中,修改传入的对象 是在 Runnable的方法run()中执行的,因此,包装器类型是无法更改的(有什么方法更改呢?)

 

总结:

使用Callable做参数,无法指定要修改的对象,只是执行一次任务,任务执行完毕,返回一个结果;

使用Runnable+result做参数,可以修改result的值,但是,最后FutureTask对象get到的仍然是这个对象,这意味着,无法修改对象地址,但可以修改对象属性。

 

程序6、线程池

quote:线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

下面的介绍来自 参考文档2

任务的提交、任务的执行:分离

任务提交者,关注任务本身,如 提交任务、获取结果、取消任务,而不需要关注任务执行的细节,如 线程创建、任务调度、线程关闭等。

前面提到的Runnable、Callable 表示 要执行的异步任务。

java.util.concurrent.Executor、java.util.concurrent.ExecutorService 表示 执行服务。

java.util.concurrent.Future<V> 表示 异步任务的结果,前面的 FutureTask 便实现了此接口。

 

注:executor n.  执行者; 实行者; 遗嘱执行人;

 

ExecutorService接口 继承了 Executor接口,AbstractExecutorService抽象类 实现了 ExecutorService接口,而其它 线程池类 则继承了 AbstractExecutorService抽象类。

要实现自己的 线程池类,继承 AbstractExecutorService抽象类 即可。

JDK提供了一些默认的线程池,通过 工具类 java.util.concurrent.Executors 提供,通常,使用这个工具类的方法来建立 我们需要的线程池即可。

Executors提供的一些公共方法如下:

注:其中还涉及到 ThreadFactory接口,不过,Executors工具类提供了默认的实现。

private static class DefaultThreadFactory implements ThreadFactory {...}

 

再看下 ExecutorService接口的public函数:其中的submit函数是最常用到的

 

示例程序1:Executors.newSingleThreadExecutor()

单线程线程池,线程池中只有一个 线程存在,每次处理一个任务。按顺序处理 任务 吗?

	public static void main(String[] args) {
		ExecutorService es1 = Executors.newSingleThreadExecutor();
		Future<?> f1 = es1.submit(()->{
			System.out.println("thread 1=" + Thread.currentThread().getName());
		});
		System.out.println("f1=" + f1);
		try {
			System.out.println("f1.get()=" + f1.get());
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
		
		System.out.println("---end---");
	}

输出结果:

f1=java.util.concurrent.FutureTask@27abe2cd[Not completed, task = 
java.util.concurrent.Executors$RunnableAdapter@5ecddf8f[Wrapped task = 
Test080102$$Lambda$1/0x0000000100060240@3f102e87]]
thread 1=pool-1-thread-1
f1.get()=null
---end---

注意,虽然上面代码的程序执行完了,但是,整个程序却没有执行完毕——程序没有终止,因为,线程池没有被关闭?

添加下面的语句即可关闭线程池,并终止程序:

es1.shutdown();

 

改造程序1:主线程 是否等待线程池中线程执行

	public static void main(String[] args) {
		ExecutorService es1 = Executors.newSingleThreadExecutor();
		Future<?> f1 = es1.submit(()->{
			System.out.println("thread 1=" + Thread.currentThread().getName());
			try {
				// sleep 4秒
				Thread.sleep(4000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("thread 1=end");
		});
//		System.out.println("f1=" + f1);
//		try {
//			System.out.println("f1.get()=" + f1.get());
//		} catch (InterruptedException | ExecutionException e) {
//			e.printStackTrace();
//		}
		
		es1.shutdown();
		
		System.out.println("---end---");
	}

执行结果:

---end---
thread 1=pool-1-thread-1
// 等待了4秒
thread 1=end

没有执行 f1.get(),主线程 不需要等待,直接执行完毕。

但是,不需要和之前一样 实现 线程的join()函数了,主线程还是会等待 线程池中的线程执行完毕。

注:线程name中以 pool开头。

 

注意,上面主线程中执行了 es1.shutdown();,但是,为什么线程池中的线程还是可以执行呢?来看看 shutdown() 函数的说明:

原来是要 等待之前提交的线程执行完毕,但是,不能提交新线程。

要是更换为 另一个函数 shutdownNow(),那么,线程池中的线程就都不会执行了。

	public static void main(String[] args) {
		ExecutorService es1 = Executors.newSingleThreadExecutor();
		Future<?> f1 = es1.submit(()->{
			System.out.println("thread 1=" + Thread.currentThread().getName());
			try {
				// sleep 4秒
				Thread.sleep(4000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("thread 1=end");
		});
//		System.out.println("f1=" + f1);
//		try {
//			System.out.println("f1.get()=" + f1.get());
//		} catch (InterruptedException | ExecutionException e) {
//			e.printStackTrace();
//		}
		
		List<Runnable> rlist = es1.shutdownNow();
		System.out.println("rlist=" + rlist);
		System.out.println("rlist.size=" + (rlist == null ? 0 : rlist.size()));
		
		System.out.println("---end---");
	}

执行结果:

发生了异常,线程被中断了,异常发生后,已提交线程的“thread 1=end”立即就输出了。

 

疑问

中断会导致线程怎样异常运行呢?

 

改造程序2:提交多个线程执行

会是顺序执行吗?

	public static void main(String[] args) {
		ExecutorService es1 = Executors.newSingleThreadExecutor();
		System.out.println("es1=" + es1);
		
		// 提交5个线程
		for (int i=0; i<5; i++) {
			final int cnt = i;
			Future<?> f1 = es1.submit(()->{
				System.out.println("thread start cnt=" + cnt + ", name=" + Thread.currentThread().getName());
				try {
					// sleep 4秒
					Thread.sleep(4000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("thread start cnt=" + cnt + ", name=" + Thread.currentThread().getName());
			});
		}
		
		System.out.println("es1=" + es1);
		
		es1.shutdown();

		System.out.println("---end---");
	}

执行结果;

执行结果
es1=java.util.concurrent.Executors$FinalizableDelegatedExecutorService@782830e
es1=java.util.concurrent.Executors$FinalizableDelegatedExecutorService@782830e
---end---
thread start cnt=0, name=pool-1-thread-1
thread start cnt=0, name=pool-1-thread-1
thread start cnt=1, name=pool-1-thread-1
thread start cnt=1, name=pool-1-thread-1
thread start cnt=2, name=pool-1-thread-1
thread start cnt=2, name=pool-1-thread-1
thread start cnt=3, name=pool-1-thread-1
thread start cnt=3, name=pool-1-thread-1
thread start cnt=4, name=pool-1-thread-1
thread start cnt=4, name=pool-1-thread-1

依次提交5个线程,结果按照提交顺序执行了(注意cnt的值)。

 

疑问

1、线程名称(前缀)怎么更改?

2、怎么获取线程池的状态信息呢?活跃线程数、排队线程数等。

 

newSingleThreadExecutor() 函数源码:实际上包装了一个 ThreadPoolExecutor,其核心线程、最大线程数量都是1。

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

 

示例程序2:Executors.newFixedThreadPool(int)

固定线程数线程池。

改造上面的代码:更改线程池-数量3、更改条线程数为8

	public static void main(String[] args) {
        // 改1
//		ExecutorService es1 = Executors.newSingleThreadExecutor();
		ExecutorService es1 = Executors.newFixedThreadPool(3);
		System.out.println("es1=" + es1);
		
		// 改2:提交8个线程
		for (int i=0; i<8; i++) {
			final int cnt = i;
            // ...

执行结果:

执行结果
es1=java.util.concurrent.ThreadPoolExecutor@69222c14[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
es1=java.util.concurrent.ThreadPoolExecutor@69222c14[Running, pool size = 3, active threads = 2, queued tasks = 5, completed tasks = 0]
---end---
thread start cnt=0, name=pool-1-thread-1
thread start cnt=1, name=pool-1-thread-2
thread start cnt=2, name=pool-1-thread-3
thread start cnt=2, name=pool-1-thread-3
thread start cnt=0, name=pool-1-thread-1
thread start cnt=1, name=pool-1-thread-2
thread start cnt=3, name=pool-1-thread-1
thread start cnt=4, name=pool-1-thread-2
thread start cnt=5, name=pool-1-thread-3
thread start cnt=3, name=pool-1-thread-1
thread start cnt=6, name=pool-1-thread-1
thread start cnt=5, name=pool-1-thread-3
thread start cnt=4, name=pool-1-thread-2
thread start cnt=7, name=pool-1-thread-3
thread start cnt=7, name=pool-1-thread-3
thread start cnt=6, name=pool-1-thread-1

 

执行结果有两个变化:

1、es1 输出了 更多内容

现实了 线程池数量、活跃线程数、排队数量;

其类型为 ThreadPoolExecutor——之前的不是如此;

2、线程池总共有3个线程,来回执行提交的任务,真正的多线程程序。

 

newFixedThreadPool(int) 函数源码:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

 

新建了一个  ThreadPoolExecutor 对象并返回。

这样的话,上面两个线程池 都是 基于 ThreadPoolExecutor 建造的。

java.util.concurrent.ThreadPoolExecutor 继承了 AbstractExecutorService抽象类,有4个构造函数:

参数最完整的一个构造函数的签名如下:

    public ThreadPoolExecutor(int corePoolSize, /** 核心线程数 */
                              int maximumPoolSize, /** 最大线程数 */
                              long keepAliveTime, /** 线程数大于核心线程数时,空闲线程等待新任务的最大时间,否则回收 */
                              TimeUnit unit, /** keepAliveTime的时间单位 */
                              BlockingQueue<Runnable> workQueue, /** 等待队列,默认无限大 */
                              ThreadFactory threadFactory, /** 线程创建工厂 */
                              RejectedExecutionHandler handler  /** 拒绝策略:没法提交线程时的策略,很重要 */
);

 

newCachedThreadPool()的执行情况:

执行结果
// ExecutorService es1 = Executors.newCachedThreadPool();

es1=java.util.concurrent.ThreadPoolExecutor@16f65612[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
thread start cnt=0, name=pool-1-thread-1
thread start cnt=4, name=pool-1-thread-5
thread start cnt=2, name=pool-1-thread-3
thread start cnt=5, name=pool-1-thread-6
thread start cnt=7, name=pool-1-thread-8
thread start cnt=1, name=pool-1-thread-2
thread start cnt=3, name=pool-1-thread-4
es1=java.util.concurrent.ThreadPoolExecutor@16f65612[Running, pool size = 8, active threads = 8, queued tasks = 0, completed tasks = 0]
thread start cnt=6, name=pool-1-thread-7
---end---
thread start cnt=4, name=pool-1-thread-5
thread start cnt=1, name=pool-1-thread-2
thread start cnt=2, name=pool-1-thread-3
thread start cnt=0, name=pool-1-thread-1
thread start cnt=7, name=pool-1-thread-8
thread start cnt=5, name=pool-1-thread-6
thread start cnt=6, name=pool-1-thread-7
thread start cnt=3, name=pool-1-thread-4

 

提交后,立即建立线程,无需等待。其实质是一个 ThreadPoolExecutor:核心线程数为0、最大线程数为无限大(Integer.MAX_VALUE)。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

 

空闲线程 等待任务的时间为 60秒——太大了。

适合快速处理大量耗时较短的任务。

如果是耗时任务,那么,一直不断提交的话,有可能出现OOM异常。

 

小结:

有两个常用的线程池实现类:ThreadPoolExecutor、ForkJoinPool,前者使用频率更高,两者都继承了 AbstractExecutorService 抽象类。

Executors的创建线程池的方法(newXXX)简单对比:

创建方法 返回值 依赖的基础类 备注
newCachedThreadPool ExecutorService ThreadPoolExecutor public class ThreadPoolExecutor extends AbstractExecutorService
newFixedThreadPool ExecutorService ThreadPoolExecutor  
newSingleThreadExecutor ExecutorService

FinalizableDelegatedExecutorService(

ThreadPoolExecutor)

 
newScheduledThreadPool(int) ScheduledExecutorService ScheduledThreadPoolExecutor

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService

 

ScheduledExecutorService接口 多了一些 scheduleXXX方法

newSingleThreadScheduledExecutor ScheduledExecutorService

DelegatedScheduledExecutorService(

ScheduledThreadPoolExecutor)

 

newWorkStealingPool ExecutorService ForkJoinPool public class ForkJoinPool extends AbstractExecutorService

说明:

返回 ExecutorService 的,提交任务使用 submit;

返回 ScheduledExecutorService,提交任务使用 schedule(当然,也可以使用 submit,但体现不出 调度的作用);

关于 ForkJoinPool:

引用:
Java 7 引入了一种新的并发框架—— Fork/Join Framework。同时引入了一种新的线程池:ForkJoinPool(ForkJoinPool.coomonPool)。

1、ForkJoinPool 不是为了替代 ExecutorService,而是它的补充,在某些应用场景下性能比 ExecutorService 更好。
2、ForkJoinPool 主要用于实现“分而治之”的算法,特别是分治之后递归调用的函数,例如 quick sort 等。
3、ForkJoinPool 最适合的是计算密集型的任务,如果存在 I/O,线程间同步,sleep() 等会造成线程长时间阻塞的情况时,
最好配合使用 ManagedBlocker。

 

示例程序3:ThreadPoolExecutor

- 无限队列

	public static void main(String[] args) {
		ThreadPoolExecutor es1 = new ThreadPoolExecutor(2, 4, 
				10, TimeUnit.SECONDS, 
				new LinkedBlockingDeque<Runnable>(),
				new ThreadPoolExecutor.AbortPolicy());
		
		System.out.println("es1=" + es1);
		
		// 提交N个线程
		for (int i=0; i<8; i++) {
			final int cnt = i;
			System.out.println("before submit..." + i + ", now=" + (new Date()));
			Future<?> f1 = es1.submit(()->{
				System.out.println("thread start cnt=" + cnt + ", name=" + Thread.currentThread().getName()
						+ ", now=" + (new Date()));
				try {
					// sleep 4秒
					Thread.sleep(4000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("thread end cnt=" + cnt + ", name=" + Thread.currentThread().getName()
						+ ", now=" + (new Date()));
			});
			System.out.println("after submit..." + i + ", now=" + (new Date()));
		}
		
		System.out.println("---end---");
		
		while(true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("main es1=" + es1 + ", now=" + (new Date()));
		}
	}

注:上面的打印语句“thread end cnt=” 最开始写错成 “thread start cnt”了,后面的一些 执行结果 未更正,请注意。

 

执行结果:

执行结果
es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
before submit...0, now=Sun Aug 01 16:21:37 CST 2021
after submit...0, now=Sun Aug 01 16:21:37 CST 2021
before submit...1, now=Sun Aug 01 16:21:37 CST 2021
after submit...1, now=Sun Aug 01 16:21:37 CST 2021
before submit...2, now=Sun Aug 01 16:21:37 CST 2021
thread start cnt=1, name=pool-1-thread-2, now=Sun Aug 01 16:21:37 CST 2021
after submit...2, now=Sun Aug 01 16:21:37 CST 2021
before submit...3, now=Sun Aug 01 16:21:37 CST 2021
thread start cnt=0, name=pool-1-thread-1, now=Sun Aug 01 16:21:37 CST 2021
after submit...3, now=Sun Aug 01 16:21:37 CST 2021
before submit...4, now=Sun Aug 01 16:21:37 CST 2021
after submit...4, now=Sun Aug 01 16:21:37 CST 2021
before submit...5, now=Sun Aug 01 16:21:37 CST 2021
after submit...5, now=Sun Aug 01 16:21:37 CST 2021
before submit...6, now=Sun Aug 01 16:21:37 CST 2021
after submit...6, now=Sun Aug 01 16:21:37 CST 2021
before submit...7, now=Sun Aug 01 16:21:37 CST 2021
after submit...7, now=Sun Aug 01 16:21:37 CST 2021
---end---
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 6, completed tasks = 0], now=Sun Aug 01 16:21:38 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 6, completed tasks = 0], now=Sun Aug 01 16:21:39 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 6, completed tasks = 0], now=Sun Aug 01 16:21:40 CST 2021
thread start cnt=0, name=pool-1-thread-1, now=Sun Aug 01 16:21:41 CST 2021
thread start cnt=1, name=pool-1-thread-2, now=Sun Aug 01 16:21:41 CST 2021
thread start cnt=2, name=pool-1-thread-1, now=Sun Aug 01 16:21:41 CST 2021
thread start cnt=3, name=pool-1-thread-2, now=Sun Aug 01 16:21:41 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 4, completed tasks = 2], now=Sun Aug 01 16:21:41 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 4, completed tasks = 2], now=Sun Aug 01 16:21:42 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 4, completed tasks = 2], now=Sun Aug 01 16:21:43 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 4, completed tasks = 2], now=Sun Aug 01 16:21:44 CST 2021
thread start cnt=3, name=pool-1-thread-2, now=Sun Aug 01 16:21:45 CST 2021
thread start cnt=2, name=pool-1-thread-1, now=Sun Aug 01 16:21:45 CST 2021
thread start cnt=4, name=pool-1-thread-2, now=Sun Aug 01 16:21:45 CST 2021
thread start cnt=5, name=pool-1-thread-1, now=Sun Aug 01 16:21:45 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 4], now=Sun Aug 01 16:21:45 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 4], now=Sun Aug 01 16:21:46 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 4], now=Sun Aug 01 16:21:47 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 4], now=Sun Aug 01 16:21:48 CST 2021
thread start cnt=4, name=pool-1-thread-2, now=Sun Aug 01 16:21:49 CST 2021
thread start cnt=5, name=pool-1-thread-1, now=Sun Aug 01 16:21:49 CST 2021
thread start cnt=6, name=pool-1-thread-2, now=Sun Aug 01 16:21:49 CST 2021
thread start cnt=7, name=pool-1-thread-1, now=Sun Aug 01 16:21:49 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:21:49 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:21:50 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:21:51 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:21:52 CST 2021
thread start cnt=7, name=pool-1-thread-1, now=Sun Aug 01 16:21:53 CST 2021
thread start cnt=6, name=pool-1-thread-2, now=Sun Aug 01 16:21:53 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 8], now=Sun Aug 01 16:21:53 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 8], now=Sun Aug 01 16:21:54 CST 2021

 

结果分析:

没有发生异常;提交了8个任务,虽然最大线程数是4,但是,线程池中最多只有2个线程——原因是 排队队列是 无限的;可以看到线程池的 queued tasks 从6到0变化,而active threads最多久是2(参考文档5)。

排队队列满了 后,才会根据 maximumPoolSize 建立新的线程;队列无限的话,线程池 就永远不会建立 超过 核心线程数的线程

maximumPoolSize 的值 不能小于 corePoolSize,必须 大于等于,否则程序启动不了。

 

- 定长队列长度

将排队长度设置为2——最多排队2个,多的就 新建线程——maximumPoolSize减去corePoolSize,否则,按照 RejectedExecutionHandler 的设置 抛出异常。

		ThreadPoolExecutor es1 = new ThreadPoolExecutor(2, 4, 
				10, TimeUnit.SECONDS, 
				new LinkedBlockingDeque<Runnable>(2), // 排队队列长度为2
				new ThreadPoolExecutor.AbortPolicy());

队列长度2 + maximumPoolSize 4 = 6,而提交了 8个线程

执行结果:

执行结果
es1=java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
before submit...0, now=Sun Aug 01 16:31:22 CST 2021
after submit...0, now=Sun Aug 01 16:31:22 CST 2021
before submit...1, now=Sun Aug 01 16:31:22 CST 2021
after submit...1, now=Sun Aug 01 16:31:22 CST 2021
before submit...2, now=Sun Aug 01 16:31:22 CST 2021
after submit...2, now=Sun Aug 01 16:31:22 CST 2021
thread start cnt=0, name=pool-1-thread-1, now=Sun Aug 01 16:31:22 CST 2021
thread start cnt=1, name=pool-1-thread-2, now=Sun Aug 01 16:31:22 CST 2021
before submit...3, now=Sun Aug 01 16:31:22 CST 2021
after submit...3, now=Sun Aug 01 16:31:22 CST 2021
before submit...4, now=Sun Aug 01 16:31:22 CST 2021
after submit...4, now=Sun Aug 01 16:31:22 CST 2021
before submit...5, now=Sun Aug 01 16:31:22 CST 2021
thread start cnt=4, name=pool-1-thread-3, now=Sun Aug 01 16:31:22 CST 2021
after submit...5, now=Sun Aug 01 16:31:22 CST 2021
before submit...6, now=Sun Aug 01 16:31:22 CST 2021
thread start cnt=5, name=pool-1-thread-4, now=Sun Aug 01 16:31:22 CST 2021
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@43a25848[Not completed, task = java.util.concurrent.Executors$RunnableAdapter@49e4cb85[Wrapped task = other.Test80103$$Lambda$6/0x0000000100060240@2133c8f8]] rejected from java.util.concurrent.ThreadPoolExecutor@470e2030[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0]
	at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2057)
	at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:827)
	at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1357)
	at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:118)
	at other.Test80103.main(Test80103.java:22)
thread start cnt=0, name=pool-1-thread-1, now=Sun Aug 01 16:31:26 CST 2021
thread start cnt=2, name=pool-1-thread-1, now=Sun Aug 01 16:31:26 CST 2021
thread start cnt=1, name=pool-1-thread-2, now=Sun Aug 01 16:31:26 CST 2021
thread start cnt=3, name=pool-1-thread-2, now=Sun Aug 01 16:31:26 CST 2021
thread start cnt=5, name=pool-1-thread-4, now=Sun Aug 01 16:31:26 CST 2021
thread start cnt=4, name=pool-1-thread-3, now=Sun Aug 01 16:31:26 CST 2021
thread start cnt=2, name=pool-1-thread-1, now=Sun Aug 01 16:31:30 CST 2021
thread start cnt=3, name=pool-1-thread-2, now=Sun Aug 01 16:31:30 CST 2021

 

结果分析:

抛出异常java.util.concurrent.RejectedExecutionException

主线程 卡住了,停在了 【Future<?> f1 = es1.submit(()】之前:before submit...6;

启动了4个线程执行任务, maximumPoolSize 的设置用到了;

 

- 其它拒绝策略

本文上面用的 拒绝策略是 AbortPolicy,实现 接口 RejectedExecutionHandler——结构展示:

红框中的四个 是在 ThreadPoolExecutor 中定义的 静态类。

 

在上一步异常的情况下,修改拒绝策略:

CallerRunsPolicy

		ThreadPoolExecutor es1 = new ThreadPoolExecutor(2, 4, 
				10, TimeUnit.SECONDS, 
				new LinkedBlockingDeque<Runnable>(2), // 排队队列长度为2
				new ThreadPoolExecutor.CallerRunsPolicy());
执行结果
es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
before submit...0, now=Sun Aug 01 17:01:54 CST 2021
after submit...0, now=Sun Aug 01 17:01:54 CST 2021
before submit...1, now=Sun Aug 01 17:01:54 CST 2021
after submit...1, now=Sun Aug 01 17:01:54 CST 2021
before submit...2, now=Sun Aug 01 17:01:54 CST 2021
after submit...2, now=Sun Aug 01 17:01:54 CST 2021
before submit...3, now=Sun Aug 01 17:01:54 CST 2021
after submit...3, now=Sun Aug 01 17:01:54 CST 2021
thread start cnt=0, name=pool-1-thread-1, now=Sun Aug 01 17:01:54 CST 2021
before submit...4, now=Sun Aug 01 17:01:54 CST 2021
thread start cnt=1, name=pool-1-thread-2, now=Sun Aug 01 17:01:54 CST 2021
after submit...4, now=Sun Aug 01 17:01:54 CST 2021
before submit...5, now=Sun Aug 01 17:01:54 CST 2021
thread start cnt=4, name=pool-1-thread-3, now=Sun Aug 01 17:01:54 CST 2021
after submit...5, now=Sun Aug 01 17:01:54 CST 2021
thread start cnt=5, name=pool-1-thread-4, now=Sun Aug 01 17:01:54 CST 2021
before submit...6, now=Sun Aug 01 17:01:54 CST 2021
thread start cnt=6, name=main, now=Sun Aug 01 17:01:54 CST 2021
thread end cnt=0, name=pool-1-thread-1, now=Sun Aug 01 17:01:58 CST 2021
thread end cnt=4, name=pool-1-thread-3, now=Sun Aug 01 17:01:58 CST 2021
thread start cnt=2, name=pool-1-thread-1, now=Sun Aug 01 17:01:58 CST 2021
thread start cnt=3, name=pool-1-thread-3, now=Sun Aug 01 17:01:58 CST 2021
thread end cnt=1, name=pool-1-thread-2, now=Sun Aug 01 17:01:58 CST 2021
thread end cnt=6, name=main, now=Sun Aug 01 17:01:58 CST 2021
thread end cnt=5, name=pool-1-thread-4, now=Sun Aug 01 17:01:58 CST 2021
after submit...6, now=Sun Aug 01 17:01:58 CST 2021
before submit...7, now=Sun Aug 01 17:01:58 CST 2021
after submit...7, now=Sun Aug 01 17:01:58 CST 2021
---end---
thread start cnt=7, name=pool-1-thread-2, now=Sun Aug 01 17:01:58 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 3, queued tasks = 0, completed tasks = 4], now=Sun Aug 01 17:01:59 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 3, queued tasks = 0, completed tasks = 4], now=Sun Aug 01 17:02:00 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 3, queued tasks = 0, completed tasks = 4], now=Sun Aug 01 17:02:01 CST 2021
thread end cnt=7, name=pool-1-thread-2, now=Sun Aug 01 17:02:02 CST 2021
thread end cnt=2, name=pool-1-thread-1, now=Sun Aug 01 17:02:02 CST 2021
thread end cnt=3, name=pool-1-thread-3, now=Sun Aug 01 17:02:02 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 7], now=Sun Aug 01 17:02:02 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 7], now=Sun Aug 01 17:02:03 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 7], now=Sun Aug 01 17:02:04 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 7], now=Sun Aug 01 17:02:05 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 7], now=Sun Aug 01 17:02:06 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 7], now=Sun Aug 01 17:02:07 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 3, active threads = 0, queued tasks = 0, completed tasks = 7], now=Sun Aug 01 17:02:08 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 3, active threads = 0, queued tasks = 0, completed tasks = 7], now=Sun Aug 01 17:02:09 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 3, active threads = 0, queued tasks = 0, completed tasks = 7], now=Sun Aug 01 17:02:10 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 3, active threads = 0, queued tasks = 0, completed tasks = 7], now=Sun Aug 01 17:02:11 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 7], now=Sun Aug 01 17:02:12 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 7], now=Sun Aug 01 17:02:13 CST 2021

结果分析:

未发生异常,主线程正常执行;

所有提交任务 都执行了;

不过,某些是使用 main线程执行的——cnt为6时;

线程池pool size最大为4;

 

DiscardOldestPolicy

忽略 最老的任务:提交一个新任务,没有线程,也无法创建线程,则把最老的干掉,用来执行新任务。

		ThreadPoolExecutor es1 = new ThreadPoolExecutor(2, 4, 
				10, TimeUnit.SECONDS, 
				new LinkedBlockingDeque<Runnable>(2), // 排队队列长度为2
				new ThreadPoolExecutor.DiscardOldestPolicy());

执行结果:

执行结果
es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
before submit...0, now=Sun Aug 01 16:50:05 CST 2021
after submit...0, now=Sun Aug 01 16:50:05 CST 2021
before submit...1, now=Sun Aug 01 16:50:05 CST 2021
after submit...1, now=Sun Aug 01 16:50:05 CST 2021
before submit...2, now=Sun Aug 01 16:50:05 CST 2021
thread start cnt=0, name=pool-1-thread-1, now=Sun Aug 01 16:50:05 CST 2021
after submit...2, now=Sun Aug 01 16:50:05 CST 2021
before submit...3, now=Sun Aug 01 16:50:05 CST 2021
thread start cnt=1, name=pool-1-thread-2, now=Sun Aug 01 16:50:05 CST 2021
after submit...3, now=Sun Aug 01 16:50:05 CST 2021
before submit...4, now=Sun Aug 01 16:50:05 CST 2021
after submit...4, now=Sun Aug 01 16:50:05 CST 2021
before submit...5, now=Sun Aug 01 16:50:05 CST 2021
thread start cnt=4, name=pool-1-thread-3, now=Sun Aug 01 16:50:05 CST 2021
after submit...5, now=Sun Aug 01 16:50:05 CST 2021
before submit...6, now=Sun Aug 01 16:50:05 CST 2021
thread start cnt=5, name=pool-1-thread-4, now=Sun Aug 01 16:50:05 CST 2021
after submit...6, now=Sun Aug 01 16:50:05 CST 2021
before submit...7, now=Sun Aug 01 16:50:05 CST 2021
after submit...7, now=Sun Aug 01 16:50:05 CST 2021
---end---
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0], now=Sun Aug 01 16:50:06 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0], now=Sun Aug 01 16:50:07 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0], now=Sun Aug 01 16:50:08 CST 2021
thread end cnt=5, name=pool-1-thread-4, now=Sun Aug 01 16:50:09 CST 2021
thread end cnt=0, name=pool-1-thread-1, now=Sun Aug 01 16:50:09 CST 2021
thread end cnt=4, name=pool-1-thread-3, now=Sun Aug 01 16:50:09 CST 2021
thread start cnt=7, name=pool-1-thread-1, now=Sun Aug 01 16:50:09 CST 2021
thread start cnt=6, name=pool-1-thread-3, now=Sun Aug 01 16:50:09 CST 2021
thread end cnt=1, name=pool-1-thread-2, now=Sun Aug 01 16:50:09 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 2, queued tasks = 0, completed tasks = 4], now=Sun Aug 01 16:50:09 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 2, queued tasks = 0, completed tasks = 4], now=Sun Aug 01 16:50:10 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 2, queued tasks = 0, completed tasks = 4], now=Sun Aug 01 16:50:11 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 2, queued tasks = 0, completed tasks = 4], now=Sun Aug 01 16:50:12 CST 2021
thread end cnt=7, name=pool-1-thread-1, now=Sun Aug 01 16:50:13 CST 2021
thread end cnt=6, name=pool-1-thread-3, now=Sun Aug 01 16:50:13 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:50:13 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:50:14 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:50:15 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:50:16 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:50:17 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:50:18 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:50:19 CST 2021

 

结果分析:

忽略最老的结果,最终,提交8个任务,只执行了6个——completed tasks = 6;

从上面的“thread end cnt=”日志可以知道,cnt=2、3的线程没有被执行;

 

DiscardPolicy

提交任务时,线程池没有空闲线程处理,和 AbortPolicy 一样不处理,但是,不抛出异常。

		ThreadPoolExecutor es1 = new ThreadPoolExecutor(2, 4, 
				10, TimeUnit.SECONDS, 
				new LinkedBlockingDeque<Runnable>(2), // 排队队列长度为2
				new ThreadPoolExecutor.DiscardPolicy());

 

执行结果:

执行结果
es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
before submit...0, now=Sun Aug 01 16:54:33 CST 2021
after submit...0, now=Sun Aug 01 16:54:33 CST 2021
before submit...1, now=Sun Aug 01 16:54:33 CST 2021
after submit...1, now=Sun Aug 01 16:54:33 CST 2021
before submit...2, now=Sun Aug 01 16:54:33 CST 2021
after submit...2, now=Sun Aug 01 16:54:33 CST 2021
before submit...3, now=Sun Aug 01 16:54:33 CST 2021
thread start cnt=0, name=pool-1-thread-1, now=Sun Aug 01 16:54:33 CST 2021
after submit...3, now=Sun Aug 01 16:54:33 CST 2021
before submit...4, now=Sun Aug 01 16:54:33 CST 2021
thread start cnt=1, name=pool-1-thread-2, now=Sun Aug 01 16:54:33 CST 2021
after submit...4, now=Sun Aug 01 16:54:33 CST 2021
before submit...5, now=Sun Aug 01 16:54:33 CST 2021
thread start cnt=4, name=pool-1-thread-3, now=Sun Aug 01 16:54:33 CST 2021
after submit...5, now=Sun Aug 01 16:54:33 CST 2021
before submit...6, now=Sun Aug 01 16:54:33 CST 2021
after submit...6, now=Sun Aug 01 16:54:33 CST 2021
thread start cnt=5, name=pool-1-thread-4, now=Sun Aug 01 16:54:33 CST 2021
before submit...7, now=Sun Aug 01 16:54:33 CST 2021
after submit...7, now=Sun Aug 01 16:54:33 CST 2021
---end---
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0], now=Sun Aug 01 16:54:34 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0], now=Sun Aug 01 16:54:35 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0], now=Sun Aug 01 16:54:36 CST 2021
thread end cnt=0, name=pool-1-thread-1, now=Sun Aug 01 16:54:37 CST 2021
thread start cnt=2, name=pool-1-thread-1, now=Sun Aug 01 16:54:37 CST 2021
thread end cnt=1, name=pool-1-thread-2, now=Sun Aug 01 16:54:37 CST 2021
thread end cnt=5, name=pool-1-thread-4, now=Sun Aug 01 16:54:37 CST 2021
thread end cnt=4, name=pool-1-thread-3, now=Sun Aug 01 16:54:37 CST 2021
thread start cnt=3, name=pool-1-thread-2, now=Sun Aug 01 16:54:37 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 2, queued tasks = 0, completed tasks = 4], now=Sun Aug 01 16:54:37 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 2, queued tasks = 0, completed tasks = 4], now=Sun Aug 01 16:54:38 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 2, queued tasks = 0, completed tasks = 4], now=Sun Aug 01 16:54:39 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 2, queued tasks = 0, completed tasks = 4], now=Sun Aug 01 16:54:40 CST 2021
thread end cnt=2, name=pool-1-thread-1, now=Sun Aug 01 16:54:41 CST 2021
thread end cnt=3, name=pool-1-thread-2, now=Sun Aug 01 16:54:41 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:54:41 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:54:42 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:54:43 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:54:44 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:54:45 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 4, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:54:46 CST 2021
main es1=java.util.concurrent.ThreadPoolExecutor@3fb4f649[Running, pool size = 2, active threads = 0, queued tasks = 0, completed tasks = 6], now=Sun Aug 01 16:54:47 CST 2021

 

结果分析:

最终只执行了6个任务;

抛弃了最后提交的 cnt=6、7两个任务,但没有抛异常;

也没有阻塞主线程。

 

疑问

1、排队系列设置为多少合理呢?

2、最大线程数设置为多少合适呢?小于等于 CPU核心数量——减少少下文切换时间?

3、只有 CallerRunsPolicy 策略 保证了所有 任务都执行了,但有什么缺点呢?阻塞了 提交任务的线程。

 

 

示例程序4:ForkJoinPool

前面介绍过,这个线程池 最重要的是 实现 “分治”,先分后合。

# 百度百科》分治算法
分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。
求出子问题的解,就可得到原问题的解。即一种分目标完成程序算法,简单问题可用二分法完成。

将上面程序的es1更换为 Executors.newWorkStealingPool():

ExecutorService es1 = Executors.newWorkStealingPool();

执行结果:

执行结果
es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 0, active = 0, running = 0, steals = 0, tasks = 0, submissions = 0]
before submit...0, now=Sun Aug 01 17:21:02 CST 2021
after submit...0, now=Sun Aug 01 17:21:02 CST 2021
before submit...1, now=Sun Aug 01 17:21:02 CST 2021
after submit...1, now=Sun Aug 01 17:21:02 CST 2021
before submit...2, now=Sun Aug 01 17:21:02 CST 2021
after submit...2, now=Sun Aug 01 17:21:02 CST 2021
before submit...3, now=Sun Aug 01 17:21:02 CST 2021
after submit...3, now=Sun Aug 01 17:21:02 CST 2021
before submit...4, now=Sun Aug 01 17:21:02 CST 2021
after submit...4, now=Sun Aug 01 17:21:02 CST 2021
before submit...5, now=Sun Aug 01 17:21:02 CST 2021
after submit...5, now=Sun Aug 01 17:21:02 CST 2021
thread start cnt=0, name=ForkJoinPool-1-worker-5, now=Sun Aug 01 17:21:02 CST 2021
before submit...6, now=Sun Aug 01 17:21:02 CST 2021
thread start cnt=1, name=ForkJoinPool-1-worker-3, now=Sun Aug 01 17:21:02 CST 2021
after submit...6, now=Sun Aug 01 17:21:02 CST 2021
thread start cnt=2, name=ForkJoinPool-1-worker-7, now=Sun Aug 01 17:21:02 CST 2021
before submit...7, now=Sun Aug 01 17:21:02 CST 2021
thread start cnt=3, name=ForkJoinPool-1-worker-9, now=Sun Aug 01 17:21:02 CST 2021
after submit...7, now=Sun Aug 01 17:21:02 CST 2021
---end---
thread start cnt=4, name=ForkJoinPool-1-worker-11, now=Sun Aug 01 17:21:02 CST 2021
thread start cnt=5, name=ForkJoinPool-1-worker-13, now=Sun Aug 01 17:21:02 CST 2021
thread start cnt=6, name=ForkJoinPool-1-worker-15, now=Sun Aug 01 17:21:02 CST 2021
thread start cnt=7, name=ForkJoinPool-1-worker-1, now=Sun Aug 01 17:21:02 CST 2021
main es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 8, active = 8, running = 0, steals = 0, tasks = 0, submissions = 0], now=Sun Aug 01 17:21:03 CST 2021
main es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 8, active = 8, running = 0, steals = 0, tasks = 0, submissions = 0], now=Sun Aug 01 17:21:04 CST 2021
main es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 8, active = 8, running = 0, steals = 0, tasks = 0, submissions = 0], now=Sun Aug 01 17:21:05 CST 2021
thread end cnt=0, name=ForkJoinPool-1-worker-5, now=Sun Aug 01 17:21:06 CST 2021
thread end cnt=2, name=ForkJoinPool-1-worker-7, now=Sun Aug 01 17:21:06 CST 2021
thread end cnt=4, name=ForkJoinPool-1-worker-11, now=Sun Aug 01 17:21:06 CST 2021
thread end cnt=6, name=ForkJoinPool-1-worker-15, now=Sun Aug 01 17:21:06 CST 2021
thread end cnt=7, name=ForkJoinPool-1-worker-1, now=Sun Aug 01 17:21:06 CST 2021
thread end cnt=1, name=ForkJoinPool-1-worker-3, now=Sun Aug 01 17:21:06 CST 2021
thread end cnt=3, name=ForkJoinPool-1-worker-9, now=Sun Aug 01 17:21:06 CST 2021
thread end cnt=5, name=ForkJoinPool-1-worker-13, now=Sun Aug 01 17:21:06 CST 2021
main es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 8, active = 0, running = 0, steals = 8, tasks = 0, submissions = 0], now=Sun Aug 01 17:21:06 CST 2021
main es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 8, active = 0, running = 0, steals = 8, tasks = 0, submissions = 0], now=Sun Aug 01 17:21:07 CST 2021

es1的变化:

es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 0, active = 0, running = 0, steals = 0, tasks = 0, submissions = 0]
main es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 8, active = 8, running = 0, steals = 0, tasks = 0, submissions = 0], now=Sun Aug 01 17:21:03 CST 2021
main es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 8, active = 0, running = 0, steals = 8, tasks = 0, submissions = 0], now=Sun Aug 01 17:21:24 CST 2021

多了parallelism、steals、submissions等属性。

 

结果分析:

提交了8个任务,都执行了。线程的前缀为 “ForkJoinPool-1-worker”,但是,序号不是从 1开始,而是 任意的。

 

我的CPU内核是8核心的,是不是和这个有关系呢?提交20个任务呢?

检查下面日志:

before submit...19, now=Sun Aug 01 17:28:31 CST 2021
after submit...19, now=Sun Aug 01 17:28:31 CST 2021
---end---
main es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 8, active = 8, running = 0, steals = 0, tasks = 0, submissions = 12], now=Sun Aug 01 17:28:32 CST 2021
...

main es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 8, active = 8, running = 0, steals = 0, tasks = 0, submissions = 4], now=Sun Aug 01 17:28:38 CST 2021
...

main es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 8, active = 4, running = 0, steals = 8, tasks = 0, submissions = 0], now=Sun Aug 01 17:28:39 CST 2021
...

main es1=java.util.concurrent.ForkJoinPool@512ddf17[Running, parallelism = 8, size = 8, active = 0, running = 0, steals = 20, tasks = 0, submissions = 0], now=Sun Aug 01 17:29:17 CST 2021

 

es1的信息中,active、steals、submissions一直在变化,steals应该是 已完成任务数量,而submissions为剩余任务数量。

CPU核心为8,所以,parallelism、size一直是 8。来自博客园

以 ForkJoinPool-1-worker- 开头的线程数也是 8。

 

newWorkStealingPool() 函数源码:

    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
    
    // 调用下面的 构造函数
    public ForkJoinPool(int parallelism,
                        ForkJoinWorkerThreadFactory factory,
                        UncaughtExceptionHandler handler,
                        boolean asyncMode) {
        this(parallelism, factory, handler, asyncMode,
             0, MAX_CAP, 1, null, DEFAULT_KEEPALIVE, TimeUnit.MILLISECONDS);
    }
    
    // 调用下面的 构造函数
    public ForkJoinPool(int parallelism,
                        ForkJoinWorkerThreadFactory factory,
                        UncaughtExceptionHandler handler,
                        boolean asyncMode,
                        int corePoolSize,
                        int maximumPoolSize,
                        int minimumRunnable,
                        Predicate<? super ForkJoinPool> saturate,
                        long keepAliveTime,
                        TimeUnit unit) {
    }

 

ForkJoinPool 结构:有4个构造函数,继承了AbstractExecutorService抽象类

 

ForkJoinPool经典程序:计算1至10_000_000的正整数之和

直接使用的 参考文档4 的代码(文档中还有 更多精彩):来自博客园

ForkJoinCalculator相关代码
// 类1
public class Test80104 {

	public static void main(String[] args) {
		long[] numbers = LongStream.rangeClosed(1, 10_000_000).toArray();
		System.out.println(numbers);
		System.out.println(numbers.length);
		
		ForkJoinCalculator cal = new ForkJoinCalculator();
		Instant inst1 = Instant.now();
		long result = cal.sumUp(numbers);
		Instant inst2 = Instant.now();
		System.out.println("result=" +result + ", 耗时=" + Duration.between(inst1, inst2).toMillis() + "毫秒");
	}

}

// 计算接口:分治拆分任务,大任务可以怎么拆分成相同的子任务
public interface Calculator {

	long sumUp(long[] numbers);
	
}

// 实现接口
public class ForkJoinCalculator implements Calculator {

	private ForkJoinPool pool;
	
	public ForkJoinCalculator() {
        // 需要合理设置并行度
		pool = new ForkJoinPool();
	}
	
    // 除了 RecursiveTask,还有 RecursiveAction,前者有返回值,后者没有
	private static class SumTask extends RecursiveTask<Long> {

		private long[] numbers;
		private int from;
		private int to;
		
		public SumTask(long[] numbers, int from, int to) {
			this.numbers = numbers;
			this.from = from;
			this.to = to;
		}
		
		// 此方法为ForkJoin的核心方法:对任务进行拆分  拆分的好坏决定了效率的高低
		@Override
		protected Long compute() {
			// 当需要计算的数字个数小于6时,直接采用for loop方式计算结果
            // 6改为1000时,计算速度更快!需要合理设置才是
			if (to -from < 6) {
				long total = 0;
				for (int i=from; i<=to; i++) {
					total += numbers[i];
				}
				return total;
			} else {
				// 否则,把任务一分为二,递归拆分(注意此处有递归)到底拆分成多少分 需要根据具体情况而定
				int middle = (from + to) / 2;
				SumTask taskLeft = new SumTask(numbers, from, middle);
				SumTask taskRight = new SumTask(numbers, middle + 1, to);
				taskLeft.fork();
				taskRight.fork();
				return taskLeft.join() + taskRight.join();
			}
		}
		
	}
	
	@Override
	public long sumUp(long[] numbers) {
		Long result = pool.invoke(new SumTask(numbers, 0, numbers.length - 1));
		pool.shutdown();
		return result;
	}

}

 

原来,ForkJoinTask 不是为了 提交 Runnable的,而是要使用 专门的 ForkJoinTask抽象类的子类 来做任务。

先设计-分治、递归,再开发 RecursiveTask、RecursiveAction 等 ForkJoinTask实现类。来自博客园

 

Q:核心线程数的设置策略是怎样的?

请参考文档 Java线程池如何合理配置核心线程数

首先要区分 CPU密集型、IO密集型 任务,然后 才设置不同的核心线程数量。

疑问:

1、要是一个线程池既有CPU密集型任务,又有 IO密集型任务呢?此时怎么设置?

2、一个Java进程,会存在多个 线程池对象,这样的话,单个线程池的设置 对于计算机来说,影响不大了?

3、多个进程在 一台计算机运行 时,大家都设置了 线程池,怎么保证不各自的设置有效呢?

针对疑问,

我以为 可以 建立专门的服务来管理线程池,每个服务一个线程池,CPU密集型线程池在一个服务、IO密集型在一个服务,

然后,服务在 容器(Docker等) 运行——限制了CPU核心数等资源,

这样,就可以确保 最大化地(高效率地)使用 系统资源了。

欢迎讨论。

 

程序7、wait、notify

wait、notify/notifyAll 是Object类 中的方法,因此,每一个对象都可以调用。

这两个方法用来进行线程同步:

运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态;

notify() :通知一个在对象上等待的线程,使其从wait()返回,而返回的前提是该线程获取到了对象的锁;

notifyAll(): 通知所有等待在该对象上的线程。

 

示例程序1:一个线程wait释放锁,另一个线程notify/notifyAll,通知wait线程继续执行。

notify、notifyAll必须在 wait之后执行,否则无效。来自博客园

wait/notify-1
public class Test80105 {

	public static void main(String[] args) {
		ExecutorService es1 = Executors.newFixedThreadPool(4);
		
		Test80105 obj = new Test80105();
		
		// 提交线程1
		es1.submit(()->{
			System.out.println(Thread.currentThread().getName() + ": holdsLock=" +Thread.holdsLock(obj));
			obj.sync1();
			System.out.println(Thread.currentThread().getName() + ": holdsLock=" +Thread.holdsLock(obj));
		});
		// 提交线程2
		es1.submit(()->{
			System.out.println(Thread.currentThread().getName() + ": holdsLock=" +Thread.holdsLock(obj));
			
			try {
				// 确保前面的线程先执行,否则程序卡住,前面的线程停止了wait的地方
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			obj.sync2();
			System.out.println(Thread.currentThread().getName() + ": holdsLock=" +Thread.holdsLock(obj));
		});

		es1.shutdown();
	}
	
	/**
	 * 执行wait
	 * @author ben
	 * @date 2021-08-01 20:33:29 CST
	 */
	public synchronized void sync1() {
		System.out.println(Thread.currentThread().getName() + ": start " + new Date());
		System.out.println(Thread.currentThread().getName() + ": holdsLock=" +Thread.holdsLock(this));
		
		try {
			System.out.println(Thread.currentThread().getName() + ": call wait");
			this.wait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(Thread.currentThread().getName() + ": end " + new Date());
	}
	
	/**
	 * 执行notify
	 * @author ben
	 * @date 2021-08-01 20:33:43 CST
	 */
	public synchronized void sync2() {
		System.out.println(Thread.currentThread().getName() + ": start " + new Date());
		System.out.println(Thread.currentThread().getName() + ": holdsLock=" +Thread.holdsLock(this));
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		// notify 、 notifyAll 效果相同
		System.out.println(Thread.currentThread().getName() + ": call notify " + new Date());
//		this.notify();
		this.notifyAll();

		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(Thread.currentThread().getName() + ": end " + new Date());
	}

}

 

执行结果:

执行结果
pool-1-thread-2: holdsLock=false
pool-1-thread-1: holdsLock=false
pool-1-thread-1: start Sun Aug 01 20:32:45 CST 2021
pool-1-thread-1: holdsLock=true
pool-1-thread-1: call wait
pool-1-thread-2: start Sun Aug 01 20:32:47 CST 2021
pool-1-thread-2: holdsLock=true
pool-1-thread-2: call notify Sun Aug 01 20:32:50 CST 2021
pool-1-thread-2: end Sun Aug 01 20:32:53 CST 2021
pool-1-thread-2: holdsLock=false
pool-1-thread-1: end Sun Aug 01 20:32:53 CST 2021
pool-1-thread-1: holdsLock=false

 

结果分析:

在同步方法sync2中执行notify后,线程1没有立即执行,因为线程2还没有释放锁;来自博客园

线程2把 sync2方法执行完毕了,线程1 才继续执行;

线程 2 必须比 线程1 后执行,即 notify/notifyAll 必须在 wait后面执行才可以唤醒 wait的线程。

 

示例程序2:生产者、消费者

生产者 生产物品,仓库满了,就wait,放置一个物品后,执行notify;

消费者 有物品时,可以消费,否则,等待(wait),消费一个物品后,执行notify。

程序:

wait/notify-2
public class Test80106 {

	// 仓库大小
	private final static int INIT_CAP = 3;
	
	// 物品仓库
	// 初始化10个,程序保证最多10个
	private static List<Object> cangku = new ArrayList<>(INIT_CAP);
	
	public static void main(String[] args) {
		ExecutorService es1 = Executors.newFixedThreadPool(4);
		
		cangku.add(new Object());
		cangku.add(new Object());
		cangku.add(new Object());
		System.out.println("cangku.size=" + cangku.size());
		
		// 生产者N个
		for (int i=0; i<2; i++) {
			es1.submit(()->{
				while (true) {
					Thread.sleep(3000);
					addCangku();
				}
			});
		}
		
		// 消费者N个
		for (int i=0; i<2; i++) {
			es1.submit(()->{
				while (true) {
					Thread.sleep(2000);
					subCangku();
				}
			});
		}
	}
	
	/**
	 * 生产者加仓:每次一个
	 * @author ben
	 * @date 2021-08-01 21:19:03 CST
	 */
	public static synchronized void addCangku() {
		if (cangku.size() >= INIT_CAP) {
			try {
				System.out.println(Thread.currentThread().getName() + ": 生产者<< wait, cangku.size=" + cangku.size());
				Test80106.class.wait();
			} catch (InterruptedException e) {
				System.out.println("生产者<< wait异常:");
				e.printStackTrace();
			}
		} else {
			System.out.println(Thread.currentThread().getName() + ": 生产者<< 加仓..., cangku.size=" + cangku.size());
			cangku.add(new Object());
			System.out.println(Thread.currentThread().getName() + ": 生产者<< notifyAll, cangku.size=" + cangku.size());
			Test80106.class.notifyAll();
		}
		
	}
	
	/**
	 * 消费者减仓:每次一个
	 * @author ben
	 * @date 2021-08-01 21:19:13 CST
	 */
	public static synchronized void subCangku() {
		if (cangku.isEmpty()) {
			try {
				System.out.println(Thread.currentThread().getName() + ": 消费者>> wait, cangku.size=" + cangku.size());
				Test80106.class.wait();
			} catch (InterruptedException e) {
				System.out.println("消费者>> wait异常:");
				e.printStackTrace();
			}
		} else {
			System.out.println(Thread.currentThread().getName() + ": 消费者>> 减仓, cangku.size=" + cangku.size());
			cangku.remove(0);
			System.out.println(Thread.currentThread().getName() + ": 消费者>> notifyAll, cangku.size=" + cangku.size());
			Test80106.class.notifyAll();
		}
	}

}

 

执行结果:

执行结果
cangku.size=3
pool-1-thread-3: 消费者>> 减仓, cangku.size=3
pool-1-thread-3: 消费者>> notifyAll, cangku.size=2
pool-1-thread-4: 消费者>> 减仓, cangku.size=2
pool-1-thread-4: 消费者>> notifyAll, cangku.size=1
pool-1-thread-1: 生产者<< 加仓..., cangku.size=1
pool-1-thread-1: 生产者<< notifyAll, cangku.size=2
pool-1-thread-2: 生产者<< 加仓..., cangku.size=2
pool-1-thread-2: 生产者<< notifyAll, cangku.size=3
pool-1-thread-3: 消费者>> 减仓, cangku.size=3
pool-1-thread-3: 消费者>> notifyAll, cangku.size=2
pool-1-thread-4: 消费者>> 减仓, cangku.size=2
pool-1-thread-4: 消费者>> notifyAll, cangku.size=1
pool-1-thread-1: 生产者<< 加仓..., cangku.size=1
pool-1-thread-1: 生产者<< notifyAll, cangku.size=2
pool-1-thread-2: 生产者<< 加仓..., cangku.size=2
pool-1-thread-2: 生产者<< notifyAll, cangku.size=3
pool-1-thread-3: 消费者>> 减仓, cangku.size=3
pool-1-thread-3: 消费者>> notifyAll, cangku.size=2
pool-1-thread-4: 消费者>> 减仓, cangku.size=2
pool-1-thread-4: 消费者>> notifyAll, cangku.size=1
pool-1-thread-3: 消费者>> 减仓, cangku.size=1
pool-1-thread-3: 消费者>> notifyAll, cangku.size=0
pool-1-thread-4: 消费者>> wait, cangku.size=0
pool-1-thread-1: 生产者<< 加仓..., cangku.size=0
pool-1-thread-1: 生产者<< notifyAll, cangku.size=1
pool-1-thread-2: 生产者<< 加仓..., cangku.size=1
pool-1-thread-2: 生产者<< notifyAll, cangku.size=2
pool-1-thread-3: 消费者>> 减仓, cangku.size=2
pool-1-thread-3: 消费者>> notifyAll, cangku.size=1
pool-1-thread-4: 消费者>> 减仓, cangku.size=1
pool-1-thread-4: 消费者>> notifyAll, cangku.size=0
pool-1-thread-1: 生产者<< 加仓..., cangku.size=0
pool-1-thread-1: 生产者<< notifyAll, cangku.size=1
pool-1-thread-2: 生产者<< 加仓..., cangku.size=1
pool-1-thread-2: 生产者<< notifyAll, cangku.size=2
pool-1-thread-3: 消费者>> 减仓, cangku.size=2
pool-1-thread-3: 消费者>> notifyAll, cangku.size=1
pool-1-thread-4: 消费者>> 减仓, cangku.size=1
pool-1-thread-4: 消费者>> notifyAll, cangku.size=0
pool-1-thread-3: 消费者>> wait, cangku.size=0
pool-1-thread-1: 生产者<< 加仓..., cangku.size=0
pool-1-thread-1: 生产者<< notifyAll, cangku.size=1
pool-1-thread-2: 生产者<< 加仓..., cangku.size=1
pool-1-thread-2: 生产者<< notifyAll, cangku.size=2
pool-1-thread-4: 消费者>> 减仓, cangku.size=2
pool-1-thread-4: 消费者>> notifyAll, cangku.size=1
pool-1-thread-3: 消费者>> 减仓, cangku.size=1
pool-1-thread-3: 消费者>> notifyAll, cangku.size=0
pool-1-thread-4: 消费者>> wait, cangku.size=0
pool-1-thread-1: 生产者<< 加仓..., cangku.size=0
pool-1-thread-1: 生产者<< notifyAll, cangku.size=1
pool-1-thread-2: 生产者<< 加仓..., cangku.size=1
pool-1-thread-2: 生产者<< notifyAll, cangku.size=2
pool-1-thread-3: 消费者>> 减仓, cangku.size=2
pool-1-thread-3: 消费者>> notifyAll, cangku.size=1
pool-1-thread-4: 消费者>> 减仓, cangku.size=1
pool-1-thread-4: 消费者>> notifyAll, cangku.size=0
pool-1-thread-1: 生产者<< 加仓..., cangku.size=0
pool-1-thread-1: 生产者<< notifyAll, cangku.size=1
pool-1-thread-3: 消费者>> 减仓, cangku.size=1
pool-1-thread-3: 消费者>> notifyAll, cangku.size=0
pool-1-thread-2: 生产者<< 加仓..., cangku.size=0
pool-1-thread-2: 生产者<< notifyAll, cangku.size=1
pool-1-thread-4: 消费者>> 减仓, cangku.size=1
pool-1-thread-4: 消费者>> notifyAll, cangku.size=0
pool-1-thread-3: 消费者>> wait, cangku.size=0
pool-1-thread-1: 生产者<< 加仓..., cangku.size=0
pool-1-thread-1: 生产者<< notifyAll, cangku.size=1
pool-1-thread-2: 生产者<< 加仓..., cangku.size=1
pool-1-thread-2: 生产者<< notifyAll, cangku.size=2
pool-1-thread-4: 消费者>> 减仓, cangku.size=2
pool-1-thread-4: 消费者>> notifyAll, cangku.size=1
pool-1-thread-3: 消费者>> 减仓, cangku.size=1
pool-1-thread-3: 消费者>> notifyAll, cangku.size=0
pool-1-thread-4: 消费者>> wait, cangku.size=0
pool-1-thread-1: 生产者<< 加仓..., cangku.size=0
pool-1-thread-1: 生产者<< notifyAll, cangku.size=1
pool-1-thread-2: 生产者<< 加仓..., cangku.size=1
pool-1-thread-2: 生产者<< notifyAll, cangku.size=2
pool-1-thread-3: 消费者>> 减仓, cangku.size=2
pool-1-thread-3: 消费者>> notifyAll, cangku.size=1
pool-1-thread-4: 消费者>> 减仓, cangku.size=1
pool-1-thread-4: 消费者>> notifyAll, cangku.size=0
pool-1-thread-1: 生产者<< 加仓..., cangku.size=0
pool-1-thread-1: 生产者<< notifyAll, cangku.size=1
pool-1-thread-2: 生产者<< 加仓..., cangku.size=1
pool-1-thread-2: 生产者<< notifyAll, cangku.size=2
pool-1-thread-3: 消费者>> 减仓, cangku.size=2
pool-1-thread-3: 消费者>> notifyAll, cangku.size=1
pool-1-thread-4: 消费者>> 减仓, cangku.size=1
pool-1-thread-4: 消费者>> notifyAll, cangku.size=0
pool-1-thread-3: 消费者>> wait, cangku.size=0
pool-1-thread-1: 生产者<< 加仓..., cangku.size=0
pool-1-thread-1: 生产者<< notifyAll, cangku.size=1
pool-1-thread-2: 生产者<< 加仓..., cangku.size=1
pool-1-thread-2: 生产者<< notifyAll, cangku.size=2
pool-1-thread-4: 消费者>> 减仓, cangku.size=2
pool-1-thread-4: 消费者>> notifyAll, cangku.size=1
pool-1-thread-3: 消费者>> 减仓, cangku.size=1
pool-1-thread-3: 消费者>> notifyAll, cangku.size=0
pool-1-thread-4: 消费者>> wait, cangku.size=0
pool-1-thread-2: 生产者<< 加仓..., cangku.size=0
pool-1-thread-2: 生产者<< notifyAll, cangku.size=1
pool-1-thread-1: 生产者<< 加仓..., cangku.size=1
pool-1-thread-1: 生产者<< notifyAll, cangku.size=2
pool-1-thread-3: 消费者>> 减仓, cangku.size=2
pool-1-thread-3: 消费者>> notifyAll, cangku.size=1
pool-1-thread-4: 消费者>> 减仓, cangku.size=1
pool-1-thread-4: 消费者>> notifyAll, cangku.size=0
pool-1-thread-2: 生产者<< 加仓..., cangku.size=0
pool-1-thread-2: 生产者<< notifyAll, cangku.size=1
pool-1-thread-1: 生产者<< 加仓..., cangku.size=1
pool-1-thread-1: 生产者<< notifyAll, cangku.size=2
pool-1-thread-3: 消费者>> 减仓, cangku.size=2
pool-1-thread-3: 消费者>> notifyAll, cangku.size=1
pool-1-thread-4: 消费者>> 减仓, cangku.size=1
pool-1-thread-4: 消费者>> notifyAll, cangku.size=0
pool-1-thread-3: 消费者>> wait, cangku.size=0

 

结果分析:两个消费者、两个生产者,生产者速度较慢

程序没有死锁;来自博客园

调用 notifyAll——存在 消费者唤醒消费者、生产者唤醒生产者的情况;

消费者唤醒消费者时,如果已消费空,则没有被唤醒的消费者 调用wait阻塞;

生产者唤醒生产者时,多增加一个物品,但生产者的速度比较慢,不存在被唤醒的生产者阻塞的情况——提高生产速度可以实现;

 

程序8、ThreadLocal类

注:写了一天了,加上昨天,两天了,现在22:15,完成这一章,今天发布吧!

java.lang.ThreadLocal<T>类,主要public方法:get set remove ...

set:设置值,get:获取值,remove:删除值。来自博客园

这些值,都是和 一个线程有关,一对一。同一个ThreadLocal对象,不同线程中执行set、get、remove,只会影响本线程的值,而不会影响其它线程。

程序:

	public static void main(String[] args) throws InterruptedException {
		final ThreadLocal<Integer> tl = new ThreadLocal<>();
		System.out.println(tl);
		System.out.println(tl.get());
		
		// 主线程的设置,不会影响其它线程
		tl.set(100);
		System.out.println(tl);
		System.out.println(tl.get());
//		
//		tl.remove();
//		System.out.println(tl);
//		System.out.println(tl.get());
		
		Thread th1 = new Thread(()->{
			// 立即设置
			System.out.println(Thread.currentThread().getName() + ": start tl.get()=" + tl.get());
			tl.set(11111);
			System.out.println(Thread.currentThread().getName() + ": after set tl.get()=" + tl.get());
			try {
				// 等5秒:线程2设置完毕
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + ": after sleep tl.get()=" + tl.get());
		}, "thread-1");
		
		Thread th2 = new Thread(()->{
			System.out.println(Thread.currentThread().getName() + ":start tl.get()=" + tl.get());
			try {
				// 等2秒:线程1先设置
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + ": after sleep tl.get()=" + tl.get());
			tl.set(22222);
			System.out.println(Thread.currentThread().getName() + ": after set tl.get()=" + tl.get());
			// 执行完毕,再去检查线程1的结果
		}, "thread-2");
		
		th1.start();
		th2.start();
		
		th1.join();
		th2.join();
	}

注:上面的 ThreadLocal用法不是正确的,请参考后面介绍的正确用法。来自博客园

测试结果:

java.lang.ThreadLocal@182decdb
null
java.lang.ThreadLocal@182decdb
100
thread-2:start tl.get()=null
thread-1: start tl.get()=null
thread-1: after set tl.get()=11111
thread-2: after sleep tl.get()=null
thread-2: after set tl.get()=22222
thread-1: after sleep tl.get()=11111

 

进一步:ThreadLocal源码分析

从 构造函数、get、set、remove 出发去分析。来自博客园

源码分析
    // 构造函数:啥也没干
    public ThreadLocal() {
    }
    
    // get函数
    public T get() {
        Thread t = Thread.currentThread();
        // 任何线程都没有set时,首次get返回map 为 null
        // 获取Thread对象的 threadLocals,类型为 ThreadLocal.ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 检查是否存在 本线程的key
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                // map中找到本线程的key,返回值
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 初始化
        return setInitialValue();
    }
    
    // setInitialValue()
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            // 创建map,set函数也会用到
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }
    
    // createMap函数
    void createMap(Thread t, T firstValue) {
        // 会执行初始化,初始值为 null
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
        // ThreadLocalMap构造函数
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            // table是一个Entry[],用来存 每个线程的数据
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
    
    // set函数
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            // createMap函数
            createMap(t, value);
        }
    }
    
    

 

经过分析得知,ThreadLocal对象的 值操作,实际上是操作 ThreadLocal 下 静态类 ThreadLocalMap 的table。

具体更复杂的,请看 参考文档。来自博客园

 

进一步:ThreadLocal内存泄露问题

 

>>>本文完<<<

 

线程(异步执行任务)更进一步:

java.util.concurrent.CompletionService接口:不是ExecutorService,但解决了 ExecutorService的一些缺陷

public class ExecutorCompletionService<V> implements CompletionService<V> {...}

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {...}

 

还有一个 Google Guava 包,需要学习下,里面有 线程(不止如此) 的一些好工具可以使用(或借鉴)。

 

并发包大神Doug Lea
https://blog.csdn.net/qq_38366063/article/details/93888219

致敬🎈

 

本文一定又错漏,还请不吝指正。

 

参考文档

1、Java中Synchronized的用法(简单介绍)

2、菜鸟教程-Java 多线程编程

文末的 笔记 也可以看看。

3、线程池之ThreadPoolExecutor使用

4、Java线程池之---ForkJoinPool线程池的使用以及原理

非常好!

5、线程池corePoolSize和maximumPoolSize关系

6、ThreadLocal 内存泄露问题

7、ThreadLocal的内存泄露?什么原因?如何避免?

8、ThreadLocal源码分析

9、书籍:《Java高并发编程详解:多线程与架构设计》、《Java高并发编程详解:深入理解并发核心库》,作者:汪文君

10、Java线程池如何合理配置核心线程数

11、Google Guava官方教程(中文版)

12、

 

版本:

210801 23:08 V1.0

210806 05:58 V1.1

 

posted @ 2021-08-01 23:18  快乐的二当家815  阅读(64)  评论(0编辑  收藏  举报