Java并发
This commit is contained in:
		| @@ -8,6 +8,7 @@ import java.util.concurrent.atomic.AtomicInteger; | ||||
| public class J2_CountDown { | ||||
|  | ||||
| 	private static int number = 100; | ||||
| 	// 指定计数器的初始值 | ||||
| 	private static CountDownLatch latch = new CountDownLatch(number); | ||||
| 	private static AtomicInteger integer = new AtomicInteger(0); | ||||
|  | ||||
| @@ -19,7 +20,7 @@ public class J2_CountDown { | ||||
| 				// 假设这是一个耗时的任务 | ||||
| 				Thread.sleep(3000); | ||||
| 				integer.incrementAndGet(); | ||||
| 				// 计数减一 | ||||
| 				// 计数器减1 | ||||
| 				latch.countDown(); | ||||
| 			} catch (InterruptedException e) { | ||||
| 				e.printStackTrace(); | ||||
| @@ -33,8 +34,8 @@ public class J2_CountDown { | ||||
| 		for (int i = 0; i < number; i++) { | ||||
| 			executorService.submit(task); | ||||
| 		} | ||||
| 		// 等待计数器为0时唤醒所有等待的线程 | ||||
| 		latch.await(); | ||||
| 		// 会等待所有任务执行完成再输出 | ||||
| 		System.out.println("integer:" + integer); | ||||
| 		executorService.shutdown(); | ||||
| 	} | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import java.util.concurrent.Semaphore; | ||||
|  | ||||
| public class J1_Semaphore { | ||||
|  | ||||
| 	// 限制并发访问的线程的数量为5 | ||||
| 	private static Semaphore semaphore = new Semaphore(5); | ||||
|  | ||||
| 	static class IncreaseTask implements Runnable { | ||||
|   | ||||
| @@ -0,0 +1,26 @@ | ||||
| package com.heibaiying.stop; | ||||
|  | ||||
| /** | ||||
|  * 线程终止 | ||||
|  */ | ||||
| public class ThreadStop { | ||||
|  | ||||
| 	private static volatile boolean stopFlag = true; | ||||
|  | ||||
| 	public static void main(String[] args) throws InterruptedException { | ||||
| 		Thread thread = new Thread(() -> { | ||||
| 			while (stopFlag) { | ||||
| 				try { | ||||
| 					Thread.sleep(100); | ||||
| 					System.out.println("持续输出"); | ||||
| 				} catch (InterruptedException e) { | ||||
| 					e.printStackTrace(); | ||||
| 				} | ||||
| 			} | ||||
| 		}); | ||||
| 		thread.start(); | ||||
| 		Thread.sleep(3 * 1000); | ||||
| 		stopFlag = false; | ||||
| 		System.out.println("线程终止"); | ||||
| 	} | ||||
| } | ||||
| @@ -11,15 +11,22 @@ public class J1_ThreadPool { | ||||
| 	static class Task implements Runnable { | ||||
| 		@Override | ||||
| 		public void run() { | ||||
| 			System.out.println(Thread.currentThread().getName() + "正在执行"); | ||||
| 			try { | ||||
| 				Thread.sleep(100); | ||||
| 				System.out.println(Thread.currentThread().getName() + "正在执行"); | ||||
| 			} catch (InterruptedException e) { | ||||
| 				e.printStackTrace(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	public static void main(String[] args) { | ||||
| 		ExecutorService executorService = Executors.newFixedThreadPool(10); | ||||
| 		for (int i = 0; i < 100; i++) { | ||||
| 			// 提交任务到线程池 | ||||
| 			executorService.submit(new Task()); | ||||
| 		} | ||||
| 		// 关闭线程池,此时不再接受新任务,但仍会等待原有的任务执行完成,如果想要立即关闭,则可以使用shutdownNow() | ||||
| 		executorService.shutdown(); | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -33,12 +33,13 @@ public class J2_ScheduledTask { | ||||
| 	} | ||||
|  | ||||
| 	public static void main(String[] args) { | ||||
| 		// 为避免相互间的影响,以下各种场景最好分别测试: | ||||
| 		ScheduledExecutorService pool = Executors.newScheduledThreadPool(10); | ||||
| 		// 只执行一次 | ||||
| 		pool.schedule(new Task("schedule"), 2, TimeUnit.SECONDS); | ||||
| 		// 指定2秒为固定周期执行,如果项目执行耗时5秒,则项目结束后立马执行下一次任务,所以输出的时间间隔为5秒 | ||||
| 		// 指定2秒为固定周期执行,因为项目执行耗时5秒,此时项目结束会立马执行下一次任务,所以输出的时间间隔为5秒 | ||||
| 		pool.scheduleAtFixedRate(new Task("FixedRate"), 0, 2, TimeUnit.SECONDS); | ||||
| 		// 总是在上一次项目结束后间隔指定周期执行,所以项目耗时5秒,还需要间隔2秒执行,所以输出的时间间隔为7秒 | ||||
| 		// 总是在上一次项目结束后间隔指定周期执行,因为项目耗时5秒,还需要间隔2秒执行,所以输出的时间间隔为7秒 | ||||
| 		pool.scheduleWithFixedDelay(new Task("WithFixedDelay"), 0, 2, TimeUnit.SECONDS); | ||||
| 		// pool.shutdown(); | ||||
| 	} | ||||
|   | ||||
| @@ -7,7 +7,7 @@ public class J1_Normal { | ||||
|  | ||||
| 	private static int j = 0; | ||||
|  | ||||
| 	public static void main(String[] args) throws InterruptedException { | ||||
| 	public static void main(String[] args)  { | ||||
| 		Thread thread = new Thread(() -> { | ||||
| 			for (int i = 0; i < 100000; i++) { | ||||
| 				j++; | ||||
|   | ||||
							
								
								
									
										435
									
								
								notes/Java_并发.md
									
									
									
									
									
								
							
							
						
						
									
										435
									
								
								notes/Java_并发.md
									
									
									
									
									
								
							| @@ -72,12 +72,107 @@ Java 线程的生命周期分为以下五类状态: | ||||
|  | ||||
|  | ||||
|  | ||||
| ### 1.4 线程终止 | ||||
|  | ||||
| 通常线程会随着代码的运行完成而终止,但如果你在线程中进行了循环操作,此时就需要考虑如何安全地停止?虽然 Thread 类中提供了 `stop()` 方法,但是其已经被标识为废弃,因为 `stop()` 只是暴力的停止线程, 此时线程中的操作可能处于中间状态,这会导致错误的结果。想要安全的停止线程,可以通过改变终止标志位的方式来进行实现: | ||||
|  | ||||
| ```java | ||||
| public class ThreadStop { | ||||
|  | ||||
|     private static volatile boolean stopFlag = true; | ||||
|  | ||||
|     public static void main(String[] args) throws InterruptedException { | ||||
|         Thread thread = new Thread(() -> { | ||||
|             while (stopFlag) { | ||||
|                 try { | ||||
|                     Thread.sleep(100); | ||||
|                     System.out.println("持续输出"); | ||||
|                 } catch (InterruptedException e) { | ||||
|                     e.printStackTrace(); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         thread.start(); | ||||
|         Thread.sleep(3 * 1000); | ||||
|         stopFlag = false; | ||||
|         System.out.println("线程终止"); | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 1.5  线程中断 | ||||
|  | ||||
| JDK 中提供了以下方法用于实现线程中断: | ||||
|  | ||||
| + `Thread.interrupt() `:用于给目标线程设置中断标志位,但实际上并不能中断线程; | ||||
| + `Thread.isInterrupted()`:通过检查中断标志位,判断当前线程是否被中断; | ||||
| + `Thread.interrupted()`:用来判断当前线程的中断状态,同时会清除当前线程的中断标志位。 | ||||
|  | ||||
| 示例如下: | ||||
|  | ||||
| ```java | ||||
| /** | ||||
|  * interrupt() 只是设置中断标志位,并不能中断线程,所以子线程会持续打印 | ||||
|  */ | ||||
| public class J1_Interrupt { | ||||
|     public static void main(String[] args) throws InterruptedException { | ||||
|         Thread thread = new Thread(() -> { | ||||
|             while (true) { | ||||
|                 System.out.println("子线程打印"); | ||||
|             } | ||||
|         }); | ||||
|         thread.start(); | ||||
|         Thread.sleep(10); | ||||
|         thread.interrupt(); | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * isInterrupted() 用于检查当前线程是否存在中断标志位,配合interrupt()使用可以中断线程 | ||||
|  */ | ||||
| public class J2_IsInterrupted { | ||||
|     public static void main(String[] args) throws InterruptedException { | ||||
|         Thread thread = new Thread(() -> { | ||||
|             while (!Thread.currentThread().isInterrupted()) { | ||||
|                 System.out.println("子线程打印"); | ||||
|             } | ||||
|         }); | ||||
|         thread.start(); | ||||
|         Thread.sleep(10); | ||||
|         thread.interrupt(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * interrupted() 用于判断当前线程的中断状态,并清除当前线程的中断标志位 | ||||
|  */ | ||||
| public class J3_Interrupted { | ||||
|  | ||||
|     public static void main(String[] args) throws InterruptedException { | ||||
|         Thread thread = new Thread(() -> { | ||||
|             // 此时由于标志位被清除,此时子线程依然会持续打印 | ||||
|             while (!Thread.interrupted() || !Thread.currentThread().isInterrupted()) { | ||||
|                 System.out.println("子线程打印"); | ||||
|             } | ||||
|         }); | ||||
|         thread.start(); | ||||
|         Thread.sleep(10); | ||||
|         thread.interrupt(); | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| ## 二、状态变量与共享变量 | ||||
|  | ||||
| **状态变量 (State Variable)** :即类的实例变量,非共享的静态变量。 | ||||
|  | ||||
| **共享变量 (Shared Variable)** : 即可以被多个线程共同访问的变量。 | ||||
|  | ||||
|  | ||||
|  | ||||
| ## 三、原子性 | ||||
|  | ||||
| ### 3.1 定义 | ||||
| @@ -95,6 +190,8 @@ Java 线程的生命周期分为以下五类状态: | ||||
|  | ||||
| 通过 Java 虚拟机规范和非原子性协定, Java 语言可以保证对基本数据类型的访问具有有原子性,如果想要保证更大范围内的原子性(如多行操作的原子性),此时可以使用字节码指令 monitorenter 和 monitorexit 来隐式执行 lock 和 unlock 操作,从而将串行变成并行来保证原子性。monitorenter 和 monitorexit 这两个字节码指令反映到 Java 代码中就是 Synchronized 关键字。 | ||||
|  | ||||
|  | ||||
|  | ||||
| ## 四、可见性 | ||||
|  | ||||
| ### 4.1 定义 | ||||
| @@ -169,6 +266,8 @@ Java 线程的生命周期分为以下五类状态: | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| ## 五、有序性 | ||||
|  | ||||
| ### 5.1 顺序语义 | ||||
| @@ -553,13 +652,15 @@ public class ReadWriteLock { | ||||
| } | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| ## 七、线程间的协作 | ||||
|  | ||||
| ### 7.1 等待与通知 | ||||
|  | ||||
| 为了支持多线程之间的协作,JDK 中提供了两个非常重要的方法:`wait()` 和 `notify()` ,这两个方法定义在 `Object` 类中,这意味着任何 Java 对象都可以调用者两个方法。如果一个线程调用了 `object.wait()` 方法,那么它就会进入该对象的等待队列中,这个队列中可能包含了多个线程,此时代表多个线程都在等待同一个对象;当 `object.notify()` 方法被调用时,它就会从这个等待队列中**随机**唤醒一个线程。 | ||||
|  | ||||
| 需要特别注意的是在调用这两个方法时,它们都必须位于对应对象的 synchronzied 语句中,因为这两个对象在调用前都需要获得对应对象的监视器,过程如下: | ||||
| 需要特别注意的是在调用这两个方法时,它们都必须位于对应对象的 synchronzied 语句中,因为这两个方法在调用前都需要获得对应对象的监视器(内部锁),过程如下: | ||||
|  | ||||
| TODO | ||||
|  | ||||
| @@ -657,12 +758,15 @@ object.notify(); | ||||
|  | ||||
| ### 7.2 条件变量 | ||||
|  | ||||
| 综上所述可以使用 `wait()` 和 `notify()` 配合内部锁 synchronized 来实现线程间的等待与唤醒,如果你使用的是显示锁而不是内部锁,此时可以使用 Condition 来实现同样的等待唤醒效果。Condition 接口中定义了如下方法: | ||||
| 综上所述可以使用 `wait()` 和 `notify()` 配合内部锁 synchronized 可以实现线程间的等待与唤醒,如果你使用的是显示锁而不是内部锁,此时可以使用 Condition 来实现同样的效果。Condition 接口中定义了如下方法: | ||||
|  | ||||
| + await(): | ||||
| + awaitUninterruptibly(): | ||||
| + **await()**:使得当前线程进入等待状态,类似于 `object.wait()`; | ||||
| + **awaitUninterruptibly()**:与 `await()` 类似,但它不会在等待过程中响应中断; | ||||
| + **awaitNanos(long nanosTimeout) & await(long time, TimeUnit unit) & awaitUntil(Date deadline)**:有时间限制的等待; | ||||
| + **signal()**:用于随机唤醒一个等待; | ||||
| + **signalAll()**:用于唤醒所有等待。 | ||||
|  | ||||
| 使用示例如下: | ||||
| 和 object 的 `wait()\notify()\notifyAll()` 一样,在使用 condition 的 `await()\signal()\signalAll()` 前,也要求线程必须持有相关的重入锁, 示例如下: | ||||
|  | ||||
| ```java | ||||
| public class AwaitAndSignal { | ||||
| @@ -706,11 +810,326 @@ Thread-0线程等待通知... | ||||
| Thread-0线程后续操作 | ||||
| ``` | ||||
|  | ||||
| ### 7.3 CountDownLatch | ||||
| ### 7.3  Join | ||||
|  | ||||
| `Thread.join()` 可以让当前线程等待目标线程结束后再开始运行,示例如下: | ||||
|  | ||||
| ```java | ||||
| public class J1_Normal { | ||||
|     private static int j = 0; | ||||
|     public static void main(String[] args)  { | ||||
|         Thread thread = new Thread(() -> { | ||||
|             for (int i = 0; i < 100000; i++) { | ||||
|                 j++; | ||||
|             } | ||||
|         }); | ||||
|         thread.start(); | ||||
|         System.out.println(j); | ||||
|     } | ||||
| } | ||||
| // 此时主线程不等待子线程运行完成,通常输出结果为:0 | ||||
|  | ||||
| public class J2_Join { | ||||
|     private static int j = 0; | ||||
|     public static void main(String[] args) throws InterruptedException { | ||||
|         Thread thread = new Thread(() -> { | ||||
|             for (int i = 0; i < 100000; i++) { | ||||
|                 j++; | ||||
|             } | ||||
|         }); | ||||
|         thread.start(); | ||||
|         thread.join(); | ||||
|         System.out.println(j); | ||||
|     } | ||||
| } | ||||
| // 此时主线程需要等待子线程运行完成,输出结果为:100000 | ||||
| ``` | ||||
|  | ||||
| ### 7.4 CountDownLatch | ||||
|  | ||||
| `Thread.join()` 可以让当前线程等待目标线程结束后再开始运行,但大多数时候,你只需要等待目标线程完成特定的操作,而不必等待其完全终止。此时可以使用条件变量 Condition 来实现,也可以使用更为简单的工具类 CountDownLatch 。CountDownLatch 会在内部维护一个计数器,每次完成一个任务,则计数器减 1,当计数器为 0 时,则唤醒所有的等待线程,示例如下: | ||||
|  | ||||
| ```java | ||||
| public class j1_Normal { | ||||
|     private static AtomicInteger integer = new AtomicInteger(0); | ||||
|     static class IncreaseTask implements Runnable { | ||||
|         @Override | ||||
|         public void run() { | ||||
|             try { | ||||
|                 // 假设这是一个耗时的任务 | ||||
|                 Thread.sleep(3000); | ||||
|                 integer.incrementAndGet(); | ||||
|             } catch (InterruptedException e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void main(String[] args) { | ||||
|         IncreaseTask task = new IncreaseTask(); | ||||
|         ExecutorService executorService = Executors.newFixedThreadPool(100); | ||||
|         for (int i = 0; i < 100; i++) { | ||||
|             executorService.submit(task); | ||||
|         } | ||||
|         System.out.println("integer:" + integer); | ||||
|         executorService.shutdown(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // 不使用CountDownLatch 时,主线程不会子线程等待计算完成,此时输出通常为: 0 | ||||
|  | ||||
|  | ||||
| public class J2_CountDown { | ||||
|     private static int number = 100; | ||||
|     // 指定计数器的初始值 | ||||
|     private static CountDownLatch latch = new CountDownLatch(number); | ||||
|     private static AtomicInteger integer = new AtomicInteger(0); | ||||
|     static class IncreaseTask implements Runnable { | ||||
|         @Override | ||||
|         public void run() { | ||||
|             try { | ||||
|                 // 假设这是一个耗时的任务 | ||||
|                 Thread.sleep(3000); | ||||
|                 integer.incrementAndGet(); | ||||
|                 // 计数器减1 | ||||
|                 latch.countDown(); | ||||
|             } catch (InterruptedException e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void main(String[] args) throws InterruptedException { | ||||
|         IncreaseTask task = new IncreaseTask(); | ||||
|         ExecutorService executorService = Executors.newFixedThreadPool(100); | ||||
|         for (int i = 0; i < number; i++) { | ||||
|             executorService.submit(task); | ||||
|         } | ||||
|         // 等待计数器为0时唤醒所有等待的线程 | ||||
|         latch.await(); | ||||
|         System.out.println("integer:" + integer); | ||||
|         executorService.shutdown(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // 使用CountDownLatch 时,主线程需要等待所有的子线程计算完成后再输出,计算结果为:100 | ||||
| ``` | ||||
|  | ||||
| ### 7.5 CyclicBarrier | ||||
|  | ||||
| CyclicBarrier 和 CountDownLatch 类似,都是用于等待一个或者多个线程完成特定的任务后再执行某项操作,但不同的是它可以循环使用,示例如下: | ||||
|  | ||||
| ```java | ||||
| /** | ||||
|  * 每五个人完成任务后,则算一个小组已完成 | ||||
|  */ | ||||
| public class J1_CyclicBarrier { | ||||
|  | ||||
|     private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> System.out.println("五人小组任务执行完成")); | ||||
|  | ||||
|     static class Task implements Runnable { | ||||
|         @Override | ||||
|         public void run() { | ||||
|             try { | ||||
|                 long l = new Double(Math.random() * 5000).longValue(); | ||||
|                 Thread.sleep(l); | ||||
|                 System.out.println("任务" + Thread.currentThread().getId() + "执行完成"); | ||||
|                 cyclicBarrier.await(); | ||||
|             } catch (InterruptedException | BrokenBarrierException e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void main(String[] args) { | ||||
|         ExecutorService executorService = Executors.newFixedThreadPool(20); | ||||
|         for (int j = 0; j < 10; j++) { | ||||
|             executorService.submit(new Task()); | ||||
|         } | ||||
|         executorService.shutdown(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // 输出如下: | ||||
| 任务21执行完成 | ||||
| 任务20执行完成 | ||||
| 任务15执行完成 | ||||
| 任务14执行完成 | ||||
| 任务22执行完成 | ||||
| 五人小组任务执行完成 | ||||
| 任务17执行完成 | ||||
| 任务13执行完成 | ||||
| 任务19执行完成 | ||||
| 任务18执行完成 | ||||
| 任务16执行完成 | ||||
| 五人小组任务执行完成 | ||||
| ``` | ||||
|  | ||||
| 基于 CyclicBarrier 的特性,通常可以用于在测试环境来模仿高并发,如每次等待一万个线程启动后再让其并发执行某项压力测试。 | ||||
|  | ||||
| ### 7.6 Semaphore | ||||
|  | ||||
| 信号量(Semaphore)可以看做是锁的扩展,由于锁的排它性,所以一次只允许一个线程来访问某个特定的资源, 而 Semaphore 则允许多个线程并发的访问某个特定的资源,并且可以通过配额来限制并发访问的线程的数量,因此其可以用于流量控制等场景中: | ||||
|  | ||||
| ```java | ||||
| public class J1_Semaphore { | ||||
|  | ||||
|     // 限制并发访问的线程的数量为5 | ||||
|     private static Semaphore semaphore = new Semaphore(5); | ||||
|  | ||||
|     static class IncreaseTask implements Runnable { | ||||
|         @Override | ||||
|         public void run() { | ||||
|             try { | ||||
|                 semaphore.acquire(); | ||||
|                 System.out.println(Thread.currentThread().getId() + "获得锁!"); | ||||
|                 Thread.sleep(5000); | ||||
|                 semaphore.release(); | ||||
|             } catch (InterruptedException e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void main(String[] args) { | ||||
|         IncreaseTask task = new IncreaseTask(); | ||||
|         for (int i = 0; i < 20; i++) { | ||||
|             new Thread(task).start(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // 输出如下,至多只能有五个线程并发获得锁 | ||||
| 13获得锁! | ||||
| 15获得锁! | ||||
| 16获得锁! | ||||
| 18获得锁! | ||||
| 17获得锁! | ||||
| .... | ||||
| 19获得锁! | ||||
| 20获得锁! | ||||
| 21获得锁! | ||||
| 22获得锁! | ||||
| 23获得锁! | ||||
| .... | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| ## 八、线程池 | ||||
|  | ||||
| ### 8.1  线程池分类 | ||||
|  | ||||
| 为了避免重复创建和销毁线程而导致额外的性能开销,JDK 提供了线程池功能来实现线程的复用,具体分为以下几类: | ||||
| + **newFixedThreadPool()**:该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新任务提交时,如果线程池中存在空闲的线程,则立即执行;如果没有,则新任务会被暂时存在一个任务队列中,待有线程空闲时再进行处理。 | ||||
| + **newSingleThreadExecutor()**: 该方法返回一个只有一个线程的线程池。若多个任务被提交到该线程池,则多余的任务会被保存在一个任务队列中,待线程空闲,按照先入先出的顺序被执行。 | ||||
| + **newCachedThreadPool()**:根据实际情况动态调整线程数量。当新任务提交时,会优先复用空闲的线程;如果所有线程均处于工作状态,则会创建新的线程来进行处理。 | ||||
| + **newSingleThreadScheduledExecutor()**:该方法返回一个 ScheduledExecutorService 对象,线程池大小为 1 。SeheduledExectorService 在继承 ExecutorService 的基础上还额外支持定时任务的执行。 | ||||
| + **newScheduledThreadPool()**:与 newSingleThreadScheduledExecutor 方法类似,但可以指定线程池中线程的数量。 | ||||
|  | ||||
| 线程池的基本使用如下: | ||||
|  | ||||
| ```java | ||||
| public class J1_ThreadPool { | ||||
|  | ||||
|     static class Task implements Runnable { | ||||
|         @Override | ||||
|         public void run() { | ||||
|             try { | ||||
|                 Thread.sleep(100); | ||||
|                 System.out.println(Thread.currentThread().getName() + "正在执行"); | ||||
|             } catch (InterruptedException e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void main(String[] args) { | ||||
|         // 创建线程池 | ||||
|         ExecutorService executorService = Executors.newFixedThreadPool(10); | ||||
|         for (int i = 0; i < 100; i++) { | ||||
|             // 提交任务到线程池 | ||||
|             executorService.submit(new Task()); | ||||
|         } | ||||
|         // 关闭线程池,此时不再接受新任务,但仍会等待原有的任务执行完成,如果想要立即关闭,则可以使用shutdownNow() | ||||
|         executorService.shutdown(); | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 8.2 定时任务 | ||||
|  | ||||
| 上面线程池分类中的 `newSingleThreadScheduledExecutor()` 和 `newScheduledThreadPool()` 都可以用于创建支持定时任务的线程池,它们返回的都是 ScheduledExecutorService 接口的实例。ScheduledExecutorService 接口中定义了如下三类定时方法: | ||||
|  | ||||
| ```java | ||||
| /*在给定的时间,对任务进行一次性调度*/ | ||||
| public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit); | ||||
| public <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit); | ||||
|  | ||||
| /** | ||||
|  * 以上一个任务开始执行时间为起点,等待period时间后开始调度下一次任务, | ||||
|  * 如果任务耗时大于period,则上一次任务结束后立即执行下一次任务 | ||||
|  */ | ||||
| public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period, | ||||
|                                               TimeUnit unit); | ||||
| /** | ||||
|  * 以上一个任务开始执行时间为起点再经过delay时间后开始调度下一次任务, | ||||
|  * 不论任务耗时如何,上一次任务结束后都需要等待delay时间之后才可以执行下一次任务 | ||||
|  */ | ||||
| public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,long delay, | ||||
|                                                  TimeUnit unit); | ||||
| ``` | ||||
|  | ||||
| 使用示例如下: | ||||
|  | ||||
| ```java | ||||
| public class J2_ScheduledTask { | ||||
|  | ||||
|     private static long cacheTime = System.currentTimeMillis(); | ||||
|  | ||||
|     static class Task implements Runnable { | ||||
|  | ||||
|         private String type; | ||||
|  | ||||
|         Task(String type) { | ||||
|             this.type = type; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void run() { | ||||
|             try { | ||||
|                 Thread.sleep(5000); | ||||
|                 long nowTime = System.currentTimeMillis(); | ||||
|                 System.out.println(type + Thread.currentThread().getId() + "执行耗时" + (nowTime - cacheTime) + "毫秒"); | ||||
|                 cacheTime = nowTime; | ||||
|             } catch (InterruptedException e) { | ||||
|                 e.printStackTrace(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static void main(String[] args) { | ||||
|         // 为避免相互间的影响,以下各种场景最好分别测试: | ||||
|         ScheduledExecutorService pool = Executors.newScheduledThreadPool(10); | ||||
|         // 只执行一次 | ||||
|         pool.schedule(new Task("schedule"), 2, TimeUnit.SECONDS); | ||||
|         // 指定2秒为固定周期执行,因为项目执行耗时5秒,此时项目结束会立马执行下一次任务,所以输出的时间间隔为5秒 | ||||
|         pool.scheduleAtFixedRate(new Task("FixedRate"), 0, 2, TimeUnit.SECONDS); | ||||
|         // 总是在上一次项目结束后间隔指定周期执行,因为项目耗时5秒,还需要间隔2秒执行,所以输出的时间间隔为7秒 | ||||
|         pool.scheduleWithFixedDelay(new Task("WithFixedDelay"), 0, 2, TimeUnit.SECONDS); | ||||
|         // pool.shutdown(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 8.3   | ||||
|  | ||||
| ## 九、并发容器 | ||||
|  | ||||
|  | ||||
|  | ||||
| ### 7.4 CyclicBarrier | ||||
|  | ||||
| ### 7.5 Semaphore | ||||
|  | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -60,8 +60,7 @@ | ||||
|  | ||||
| + `Thread.interrupt() `是一个实例方法,它通知目标线程被中断,也就是设置中断标志位; | ||||
| + `Thread.isInterrupted()`也是实例方法,判断当前线程是否有被中断(通过检查中断标志位); | ||||
|  | ||||
| - `Thread.interrupted()`是静态方法:用来判断当前线程的中断状态,但同时会清除当前线程的中断标志位状态。 | ||||
| + `Thread.interrupted()`是静态方法:用来判断当前线程的中断状态,同时会清除当前线程中断标志位的状态。 | ||||
|  | ||||
| ```java | ||||
| // Thread 类 | ||||
| @@ -1180,7 +1179,6 @@ public class Test { | ||||
| ### 3.2 线程池 | ||||
|  | ||||
| <div align="center"> <img src="https://github.com/heibaiying/LearningNotes/blob/master/pictures/Executors.png"/> </div></br> | ||||
|  | ||||
| #### 3.2.1 JDK对线程池的支持 | ||||
|  | ||||
| **newFixedThreadPool()方法**:该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲的线程,则立即执行。若没有,则新的任务会被暂时存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。 | ||||
| @@ -1189,7 +1187,7 @@ public class Test { | ||||
|  | ||||
| **newCachedThreadPool()方法**:根据实际情况动态调整线程数量。 | ||||
|  | ||||
| **newSingleThreadScheduledExecutor()方法**:该方法返回一个ScheduledExecutorService对象,线程池大小为1。SeheduledExectorService接口在ExecutorService接口之上扩展了在给定时间执行某任务的功能,如在某个固定的延时之后执行,或者周期性执行某个任务。 | ||||
| **newSingleThreadScheduledExecutor()方法**:该方法返回一个ScheduledExecutorService对象,线程池大小为 1。SeheduledExectorService接口在ExecutorService接口之上扩展了在给定时间执行某任务的功能,如在某个固定的延时之后执行,或者周期性执行某个任务。 | ||||
|  | ||||
| **newScheduledThreadPool()方法**:该方法也返回一个ScheduledExecutorService对象,但该线程池可以指定线程数量。 | ||||
|  | ||||
| @@ -1401,9 +1399,7 @@ public class Test { | ||||
|  | ||||
| ##### 3.合理优化线程池的数量 | ||||
|  | ||||
| <div align="center"> <img src="https://github.com/heibaiying/LearningNotes/blob/master/pictures/合理线程池数量.png"/> </div></br> | ||||
|  | ||||
|  | ||||
| <div align="center"> <img src="../pictures/合理线程池数量.png"/> </div></br> | ||||
|  | ||||
| #### 3.2.3 Fork/Join 框架 | ||||
|  | ||||
| @@ -1631,7 +1627,6 @@ CAS的全称是Compare And Swap 即比较交换,其算法核心思想如下 | ||||
| 如果V值等于E值,则将V的值设为N。若V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。通俗的理解就是CAS操作需要我们提供一个期望值,当期望值与当前线程的变量值相同时,说明还没线程修改该值,当前线程可以进行修改,也就是执行CAS操作,但如果期望值与当前线程不符,则说明该值已被其他线程修改,此时不执行更新操作,但可以选择重新读取该变量再尝试再次修改该变量,也可以放弃操作。 | ||||
|  | ||||
| <div align="center"> <img src="https://github.com/heibaiying/LearningNotes/blob/master/pictures/原子包.png"/> </div></br> | ||||
|  | ||||
| ```java | ||||
| // 原子类 | ||||
| public class Test { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user