Java多线程实践总结
Multiple thread concept application.
参考
https://en.wikipedia.org/wiki/Thread_safety
https://www.uml-diagrams.org/java-thread-uml-state-machine-diagram-example.html
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html
线程安全的定义
-
它是相对于多线程环境而言的,如果是单线程环境谈不上线程安全。
-
它谈论的目标是线程间的共享变量。
多个线程可以同时读写的变量。 这常常是人为引入的复杂。
-
如果代码的执行是竞争条件无关的(不包含线程间共享变量),则 代码执行过程是线程安全的
-
执行的代码包含线程间的共享变量,但是变量被竞争条件保护,这种情况成为条件安全
实现线程安全的几种方式
1) 执行程序可重入
什么是可重入?
从结果上看,不管多少线程同时执行,结构都相同。 从集合论上讲,这是一个一对一的函数。
通常的实现方法是函数调用的依赖全部保存在栈上
。
为什么保存在栈上可以做到可重入?这是栈的性质决定的。
2) 使用本地化的线程变量。
其它线程无法看到当前变量。
3) 使用不可变的对象(immutable objects)
对象的状态在完成构造以后就不会被修改。
4) 排它执行
使用锁机制保证同时只有一个线程执行临界区内的代码。
5) 原子操作
Java 中线程的状态转换
1) 以下是线程各状态的含义
- A. New 状态
线程实例化后,还没有执行start方法的状态。
- B. Runnable
线程执行start方法后,但是还没有没thread scheduler 选中。
- C. waiting
这个状态的线程的核心职能
是等待被通知,然后进入blocked
状态。
线程等待另外一个线程完成执行的动作时的状态。
以下三个方法可以让一个线程处于waiting状态
-
object的wait方法(没有timeout参数)
-
Thead的join方法
-
Locksupport的park方法。
如果一个对象让一个线程处于waiting状态, 这个线程会一直(忠诚地)等待,直到被notify。
- D. blocked
这个状态的对象的核心职责是 等待锁释放
阻塞状态, 它在等待区(entry set 或者 wait set)等待锁的释放。 如果一个方法锁被线程占用,另外一个线程就会进入Blocked状态。
- E. timed_waiting
这个状态的核心职能是等待到达指定时间
,然后进入runnable状态或者blocked状态。
线程等待另外一个线程到达
指定的时间时的状态。
- terminated
线程终结后的状态。
与线程有关的常用方法
1) sleep 方法
- 静态方法,使用当前线程(调用方法的线程)暂停一段时间, 让其他线程有机会继续执行。
这个方法需要捕捉被中断异常 InterruptedException。当线程sleep时被中断会立刻抛出InterruptedException异常。
-
sleep 时间结束后会重新回到Runnable
-
线程sleep后会进入timed_wait状态,在此期间不释放它占用的对象锁。也就是说,其它线程仍然没有机会执行它占有的资源。
2) wait 方法
-
这个方法是对象方法,任何java对象都包含这个方法,与notifiy是一对。
-
必须在synchronized方法区内调用。 原因是
线程等待的对象
被notify以后,线程需要重新进入执行状态。
3) sleep 和 wait方法的区别
-
sleep() 是Thread类的静态方法;wait方法是Ojbect类的方法
-
sleep()进入timed_waiting状态以后,仍然占有对象锁,wait()会释放对象锁。
-
sleep() 的状态转换是timed_waiting到Runnable, wait()被notify以后下一个状态是Blocked,它需要重新等待锁释放。
-
wait()必须被包含在synchronized()方法内
4) sleep与yield方法的区别
-
yield 不设定超时时间, sleep包含一个事件参数。
-
线程sleep以后,任意优先级的对象都可被调度。 yield方法让出执行权以后,只调度相同优先级的线程。
monitor 和 lock 的区别
lock(锁)是JDK对象(object)实现中的一个字段,是对象属性,在运行时中随对象保存在堆中。运行过程中,这个字段会被线程检查。
任何线程在访问一个对象之前,线程必须设置或者拥有这个对象的lock
,当其他线程尝试访问对象实例的时候,它必须等待当前对象的锁处于释放状态。
一个对象要让一个线程拥有它的锁,对象通常有两种实现方式:
-
使用synchronized关键字。
-
调用对象的wait和notify方法。
下面是monitor部分
monitor是锁对应的
的实现。
monitor由jvm编译后字节码中的一对指令(monitorenter/monitorexit)实现。
它的设计目的有2个:
-
保护共享数据。
-
完成线程之间的协作(cooperate. 比如 消费者线程需要等待生产者的状态。