package com.johnnian.thread;
import java.util.Vector;
public class ThreadDemo {
private static Vector< Integer> vector = new Vector< Integer>();
public static void main(String[] args) {
while (true) {
try {
for (int i = 0; i < 100; i++) {
vector. add( i);
}
Thread removeThread = new Thread( new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector. size(); i++) {
vector. remove( i);
}
}
});
Thread printThread = new Thread( new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector. size(); i++) {
Integer item = vector. get(i);
}
}
});
removeThread.start();
printThread.start();
} catch (Exception e) {
// TODO: handle exception
System.out.println(e);
}
}
}
}
运行结果:
Exception in thread "Thread-23" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 141
at java.util.Vector.get(Vector.java:748)
at com.johnnian.thread.ThreadDemo$2.run(ThreadDemo.java:30)
at java.lang.Thread.run(Thread.java:745)
如果对vector对象进行同步操作,修改代码如下:
Thread removeThread = new Thread( new Runnable() {
@Override
public void run() {
synchronized (vector) {
for (int i = 0; i < vector. size(); i++) {
vector. remove( i);
}
}
}
});
Thread printThread = new Thread( new Runnable() {
@Override
public void run() {
synchronized (vector) {
for (int i = 0; i < vector. size(); i++) {
Integer item = vector. get(i);
}
}
}
});
https://github.com/johnnian/Blog/issues/37
一、定义
一个线程安全的代码,要有这样的特征:
二、线程间共享数据类型
2.1 不可变
不可变的对象一定是线程安全的,如 使用 final 关键字修饰的变量、String类型对象、枚举对象等;
2.2 绝对线程安全
在Java中标注自己是线程安全的类,如Vector、HashTable等,大多数都不是绝对的线程安全,可以运行下面测试代码:
运行结果:
如果对vector对象进行同步操作,修改代码如下:
结果一切正常, :)
2.3 相对线程安全
相对的线程安全就是我们通常意义上所讲的线程安全,它需要保证对这个对象单独的操作是线程安全的,我们在调用的时候不需要做额外的保障措施,但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。
2.4 线程兼容和线程对立
线程兼容指的是,原本不是线程安全的,例如 HashTable,通过一些同步方法(同步锁),保证线程安全。
线程对立指的是,无论如何都无法在多线程环境中使用,例如 Thread.suspend() & Thread.resume()方法。
三、线程安全的方法
3.1 同步互斥(阻塞同步)
方法一: 使用synchronized关键字
在Java里面,最基本的互斥同步手段就是synchronized关键字。
synchronized关键字,编译后,在同步块的前后生成 monitorenter 和 monitorexit 这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。
reference参数:
方法二:ReentrantLock(重入锁)
可以用 java.util.concurrent 中的ReentrantLock实现, ReentrantLock是API级别的互斥锁,synchronized是系统级别(Java中重量级操作)。
ReentrantLock可以实现下面三种策略的锁:
方法三: 使用第三方同步互斥锁(适用于分布式系统场景下)
可以使用Zookeeper、Redission分布式锁, 实现在分布式系统下的资源同步。
3.2 非阻塞同步
原理: 先进行正常操作,如果发现有线程操作冲突,则再进行处理。
可以通过 CPU的CAS指令(Compare-and-swap)实现(JDK1.5之后)。
四、锁优化
使用同步互斥锁,会阻塞等待中的线程(使其挂起),而挂起线程、恢复线程都算是重量级操作(这些操作需要转入内核进行),给操作系统的并发与性能带来不小的压力。
JDK1.6后,引入了一系列的锁优化技术,尽量减少线程直接的挂起,主要如下:
4.1 自旋锁 & 自适应自旋锁
自旋锁
可以从上图中看到:自旋锁,将原先使得线程挂起的操作 改为自循环,等到锁资源释放后,再继续。这种操作节省了线程挂起/恢复的开销,但是占用了处理器处理的时间。
JDK1.6后默认开启锁的自旋,当然,锁自旋是有限制的,如果超过自定次数的自旋后还没获得锁,就直接挂起线程(用 -XX: PreBlockPin 参数来配置自旋次数,默认10)
自适应自旋锁
在自旋锁基础上,自旋的时间不固定,而是由前一次同一个锁的自旋状态以及时间决定。
4.2 锁消除
JVM的JIT编译器在编译的时候,对于一些代码写着要同步锁,但是实际不存在数据资源竞争,JVM会消除这种锁。
4.3 锁粗化(扩大锁的范围)
正常情况下,我们总是尽量缩小锁的范围,但是对于一些频繁在同一个对象上加锁的操作,甚至在循环中没有竞争的情况下加锁,这个时候JVM会将锁的范围适当的扩大,节省加锁的次数。
4.4 轻量级锁
如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。
4.5 偏向锁
在无竞争的情况下,把整个同步操作都消除了。