JUC

"一百天也要学完JUC"

Posted by HZY on January 27, 2026

预备知识

进程与线程
  • 进程的概念:

  • 线程的概念:

  • 对比:

    • 进程几乎相互独立
    • 进程内数据,可供内部线程共享
    • 进程间通信复杂
    • 线程通信相对简单
    • 线程更加轻量级
并行与并发
  • 操作系统的任务调度器
    • 对于单核的cpu:微观串行,宏观并行
    • 多核cpu
  • 并发: 同一时间应对多件事情的能力
  • 并行: 同一时间动手做多件事情的能力
同步与异步
  • 同步
  • 异步

线程

Thread
  • 直接使用Thread
  • ThreadRunnable
1
2
3
4
5
6
7
8
9
10
11
@Test
public void test02() throws InterruptedException {
    Thread thread = new Thread(
            ()-> cout.print("我启动了")
            //cout是作者自定义的一个red_printer
    ) ;
    thread.setName("thread2");
    thread.start();

    thread.join();
}
  • FutureTask实现了Runnable接口,所以也可以扔给task
    • task.get()会阻塞线程直到拿到值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test03() throws InterruptedException, ExecutionException {
    FutureTask<String > task = new FutureTask(()->{
        cout.print("我也喜欢你");
        return "这是我的答案!!!love you too";
    });

    Thread thread = new Thread(task);
    thread.setName("表白线程");

    thread.start();
    System.out.println("我喜欢你,可以给我答复吗");
    cout.print( task.get());
}
进程命令
1
2
3
4
5
6
tasklist   
taskkill 

jps  #查看所有java线程
jstack <PID>  #查看某个java进程的所有线程状态
jconsole   #查看某个java进程中线程的运行情况
线程运行原理
  • 栈:
    • 栈帧:
  • 堆:
  • 方法区

  • 栈帧图解:
  • 线程上下文切换:
    • 线程数过多,频繁切换上下文会使得效率变低
常见方法
1
2
3
4
5
6
7
8
9
10
11
12
run();
join();
getId();
getName();
getState();
isInterrupted();
isAlive();
interrupt();
interrupter();
Thread.currentThread();
sleep();
yield();
  • 直接new Thread重写run()方法已经在jdk17+不再推荐使用
    • 这里简短的说明一下start()run()方法,调用run方法就是把run拿到当前线程,而不是新建立了一个线程
sleep和yield
  • sleep之后会进入线程阻塞状态Timed Waiting
    • intercepter可以打断睡眠,睡眠被打断会抛出异常InterruptedException
    • 睡眠结束后的线程未必立即执行
    • TimeUnit.sleep()方法可读性更高
  • yield使得当前线程从Running变为Runnable
    • 具体实现靠任务调度器
  • 区别:
    • yield没有等待时间
    • sleep之后,任务调度器一定不会分配资源
线程优先级
  • 默认5,最大10,最小1
  • 优先级不靠谱,仅仅作为一个提示 (但是是能用)
    • 在任务紧张,才会有用,cpu空闲时候,几乎无用
案例
  • 我们在做服务端使用while(true)的时候,记得使用sleep(50),不要让cpu被100%占用,让出一部分时间
  • sleep适用于无需锁同步的场景
join方法
  • 为什么用join而不用sleep
  • join等待线程结束
  • join实现同步:需要等待结果返回
  • join(n) java喜欢用毫秒
interrupt打断
  • 打断sleep,wait,join:
    • 打断之后,isinterrupted就会变为true,但是,程序内部一直在检查这个值,一旦看到为true就会抛出一个InterruptedException,然后将状态重置为false,程序可以在try-catch中捕获异常来自定义进行打断之后的操作
  • 打断正在运行的线程: isinterrupted就会变为true,线程可以自己检查自己的isinterrupted属性,进行判断然后自定义逻辑
  • interrupted()会清除打断标记
  • isInterrupted()判断是否被打断
