概述
线程:在 Java 的世界中 JVM 中只有 Thread
代表线程
Thread
-
Thread
类的每一个实例代表一个 JVM 中的线程 -
Thread.start()
之后,JVM 中就增加一个线程,增加一个执行流,增加了一套方法栈 - 不同的执行流的同步执行是一切线程问题的根源
- 主线程的入口一定是
Main
方法,其余新开的线程的最底部一定是Thread.run
方法
生命周期
java.lang.Thread.State
Thread Life Cycle
-
BLOCKED
=> synchronized -
WAITING
|TIMED_WAITING
=> Object.wait
Runnable & Callable
- Runnable | Callable 只代表一个任务, 可以被任何一个线程执行
- Runnable 的限制
- 不能丢出来异常,Runnable 方法签名上没有
Throws
- Runnable 没有返回值
- 不能丢出来异常,Runnable 方法签名上没有
基于 Runnable 的问题,创造了 Callable,Callable 就是 Runnable 的高级版本,Callable 解决了 Runnable 的问题
Java Memory Modal
JMM => Java 线程模型 => 方法中的局部变量是私有的,其他都是公有的
当两个线程执行时,方法栈是私有的,方法栈中有局部变量,局部变量也是私有的,当在线程中 new Object()
的时候,每个线程都会在 Heap 中生成一个 Object
,方法栈中的局部变量指向 Heap 中的 Object
,两个线程会生成两个 Object
由于 CPU 和 Main Memory 交换是很慢的,CPU 进行一次内存寻址大约是 1 微秒,大约相当于 CPU 的 1000 - 3000 个时钟周期。所以 Java 模型允许每一个线程自己有一份私有的副本,CPU 和私有的副本进行读写。Java 线程模型定期的把每个线程的私有变量同步给 Main Memory
上述导致的问题:
- 线程A更新一个共有变量globalVariable,线程B感知不到,需要等到线程A同步至 Main Memory,并且线程B的副本同步 Main Memory,才能感知到 globalVariable 的更新
- 在现代 CPU 中,如果两条指令没有依赖关系,编译器可以进行一个指令重排
volatile
volatile
关键字声明的变量在多线程中,所有的线程写该变量的时候都会直接写入 Main Memory,对于共享变量所做的修改会立刻写回 Main Memory。当读该变量时,会立刻从 Main Memory 中读取刷新至当前 CPU 的副本中,再从副本中读取
- 可见性,并非原子性
- 写入 volatile 变量会直接写入 Main Memory
- 从 volatile 变量读取会直接读取 Main Memory
- 非常弱的同步机制
- 禁止指令重排 => 编译器和处理器都可能对指令进行重排,以提高效率,提高程序执行的速度,在多线程中会导致问题 => 可以保证自己的读写前后插入屏障,使得不会发生指令重排
- 有同步的时候无需
volatile
=> synchronized | Lock | AtomicInteger => 既保证可见性又保证原子性
JUC
JUC => java.util.concurrent => Java 并发工具包
Java 的协同机制一定要和锁(monitor | 监视器 | 管程)一起来工作
Java 为多线程提供了语音级的支持
-
sychoronized
关键字 -
Thread
类 Object.wait | Object.notify | Object.notifyAll
多线程问题
- 正确性
- 安全 => 竞争条件 & 死锁
- 协同 => 同时、随机执行的线程,如何让他们协同工作?
- 效率 & 易用性
- 执行的越快越好
- 用起来越不容易出错越好
synchronized
- Java 语音级的支持,1.6 之后性能极大的提升
- 字节码层面的实现 =>
MONITORENTER
+MONITOREXIT
- 锁住的是什么?
# 锁住对象 LOCK public static void Main() { synchronized(LOCK) { } } # 锁住 Class 对象 public synchronized static void foo() { } # 锁住 this (当前对象) public synchronized void bar() { } // 等价于 => 效果等价,字节码不等价 public void bar() { synchronized(this) { } }
- 非公平锁 => 谁获得锁取决于操作系统的调度
- 可重入锁
缺点
-
synchronized
不能知道当前锁是否能被拿到 - 只有悲观锁、排他锁,没有乐观锁、共享锁
- 性能相对稍差
Lock & Condition
- 同一个锁可以有多个条件
- 读写分离 =>
ReadLock
|WriteLock
=> 读写锁 => 可以实现多个线程读,只有一个线程写 -
Lock.tryLock()
=> Acquires the lock only if it is free at the time of invocation. =>boolean tryLock()
- 可以方便的实现更加灵活的优先级 & 公平性 => 公平锁 & 非公平锁
-
ReentrantLock
=>sync
+NonfairSync
+FairSync
=> 默认的ReentrantLock
是非公平锁
ReentrantLock - 锁的重入问题 => 当一个线程重复的去获取锁,是否可以获取到 =>
Lock
支持可重入锁
private static Lock LOCK = new ReentrantLock();
public static void Main() {
LOCK.lock();
LOCK.lock();
LOCK.lock();
# lock 了几次就要 unlock 几次,不然其他线程获取不到锁
LOCK.unlock();
LOCK.unlock();
LOCK.unlock();
}
CountDownLatch
- 倒数闭锁
- 用于协调一组线程的工作
-
countDown
+await
CyclicBarrier
-
CyclicBarrier
=> 循环屏障 => 等待所有线程到达一个屏障之后所有线程再一起出发 await
Semaphore
- 信号量 => 当信号量为1时,就是锁,排它锁就是一个信号量为1的特殊的锁
-
acquire
+release
BlockingQueue & BlockingDeque
- 传统的集合框架的操作要么正常返回,要么丢出异常,BlockingQueue | BlockingDeque 提供了一种等待的可能
- queue => 先进先出
- deque => double ended queue => 头尾都可以进出
-
BlockingQueue
=> capacity +put
+take
线程池 ThreadPoolExecutor
- 线程的代价太昂贵
- 使用线程池完成多线程协同和任务分解
new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
-
corePoolSize
=> the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set => 核心员工数量 -
maximunPoolSize
=> the maximum number of threads to allow in the pool => 最大招募的员工数量 -
keepAliveTime
&unit
=> 员工闲下来之后多久炒掉他们-
keepAliveTime
=> when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating -
unit
=> the time unit for the keepAliveTime argument
-
-
workQueue
=> the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method => 订单队列 -
threadFactory
=> the factory to use when the executor creates a new thread => 造人的工厂 -
handler
=> the handler to use when execution is blocked because the thread bounds and queue capacities are reached => 订单实在太多的处理策略
Rejected Execution Handler Policy
RejectedExecutionHandler-
AboutPolicy
=> A handler for rejected tasks that throws a RejectedExecutionException -
CallerRunsPolicy
=> A handler for rejected tasks that runs the rejected task directly in the calling thread of the execute method, unless the executor has been shut down, in which case the task is discarded -
DiscardOldestPolicy
=> A handler for rejected tasks that discards the oldest unhandled request and then retries execute, unless the executor is shut down, in which case the task is discarded -
DiscardPolicy
=> A handler for rejected tasks that silently discards the rejected task
ExecutorService
一个多线程执行器框架,最常用的实现是线程池。屏蔽了线程的细节,提供了并发执行任务机制
Future
- 代表未来才会发生的事情
- Future 本身是立即返回的
-
Future.get
会阻塞并返回执行结果,并抛出可能的异常 => 使用get
异常会从其他线程中转移到当前线程,方便处理异常
知识点
- Runnable | Callable 都不是线程,只是一个任务,只是一小段代码,这个任务会被哪个线程执行取决于线程池本身
- 异常的抛出是顺着方法栈往外抛的
- 图灵机
- 图灵完备性
- 线程是一种很昂贵的资源,不能频繁的创建它。在 IO 密集型应用中,创建线程数量最大值约为 CPU 数量 + 1
- 创建线程池里有
new Thread
=> TODO: 找到相应代码 -
Object.wait()
和Thread.sleep()
有什么区别?
-
Object.wait()
=> 持有锁之后调用锁的wait
方法后 sleep,之后放弃锁,等待被notify
-
Thread.sleep()
=> sleep 的时候持有锁,sleep 可以不持有锁,当不持有锁的时候sleep
,不会影响其他线程工作
- TODO
- 排它锁
- 共享锁
- 乐观锁
- 悲观锁
- 公平锁
- 非公平锁
网友评论