记录 Java 多线程的常见概念和原理
参考:
如何停止一个线程
使用 volatile 变量终止正常运行的线程 + 抛异常法 / Return 法
组合使用 interrupt 方法与 interruptted/isinterrupted 方法终止正在运行的线程 + 抛异常法 / Return 法
使用 interrupt 方法终止 正在阻塞中的 线程
线程安全
当多个线程访问某个类时, 不管运行时环境采用何种调度方式或者这些线程将如何交替执行, 并且在主调代码中不需要任何额外的同步或协同, 这个类都能表现出正确的行为, 那么这个类就是线程安全的.
为什么 wait(), notify()和 notifyAll()被定义在 Object 类里
Wait-notify 机制是在获取对象锁的前提下不同线程间的通信机制. 在 Java 中, 任意对象都可以当作锁来使用, 由于锁对象的任意性, 所以这些通信方法需要被定义在 Object 类里.
为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用
wait/notify 机制是依赖于 Java 中 Synchronized 同步机制的, 其目的在于确保等待线程从 Wait()返回时能够感知通知线程对共享变量所作出的修改. 如果不在同步范围内使用, 就会抛出 java.lang.IllegalMonitorStateException 的异常.
原子性
Java 中只有对基本类型变量的赋值和读取是原子操作, 如 i = 1 的赋值操作, 但是像 j = i 或者 i++ 这样的操作都不是原子操作, 因为他们都进行了多次原子操作, 比如先读取 i 的值, 再将 i 的值赋值给 j, 两个原子操作加起来就不是原子操作了. 所以, 如果一个变量被 volatile 修饰了, 那么肯定可以保证每次读取这个变量值的时候得到的值是最新的, 但是一旦需要对变量进行自增这样的非原子操作, 就不会保证这个变量的原子性了.
volatile 关键字
概念: 为了提高处理速度, 处理器不直接和内存进行通信, 而是先将系统内存的数据读到内部缓存 (L1,L2 或其他) 后再进行操作, 但操作完不知道何时会写到内存. 如果对声明了 volatile 的变量进行写操作, JVM 就会向处理器发送一条 Lock 前缀的指令, 将这个变量所在缓存行的数据写回到系统内存. 一个处理器的缓存回写到内存会导致其他处理器的缓存失效; 当处理器发现本地缓存失效后, 就会从内存中重读该变量数据, 即可以获取当前最新值. 这样针对 volatile 变量通过这样的机制就使得每个线程都能获得该变量的最新值.
特点: 保证了可见性, 不能保证原子性
如何保证线程安全
通过加锁 (Lock/Synchronized) 保证对临界资源的同步互斥访问
使用 volatile 关键字, 轻量级同步机制, 但不保证原子性
使用不变类 和 线程安全类(原子类, 并发容器, 同步容器等)
ThreadLocal 及其引发的内存泄露
ThreadLocal 是 Java 中的一种线程绑定机制, 可以为每一个使用该变量的线程都提供一个变量值的副本, 并且每一个线程都可以独立地改变自己的副本, 而不会与其它线程的副本发生冲突. 每个线程内部有一个 ThreadLocal.ThreadLocalMap 类型的成员变量 threadLocals, 这个 threadLocals 存储了与该线程相关的所有 ThreadLocal 变量及其对应的值, 也就是说, ThreadLocal 变量及其对应的值就是该 Map 中的一个 Entry, 更直白地, threadLocals 中每个 Entry 的 Key 是 ThreadLocal 变量本身, 而 Value 是该 ThreadLocal 变量对应的值.
实线代表强引用, 虚线代表弱引用. map 中的 key 为一个 ThreadLocal 实例. 这个 Map 的确使用了弱引用, 不过弱引用只是针对 key, 每个 key 都弱引用指向 ThreadLocal 对象. 一旦把 threadlocal 实例置为 null 以后, 那么将没有任何强引用指向 ThreadLocal 对象, 因此 ThreadLocal 对象将会被 Java GC 回收. 但是 value 却不能回收, 因为存在一条从 current thread 连接过来的强引用. 只有当前 thread 结束以后, current thread 就不会存在栈中, 强引用断开, Current Thread,Map 及 value 将全部被 Java GC 回收.
得出一个结论就是: 只要这个线程对象被 Java GC 回收, 就不会出现内存泄露. 但是如果只把 ThreadLocal 引用指向 null 而线程对象依然存在, 那么此时 Value 是不会被回收的, 这就发生了我们认为的内存泄露. 比如, 在使用线程池的时候, 线程结束是不会销毁的而是会再次使用的, 这种情形下就可能出现 ThreadLocal 内存泄露.
来源: http://www.bubuko.com/infodetail-3359616.html