1.多线程基础
为什么会有多线程
本质原因是摩尔定律失效 -> 多核+分布式时代的来临。 JVM、NIO 、MySQL是不是都因为这个问题变复杂? 后面讲的分布式系统,也是这个原因。
为什么会有多线程
多 CPU 核心意味着同时操作系统有更多的并行计算资源可以使用。 操作系统以线程作为基本的调度单元。 单线程是最好处理不过的。 线程越多,管理复杂度越高。 跟我们程序员都喜欢自己单干一样。 《人月神话》里说加人可能干得更慢。 可见多核时代的编程更有挑战。
Java 线程的创建过程
2.Java多线程
线程状态
Thread 的状态改变操作
- Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进TIMED_WAITING 状态,但不释放对象锁,millis 后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
- Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的 CPU 时间片,但不释放锁资源,由运行状态变为就绪状态,让 OS 再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield() 不会导致阻塞。该方法与
sleep() 类似,只是不能由用户指定暂停多长时间。 - t.join()/t.join(long millis),当前线程里调用其它线程 t 的 join 方法,当前线程进入WAITING/TIMED_WAITING 状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者 millis 时间到,当前线程进入就绪状态。
- obj.wait(),当前线程调用对象的 wait() 方法,当前线程释放对象锁,进入等待队列。依靠 notify()/notifyAll() 唤醒或者 wait(long timeout) timeout 时间到自动唤醒。
- obj.notify() 唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll() 唤醒在此对象监视器上等待的所有线程。
Thread 的中断与异常处理
- 线程内部自己处理异常,不溢出到外层。
- 如果线程被 Object.wait, Thread.join 和 Thread.sleep 三种方法之一阻塞,此时调用该线程的interrupt() 方法,那么该线程将抛出一个 InterruptedException 中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用interrupt() 将不起作用,直到执行到 wait(),sleep(),join() 时,才马上会抛出
InterruptedException。 - 如果是计算密集型的操作怎么办?
- 分段处理,每个片段检查一下状态,是不是要终止。
Thread状态
1、本线程主动操作 2、被动:
3.线程安全
多线程执行会遇到什么问题?
多个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。 不进行恰当的控制,会导致线程安全问题 解决方案:同步/加锁
并发相关的性质
原子性:原子操作,注意跟事务 ACID 里原子性的区别与联系 对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的, 要么执行,要么不执行。
只有语句1是原子操作。 多个线程并发问题 类似于 多个事务的并发问
并发相关的性质
可见性:对于可见性,Java 提供了 volatile 关键字来保证可见性。 当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他 线程需要读取时,它会去内存中读取新值。 另外,通过 synchronized 和 Lock 也能够保证可见性,synchronized 和 Lock 能保证 同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改 刷新到主存当中。 volatile 并不能保证原子性
并发相关的性质
有序性:Java 允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影 响到多线程并发执行的正确性。可以通过 volatile 关键字来保证一定的“有序性”(synchronized 和 Lock 也可以)。 happens-before 原则(先行发生原则):
- 程序次序规则:一个线程内,按照代码先后顺序
- 锁定规则:一个 unLock 操作先行发生于后面对同一个锁的 lock 操作
- Volatile 变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
- 传递规则:如果操作 A 先行发生于操作 B,而操作 B 又先行发生于操作 C,则可以得出 A 先于 C
- 线程启动规则:Thread 对象的 start() 方法先行发生于此线程的每个一个动作
- 线程中断规则:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过 Thread.join() 方法结束、
Thread.isAlive() 的返回值手段检测到线程已经终止执行 - 对象终结规则:一个对象的初始化完成先行发生于他的 finalize() 方法的开始
synchronized 的实现
4.线程池原理与应用
|