多线程是java里比较复杂的一个技术,多线程有2个重要的概念:可见性与原子性。
因为线程是有缓存的,它里面对象的值可能会与主存里的不一样。这样就会导致一个问题:线程用的不是最新的值,从而导致错误。
可见性是指同一个对象在不同的线程之间表现一致,具有相同的值。怎么实现可见性呢?线程在读一个变量的时候,先去主存把值同步过来,在写一个变量的时候,把新值同步到主存,这样可以保证各个线程用的都是最新的值。
一个i++的简单语句,在我们看来已经不能再拆分了,但在虚拟机字节码层面却不是这样,这样一个简单的java语句会被拆分成3个操作,先取值,再加1,再存值。线程有可能在取值完,还没加1的时候,时间片就用完了,被置换出CPU,等待下一个时间片。
原子性就是说我们要保证一系列的指令要么不执行,要么一次执行完。如果不保证原子性,会有什么问题呢?不保证原子性就保证不了可见性。假设我们有多个线程同时让一个变量自增,当某一个线程自增的指令没有执行完时间片就耗尽了,这时它就不会去更新主存里的值,当另一个线程运行时,它拿到的还是老的值,这时错误就发生了。Java提供了AtomicInteger等类用来保证原子性。
如果我们有一个变量是多个线程同时读写的,如何保证它是线程安全的呢?
1、可以用synchronized关键字来修饰所有的读写方法,synchronized关键字既可以保证原子性,又可以保证可见性。像Java的Vector类和HashTable都是这么做的。
2、可以用volatile和AtomicXXX来实现,volatile用来保证可见性,AtomicXXX类保证了原子性。
接下来,一个经典的多线程累加让你看看效果:
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static void main(String[] args) {
new Test().start();
}
public static class Test {
private int a = 0;
private volatile int b = 0;
private int c = 0;
private volatile AtomicInteger d = new AtomicInteger(0);
public void start() {
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
a++;
b++;
plus();
d.incrementAndGet();
}
}
}).start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
System.out.println("d = " + d.get());
}
public synchronized void plus() {
c++;
}
}
}
运行结果:
a = 9905
b = 9748
c = 10000
d = 10000