两阶段终止模式
  • 如何在t1线程中 “优雅地”终止t2线程
  • 禁止使用stop()方法
    • stop()会真正直接杀死程序,有些资源的锁无法释放,那就完蛋了
  • 绝对禁止System.exit(int)直接终止程序
  • 实现思路:while(ture)中要判断打断标记,如果标记为true就执行释放逻辑。对于抛出异常的情况,我们在catch里面只需要再把打断标记置为true,走判断打断标记的逻辑
打断park线程
  • LockSupport.park()阻塞线程
  • 直接打断
  • 打断标记为true的时候不能再次park()了:park失效
不推荐的方法
  • stop()
  • suspend()
  • resume()
守护线程
  • Java 进程会等待所有非守护线程结束后才会结束,守护线程即使没有执行完,进程也会强制结束
  • 垃圾回收是一种守护线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
    public void test06() throws InterruptedException, ExecutionException {
        Thread t1 = new Thread(()->{
            cout.print("我是新线程1,我要开始叫了");
            Thread thread = new Thread(()->{
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                cout.print("我是小线程,我睡了1s");
            });
            thread.start();
        });
        t1.start();

        t1.join();
        cout.print("t1已经死了结束了");
        Thread.sleep(2000);
        System.out.println("主线程结束");
    }

这个例子你可以清楚的看到,java确实是默认非守护线程 因为t1.join()结束以后,t2仍然执行了打印(注意主程序一定要等一等子线程),不然看不到打印结果

五种状态–操作系统
  • 初始状态
  • 可运行状态(就绪状态)
  • 运行状态: 获得了cpu时间片
  • 阻塞状态:
    • 调用了阻塞API,cpu不会分配资源
    • 与可运行状态的区别是,对阻塞状态的线程来说,只要它们一直不唤醒,调度器就一直不会考虑调度他们
    • 还有活的可能
  • 终止状态:生命周期结束了,死翘翘了
线程的六种状态————JAVA中
  • NEW
  • RUNNABLE
    • 运行
    • 阻塞:执行阻塞api
    • 可运行
  • TERMINATED
    • 结束了
  • BLOCKED: 阻塞
  • WAIRING: 阻塞,如wait()
  • TIMED_WAITING: 有时限的阻塞,如sleep

共享模型之管程

例子引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ThreadTest2 {
    int count = 0 ;

    @Test
    public void test() throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        });
        Thread thread2 = new Thread(()->{
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        });

        thread.start();
        thread2.start();

        thread.join();
        thread2.join();
        System.out.println(count);
    }
}

在多线程同时对一个数据进行写操作就会导致资源共享问题

  • 临界区: 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
  • 竞态条件: 多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

互斥

  • 阻塞式: synchronized, Lock
  • 非阻塞式: 原子变量
    synchronized
  • synchronized代码块包住临界区代码
  • 需要拿到对象锁才能执行临界区代码
  • 其他线程会进行BLOCKED状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Test
