非线程安全场景实例:
public static int count = 0;
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(
new Runnable() {
public void run() {
try {
Thread.sleep((10));
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j < 100; j++) {
count++;
}
}
}
).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count===>" + count);
}
for循环创建两个线程,每次自增100 ,由于非线程安全,count会出现少于200的情况
解决方案一:使用synchronized关键字实现同步
public static int count = 0;
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(
new Runnable() {
public void run() {
try {
Thread.sleep((10));
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j < 100; j++) {
synchronized (cas.class){
count++;
}
}
}
}
).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count===>" + count);
}
缺点:
Synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。
尽管Java1.6为Synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低。
解决方案二:使用原子操作类
所谓原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。例如AtomicBoolean,AtomicInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操作。
//原子操作类替代synchronized
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
new Thread(
new Runnable() {
public void run() {
try {
Thread.sleep((10));
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j < 100; j++) {
count.incrementAndGet();
}
}
}
).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("count===>" + count);
}
原子类底层使用了CAS机制,其原理可自行了解。
CAS的缺点:
1.CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
2.不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
3.ABA问题
这是CAS机制最大的问题所在。