背景: 并发知识是一个程序员段位升级的体现, 同样也是进入 BAT 的必经之路, 有必要把并发知识重新梳理一遍.
并发 concurrent:
使用 ThreadLocal 可以实现线程范围内共享变量, 线程 A 写入的值和线程 B 获取到的结果一致; ReentrantReadWriteLock 允许多个读线程或多个写线程同时进行, 但不允许写线程和读线程同时进行; 使用 Callable 可以得到线程执行的返回结果; Exchanger 可以相互交换家线程执行的结果; 这些使用方法大致都一样, JDk 参考文档里面哪里不会点哪里, 下面写个 ThreadLocal 实现线程范围内变量共享, 里面还用到了一下饿汉模式:
执行结果如图中控制台打印, 使用 ThreadLocal 保证了线程 0 和线程 1 读取到的值与写入的一致.
- import java.util.Random;
- import java.util.concurrent.locks.ReentrantReadWriteLock;
- import lombok.Data;
- public class ThreadLocalTest {
- //ThreadLocal 实现线程范围内共享变量
- private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
- private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>();
- public static void main(String[] args) {
- new ReentrantReadWriteLock();
- for(int i = 0; i<2; i++) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- int data = new Random().nextInt();
- System.out.println(Thread.currentThread().getName()
- +"has put data:"+ data);
- x.set(data); // 存的时候与当前线程相关 取的时候也是与当前线程相关
- //MyThreadScopeData.getInstance() 拿到与本线程实例相关的对象: 不用反复 new 对象来 getter/setter
- MyThreadScopeData.getThreadInstance().setName("name:"+data);
- MyThreadScopeData.getThreadInstance().setAge(data);
- new A().get();
- new B().get();
- }
- }).start();
- }
- }
- static class A{
- public void get() {
- int data = x.get();
- System.out.println("A from"+Thread.currentThread().getName()+"get data:"+data);
- //MyThreadScopeData.getInstance() 拿到与本线程实例相关的对象
- MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
- System.out.println("A from"+Thread.currentThread().getName()
- +"getMyData:"+myData.getName() +","+myData.getAge());
- }
- }
- static class B{
- public void get() {
- int data = x.get();
- System.out.println("B from"+Thread.currentThread().getName()+"get data:"+data);
- //MyThreadScopeData.getInstance() 拿到与本线程实例相关的对象
- MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
- System.out.println("B from"+Thread.currentThread().getName()
- +"getMyData:"+myData.getName() +","+myData.getAge());
- }
- }
- // 设计自己线程范围内变量的共享, 不需要创建对象, 只需调用线程即可用到线程内的变量
- @Data
- static class MyThreadScopeData{
- // 构造方法私有化, 外部没发直接调用, 但是可以调用里面的静态方法
- private MyThreadScopeData() { }
- public static /*synchronized*/ MyThreadScopeData getThreadInstance() {
- MyThreadScopeData instance = map.get();
- if (instance == null) {
- // 饿汉模式 : 第一次来创建
- instance = new MyThreadScopeData();
- map.set(instance);
- }
- return instance;
- }
- // private static MyThreadScopeData instance = null; // new MyThreadScopeData();
- private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();
- private String name;
- private int age;
- }
- }
网上还有一个多线程面试很有趣的题目: 子线程执行 10 次, 主线程执行 100 次, 接着子线程再执行 10 次, 主线程继续再执行 100 次, 往复循环 50 次;
- //Java 多线程面试: 子线程执行 10 次, 主线程执行 100 次, 接着子线程再 10 次, 主线程再执行 100 次, 往复循环 50 次
- public class ThreadCommunication {
- public static void main(String[] args) {
- Business business = new Business();
- new Thread(
- new Runnable() {
- @Override
- public void run() {
- for (int i = 1; i <= 50; i++) {
- business.sub(i);
- }
- }
- }).start();
- for (int i = 1; i <= 50; i++) {
- business.main(i);
- }
- }
- // 把主线程和自线程执行的方法归结到一个类 (共同算法的若干方法), 巧妙设计, 好维护高聚合, 健壮性;
- public static class Business{
- // 子线程方法
- private boolean bShouldSub = true;
- public synchronized void sub(int i) {
- while(!bShouldSub) {
- // 用 while 比 if 更好, 可以防止线程被伪唤醒
- try {
- this.wait(); // 如果不是子线程方法该执行的, 则令其等待
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- for(int j = 1; j<= 10; j++) {
- System.out.println("sub thread sequence of"+j + ",loop of"+ i);
- }
- bShouldSub = false;
- this.notify(); // 唤醒主线程方法
- }
- // 主线程方法
- public synchronized void main(int i) {
- while(!bShouldSub) {
- try {
- this.wait(); // 如果不是主线程方法该执行的, 则令其等待
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- for(int j = 1; j<= 100; j++) {
- System.out.println("main thread sequence of"+j + ",loop of"+ i);
- }
- bShouldSub = true;
- this.notify(); // 唤醒子线程方法
- }
- }
- }
线程池:
1, 固定线程数目的线程池 newFixedThreadPool;
2, 缓存线程数目的线程池 newCachedThreadPool;
3, 单一线程池 newSingleThreadExecutor;
4, 定时器线程池 newScheduledThreadPool;
- package com.xinyan.springcloud.controller;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.TimeUnit;
- public class ThreadPoolTest {
- public static void main(String[] args) {
- // 固定线程数目的线程池 3 个
- ExecutorService threadPool = Executors.newFixedThreadPool(3);
- // 缓存线程数目的线程池即动态变化 当任务过来了, 线程池内部会自动增加线程, 空闲后线程又被回收, 线程数目不定
- //ExecutorService threadPool = Executors.newCachedThreadPool();
- // 单一线程池
- //ExecutorService threadPool = Executors.newSingleThreadExecutor();
- // 往线程池中放入 10 个任务:
- for(int i = 1; i<= 10; i++) {
- final int task = i; // task 被 final 修饰不能变了, 但是 i 可以变
- threadPool.execute(new Runnable() {
- @Override
- public void run() {
- for(int j =1; j<=10; j++) {
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //System.out.println(Thread.currentThread().getName()+"loop of"+ j +"task is"+ task);
- }
- }
- });
- }
- System.out.println("所有的 10 个任务已经全部提交."); // 任务都提交了, 交由线程池去搞
- threadPool.shutdown(); // 没有任务后关闭线程
- //threadPool.shutdownNow(); // 还有任务没有给执行完毕就立即关闭线程
- // 定时器线程池: 3 个线程
- System.out.println("敌军还有 5 秒到达战场.");
- Executors.newScheduledThreadPool(3).schedule(new Runnable() {
- @Override
- public void run() {
- System.out.println("敌军抵达战场, 碾碎她们.");
- }
- //5 秒后执行线程池内 run 方法
- }, 5, TimeUnit.SECONDS);
- //Executors.newScheduledThreadPool(3)scheduleAtFixedRate(command, initialDelay, period, unit)
- //scheduleAtFixedRate 定时循环执行线程池内方法
- }
- }
来源: https://www.cnblogs.com/taojietaoge/p/10293651.html