public void test() throws InterruptedException {
    Thread thread = new Thread(()->{
        synchronized ( lock){
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    });
    Thread thread2 = new Thread(()->{
        synchronized (lock){
            for (int i = 0; i < 10000; i++) {
                count--;
            }
        }
    });
    thread.start();
    thread2.start();
    thread.join();
    thread2.join();
    System.out.println(count);
}
  • 只要拿到锁,别人就拿不到资源,即使自己没有时间片的分配
  • synchronized执行结束之后,就会唤醒其他线程
  • 原子性
  • 锁对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//基于面向对象的改造
class Room{
    private int count = 0 ;
    
    public void addCount(){
        synchronized (this){
            count++;
        }
    }
    
    public void minusCount(){
        synchronized (this){
            count--;
        }
    }
    
    public int getCount(){
        synchronized (this){
            return count;
        }
    }
}

//可以优化
class Room{
    private int count = 0 ;
    public synchronized void addCount(){
        count++;
    }
    public synchronized void minusCount(){
        count--;
    }
    public synchronized int getCount(){
        return count;
    }
}
  • 上面都是对象锁,锁住的是对象的资源
  • 针对于静态方法等,还有类锁等东西,用于同时对所有对象加锁,这一点很好理解
线程安全分析
  • 非引用的不返回的局部变量不需要加锁
    • 局部变量不返回
    • 不指向其他变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    
      @Slf4j
      public class Thread04 {
    
          void method1(){
              int i = 0;
              i++;
          }
          // 局部变量
          public void test_temp(){
              for (int i   = 0; i <200 ; i++) {
                  method1();
              }
          }
    
    
          ArrayList<String> list = new ArrayList<>();
          void method2(){list.add("a");}
          void method3(){list.remove(0);}
          // 指向对象的局部变量
          @Test
          public void test_point_temp() throws InterruptedException {
    
                
              List<String> tmp = list;
    
              Thread t = new Thread(() -> {
                  for (int i = 0; i < 200_000; i++) {
                      tmp.add("a");
                      tmp.remove(0); // 这里非常容易在并发交错时触发 IndexOutOfBoundsException
                  }
              });
    
              t.start();  
    
              for (int i = 0; i < 200_000; i++) {
                  tmp.add("a");
                  tmp.remove(0);
              }
    
              t.join();  
    
              System.out.println("done, size=" + tmp.size());
          }
    
  • 子类用多线程重写父类方法,会导致线程不安全
    • 访问修饰符可以保护线程安全
      • private禁止子类重写 再加个final
      • private保护会导致外部看不到,final就可以让外部看到
  • 常见线程安全类
    • String
      • substring(),replace()方法,底层是创建了新字符串
    • Integer
    • StringBuffer
    • Random
    • Vector
    • Hashtable
    • java.util.concurrent 即JUC下的类
  • 方法都是原子的
  • 但是他们的组合不是原子的,自己加 synchronized
  • String Integer 等都是不可变变量

  • 实例分析
    • final修饰的也不一定是安全,加final 之后,对象内的属性也可能是可变的, final Data
    • ServiceImpl里面的属性会被共享
    • 切面编程避免出现以下
    • 外星方法:调用的一些方法中使用了多线程。要注意被使用的资源的安全问题,是否支持并发
  • 注意事项
    • 避免给Bean创建可变或者当作局部变量使用的成员变量
      • 尽量使用ThreadLocal去改进
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
      @Aspect
      @Componet
      public class MyAspect{
          private long start = 0L;
    
          @Before("execution(* *(..))")
          public void before(){
              start = System.nanoTime();
          }
          @After("execution(* *(..))")
          public void after(){
              sout(start - System.nanoTime());
          }
      }
    
    
  • 买卖票
    • 最简单的方法就是加synchronized
  • 转账: 涉及到两个对象
    • sychronized(this)肯定不行,因为操作了俩对象,锁一个,没有用
    • 锁Account类,不行,粒度太大了
    • 锁两个对象:注意避免死锁的发生
      • 死锁发生的情况: 两个线程都要使用A和B,一个线程去锁A,另一个去锁B,那么就造成了死锁
      • 解决办法一:固定顺序锁对象
        • 如果都是按照先锁A再锁B,这样就十分方便
      • 解决方法二: 使用ReentrantLock(后面会学)
      • 解决方法三:设置锁的超时机制(我就不信你俩线程能一直在这里争来争去)
Monitor概念

java对象头

20260304-155810.png

20260304-170328.png

  • 主要记住一个对象的锁是加在对象头上的一个区域
  • 知道sychronized 锁对象的时候发生了什么就够了
轻量级锁
  • 一个对象如果有多线程访问,但是大多数时间都是错开的,基本不会发生锁竞争,我们可以用轻量级锁来优化
  • 直接用sychronized(obj)
  • 栈帧中的LockRecord是 轻量级锁的实现

20260304-161854.png

锁重入
  • 如果cas的时候发现已经加有锁了,根据对象指向的锁地址,如果是当前的LockRecord ,就会进行锁重入,给锁的计数+1
  • 同步代码块退出的时候,如果检测到锁的记录为null,说明是锁重入,重置这条锁记录,然后锁计数减一
  • 如果退出的时候值不为null,这时候使用cas把Mark Word的值恢复给对象头
    • 成功,则解锁成功
    • 失败,说明轻量级锁进行了锁膨胀或者已经升级为重量级锁,进入重量级锁解锁流程
      锁膨胀
  • 在有轻量级锁的状态下,另一个线程来加锁,会发现锁对象是00状态
  • 锁膨胀为 重量级锁,创建一个Moniter,把线程挂在EntryList的BLOCKED上
  • 之后当原有线程退出同步代码块的时候,发现解锁失败,找不到原来的Lock Record了
  • 那就去Object中找Moniter地址,然后再设置Owner=null
自旋优化
  • 重量级锁竞争的时候,我们不能直接让他阻塞,应该做自旋优化,提高性能,避免频繁的上下文切换
偏向锁
  • java 9 开始,默认会禁用偏向锁,在java 16被彻底移除了
  • 轻量级锁对同一个线程多次加锁,就会发生多次锁重入,会有多次csa
  • java6引入了偏向锁来做进一步的优化,只有第一次使用cas将线程ID设置到对象的Mark Word之后,之后发现这个线程ID是自己就表示没有竞争,不用cas

20260304-170310.png

这里有一堆东西,懒得学习了,这里直接跳过了,反正也不怎么好

锁消除

jvm底层有 JIT即时编译器,对字节码进行解释的时候会对一些锁进行优化,然会去掉锁,默认打开

锁粗化

尽管锁粗化能够提高性能,但如果粗化过度,可能会导致 锁粒度过大,使得长时间的锁持有影响了其他线程的执行,进而 降低并发性,特别是在多线程竞争的情况下。

这里举不出一个很好的例子,就先这样吧

wait-notify

20260304-181606.png

1
2
3
4
5
6
7
obj.wait();
obj.wait(1000);  //有时间的等待
 
obj.notify();   //随机唤醒一个
obj.notify_All();

//必须获得对象锁才能调用
  • sleep()是Thread的方法,wait()是Thread的方法
  • wait需要强制和synchronized配合使用
  • sleep在睡眠的同时,不会释放锁对象;但是使用wait是会释放锁的
  • 线程状态都是TIMED_WAITING

  • 这里其实有一个典型示例:就是如果使用sleep,会出现抱锁睡觉这种情况,锁的利用效率就太低了
    • 改造为wait() + notify()
    • 但是notify是随机唤醒,会导致虚假唤醒
    • 改进
    1
    2
    3
    4
    
      While(!get_necessity){
          try{obj.wait();}
          catch{}
      }  // 在唤醒的地方加上一个判断,这样就好了
    
Park & unPark

先park再unpark也能唤醒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test22 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            LockSupport.park();
        });
        t1.start();

        LockSupport.unpark(t1);

    }
}

