轻量级锁,轻量级锁会尝试使用CAS操作来获得锁,如果轻量级锁获得失败,说明存在竞争。但是也许很快就能获得锁,就会尝试自旋锁,将线程做几个空循环,每次循环时都不断尝试获得锁。如果自旋锁也失败,那么只能升级成重量级锁。
可见偏向锁,轻量级锁,自旋锁都是乐观锁。
3. 一个错误使用锁的案例
public class IntegerLock {
static Integer i = 0;
public static class AddThread extends Thread {
public void run() {
for (int k = 0; k < 100000; k++) {
synchronized (i) {
i++;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
AddThread t1 = new AddThread();
AddThread t2 = new AddThread();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
一个很初级的错误在于,在 高并发Java(7):并发设计模式提到,Interger是final不变的,每次++后,会产生一个新的 Interger再赋给i,所以两个线程争夺的锁是不同的。所以并不是线程安全的。
4. ThreadLocal及其源码分析
这里来提ThreadLocal可能有点不合适,但是ThreadLocal是可以把锁代替的方式。所以还是有必要提一下。
基本的思想就是,在一个多线程当中需要把有数据冲突的数据加锁,使用ThreadLocal的话,为每一个线程都提供一个对象实例。不同的线程只访问自己的对象,而不访问其他的对象。这样锁就没有必要存在了。
package test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
private static final SimpleDateFormat sdf = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
public static class ParseDate implements Runnable {
int i = 0;
public ParseDate(int i) {
this.i = i;
}
public void run() {
try {
Date t = sdf.parse("2016-02-16 17:00:" + i % 60);
System.out.println(i + ":" + t);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
es.execute(new ParseDate(i));
}
}
}
由于SimpleDateFormat并不线程安全的,所以上述代码是错误的使用。最简单的方式就是,自己定义一个类去用synchronized包装(类似于Collections.synchronizedMap)。这样做在高并发时会有问题,对 synchronized的争用导致每一次只能进去一个线程,并发量很低。
这里使用ThreadLocal去封装SimpleDateFormat就解决了这个问题
package test;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
public static class ParseDate implements Runnable {
int i = 0;
public ParseDate(int i) {
this.i = i;
}
public void run() {
try {
if (tl.get() == null) {
tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
Date t = tl.get().parse("2016-02-16 17:00:" + i % 60);
System.out.println(i + ":" + t);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
es.execute(new ParseDate(i));
}
}
}
每个线程在运行时,会判断是否当前线程有SimpleDateFormat对象
if (tl.get() == null)
如果没有的话,就new个 SimpleDateFormat与当前线程绑定
tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
然后用当前线程的 SimpleDateFormat去解析
tl.get().parse("2016-02-16 17:00:" + i % 60);
一开始的代码中,只有一个 SimpleDat