线程状态转换

20260508-201458.png

  • new:未与操作系统关联
  • RUNNABLE: 与操作系统关联了
    • 运行状态
    • 阻塞状态
    • 可运状态
  • Waiting
    • 被 notify interupt notifyAll就->
      • BLOCKED 被阻塞了
      • Runnable
  • BLOCKED

活跃性

死锁
  • 如何定位检测死锁?
    • jps
    • jconsole
哲学家就餐
活锁
  • A在对 i 进行 INC
  • B在对 i 进行 DESC
  • 俩人谁也不让谁,都无法结束
饥饿

ReentrantLock

可重入
可打断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 lock.lock(); // 如果没有拿到锁,就进入了阻塞队列,而且该线程不能被打断

lock.lockInterruptibly(); // 同上,但是可打断


public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("没有获得到锁");
                return;
            }

            try {
                log.debug("获得到锁");
            } finally {
                lock.unlock();
            }
        });
        lock.lock();
        thread.start();
        thread.interrupt();
    }

  • tryLock(): 无参,不阻塞,不可打断
  • tryLock(): 带参数,
    • 抢不到进入AQS
    • parkNanos阻塞
    • 要么等待结束,要么拿到锁,要么被打断
锁超时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Thread thread = new Thread(() -> {
            try {
                if (!lock.tryLock(2, TimeUnit.SECONDS)) {
                    log.debug("获取不到锁");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("无法获得 lock");
                return;
            }

            try{
                log.debug("获取到锁了");
            }finally{
                lock.unlock();
            }
        });
解决哲学家问题
公平与不公平锁
  • 默认不公平,都可以抢
  • 一般我们不需要公平锁
条件变量
  • 类似synchronized中的waitSet()
1
2
3
4
5
6
7
8
9
10
lock.lock();
Condition condition = lock.newCondition();

condition.await();

condition.signal();
condition.signalAll();

lock.unlock();

JMM 共享模型之内存

可见性

  • volatile

原子性

  • synchronized

有序性

  • 指令重排
    • 问题:
    • volatile可以禁止重排序

volatile原理

可见性保障
  • 写屏障
    • 写之前的所有修改(自己和其他所有东西)都会同步到主存
  • 读屏障
    • 在读之后的代码,一定读取了主存的最新数据
有序性保障
  • 写屏障
    • 写之前的代码不会出现在写之后
  • 读屏障
    • 读之后的代码不会出现在读之前
  • 不保障原子性
  • 只保证本线程指令不重排
dcl (double-checked locking)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton{
    private static Singleton instance;
    private Singleton(){}
    public static Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
  • 上面还是有问题的
  • 解决方法: 加上volatile

happen-before规则

  • 有锁,前面的写操作能保证后面的读操作
  • volatile的写,对其他线程的读可见
  • 线程启动前的写,对线程可见
  • 线程结束前对变量的写,对线程结束的读可见
  • 被打断的线程的写对该线程可见
  • 传递性,对于写屏障,同步的不只有volatile变量,连同其他都同步

保护共享资源–无锁并发–乐观锁

  • cas需要volatile修饰
  • cas 会无限自动重试,占用资源
  • ABA问题
    • 使用版本号解决

原子类

  • AtomicInteger
    • .incrementAndGet()
    • .compareAndSet(prev,next)
    • .updateAndGet(value -> value*10)

原子引用

  • AutomicReference
  • AtomicMarkableReference
  • AtomicStampedReference

  • .compareAndSet()
AtomicStampedReference
  • 版本号保障 atomic
AtomicMarkableReference
  • 使用的bool

字段更新器

  • AutomicReferenceFieldUpdater
  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
1
2
3
4
5
 student.name = "dfa";
AtomicReferenceFieldUpdater referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Student.class, Student.class, "name");
referenceFieldUpdater.compareAndSet(student,"dfa","afds");

// 注意name一定要是volatile

原子累加器

  • 性能比AtomicInteger好

  • LongAdder

    • 优化:通过设置多个累加单元,减少cas的竞争次数
LongAdder 源码解读先跳过了
  • 当前状态不太好
  • 后面一定学

unsafe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class MyAtomicInteger{
    static final Unsafe UNSAFE ;
    private volatile int value;
    private static final long valueOffset;

    static {
        Field theUnsafe = null;
        try {
            theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);
            valueOffset   =UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public int getValue() {
        return value;
    }

    public void decrement(int amout) {
        int prev,next;
        do{
            prev = this.value;
            next = prev - amout;
        }while(!UNSAFE.compareAndSwapInt(this,valueOffset,prev,next));
    }

}

不可变对象

  • 保护性拷贝

线程池

ThreadPoolExecutor

高3位用来表示线程池状态,29位表示数量

  • Running
  • Shutdown: 不会接受新任务
  • stop: 直接停掉,正在运行的直接interrupt,阻塞队列全部抛弃
  • Tidying:
  • TERMINATED: 终结状态
构造方法
  • 核心线程数corePoolSize
  • 最大线程数目
  • 生存时间
  • 时间单位
  • 阻塞队列
  • 线程工厂
  • 拒绝策略
    • AbortPolicy: 抛出异常
    • CallerRunsPolicy: 让调用者运行任务
    • DiscardPolicy: 放弃本次任务
    • DiscardOldestPolicy: 放弃最早的任务
newFixedThreadPool
1
2
3
4
5
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • 核心线程数量 == 最大线程数量,没有救急线程
  • 阻塞队列无限长度
newCachedThreadPool
1
2
3
4
5
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • 核心线程数量为 0
  • 全是救急线程,超时等待时间是60s,最大是int个
  • 用SynChronousQueue实现,没有容量,没有线程取的话无法放进去
  • 适合任务数密集,时间短
newSingleThreadExecutor
1
2
3
4
5
6
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • 线程数为1,任务多余1,就会进入无界队列排队
  • 如果线程挂掉了,还会补出一个线程,相比于单线程运行的好处就是在于稳定
  • 装饰器模式,只返回包装后的对象,无法再次set
提交任务
  • void execute()
  • Future submit
  • invokeAll() -> 返回List
  • invokeAny() -> 返回Object
关闭线程池
  • void shutdown();
    • 修改线程池状态
    • 打断空闲线程
    • 会把任务执行完,包括阻塞队列里的
    • 尝试终结
  • shutdownNow()
    • 变为STOP
    • 全部打断
    • 返回没有执行的任务
  • isShutdown()
  • isTerminated()
  • awaitTermination(long timeout,TimeUnit unit);
工作线程
  • 饥饿现象
    • 固定大小的线程池会有饥饿
    • 对于带缓冲的线程池可以减少饥饿的发生
    • 对于不同类型的工作使用不同的线程池
线程池大小
  • cpu 密集型运算
    • cpu + 1
  • I/O 密集型运算
    • 线程数 = 核数 * 期望CPU利用率 * 总时间(cpu计算时间+等待时间)/cpu计算时间

定时任务

  • Timer缺陷
    • 是单线程的
    • 后面任务会等待前面的任务
    • 前面抛出异常,后面直接终止
  • 如果任务执行时间比较长的话会导致,定时失效

    1
    2
    3
    4
    
      ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
      pool.scheduleAtFixedRate(()->{
          System.out.println("running");
      },1,1, TimeUnit.SECONDS);
    
  • 固定时间间隔

    1
    2
    3
    
          pool.scheduleWithFixedDelay(()->{
          System.out.println("running");
      },1,1, TimeUnit.SECONDS);
    

异常处理

  • 默认没有异常
    • 实现的是Runnable
  • 加上(),使用Future
    • 会抛出异常,封装在了Future
    • 也可以接受返回值

应用

Tomact

Fork/Join

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Test02 {
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool(3);
        System.out.println(pool.invoke(new SumTask(0,100)));

    }
}


@Slf4j
class SumTask extends RecursiveTask<Integer> {
    private final int start;
    private final int end;

    public SumTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        if (end - start <= 2) {
            int sum = 0;
            for (int i = start; i <= end; i++) {
                sum += i;
            }
            log.debug("直接计算 {} ~ {}, sum={}", start, end, sum);
            return sum;
        }

        int mid = (start + end) / 2;

        SumTask left = new SumTask(start, mid);
        SumTask right = new SumTask(mid + 1, end);

        left.fork();
        int rightResult = right.compute();
        int leftResult = left.join();

        return leftResult + rightResult;
    }
}

AQS

ReentrantLock