一、概念
Java内存模型:
Java内存模型规定所有的变量都是存在主内存,每个线程都有自己的工作内存。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主内存进行操作,并且每个线程不能访问其他线程的工作内存。
线程中的三个概念:
1.原子性
在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
x = 10; //语句1,原子性操作
y = x; //语句2,非原子性操作,包含两个操作:读取x值、给y赋值
x++; //语句3,非原子性操作,包含三个操作:读取x值、加1、给x赋值
x = x + 1; //语句4,非原子性操作,包含三个操作:读取x值、加1、给x赋值
Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。synchronized和Lock能够保证同一时刻只有一个线程获取锁然后执行同步代码,那么自然就不存在原子性问题了,从而保证了原子性。
2.可见性
Java提供了volatile关键字来保证可见性,当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主内存,当有其他线程需要读取时,它会去主内存读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
另外,通过synchronized和Lock也能够保证可见性。synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主内存中,从而保证了可见性。
3.有序性
在Java内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,但是会影响到多线程并发执行的正确性。可以通过volatile关键字来保证一定的"有序性"。
volatile关键字禁止指令重排序有两层意思:
- 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行。
- 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
例子:
//x、y为非volatile变量
//flag为volatile变量
x = 2; //语句1
y = 0; //语句2
flag = true; //语句3
x = 4; //语句4
y = -1; //语句5
由于flag变量为volatile变量,那么在进行指令重排序的过程的时候,不会将语句3放到语句1、语句2前面,也不会讲语句3放到语句4、语句5后面。但是要注意语句1和语句2的顺序、语句4和语句5的顺序是不作任何保证的。并且volatile关键字能保证执行到语句3时,语句1和语句2必定是执行完毕了的,且语句1和语句2的执行结果对语句3、语句4、语句5是可见的。
另外,也可以通过synchronized和Lock来保证有序性。synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
二、使用
使用volatile关键字增加了成员变量在多个线程之间的可见性,但是volatile关键字最致命的缺点是不支持原子性。
private static volatile boolean flag = false; //成员变量flag在多个线程之间具有可见性
三、volatile与synchronized的比较
- volatile解决的是变量在多个线程之间的可见性,而synchronized解决的是多个线程之间访问资源的同步性。
- volatile能保证数据的可见性,但不能保证原子性,而synchronized可以保证原子性,也可以间接保证可见性,因为它将主内存和工作内存中的数据做同步。
- 多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
- volatile是线程同步的轻量级实现,只能修饰于变量,而synchronized可以修饰方法以及代码块。
四、例子
volatile不支持原子性:
static class MyThread extends Thread {
private static volatile int count = 0; //保证可见性,但是不支持原子性
private MyThread(String name) {
setName(name);
}
@Override
public void run() {
Log.d(TAG, "zwm, thread: " + getName() + " run start");
for(int i=0; i<10000; i++) {
count++; //非原子性操作,包含三个操作:读取x值、加1、给x赋值
}
Log.d(TAG, "zwm, count: " + count);
Log.d(TAG, "zwm, thread: " + getName() + " run end");
}
}
//测试代码
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.start();
MyThread thread3 = new MyThread("thread3");
thread3.start();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Log.d(TAG, "zwm, count: " + MyThread.count);
}
}, 5000);
//输出
2019-02-11 17:11:04.063 zwm, thread: thread1 run start
2019-02-11 17:11:04.070 zwm, thread: thread2 run start
2019-02-11 17:11:04.073 zwm, count: 10397
2019-02-11 17:11:04.073 zwm, thread: thread2 run end
2019-02-11 17:11:04.094 zwm, thread: thread3 run start
2019-02-11 17:11:04.096 zwm, count: 25119
2019-02-11 17:11:04.096 zwm, thread: thread1 run end
2019-02-11 17:11:04.097 zwm, count: 28435
2019-02-11 17:11:04.097 zwm, thread: thread3 run end
2019-02-11 17:11:09.069 zwm, count: 28435 //最终结果不为30000!
使用synchronized保证原子性:
static class MyThread extends Thread {
private static int count = 0;
private MyThread(String name) {
setName(name);
}
@Override
public void run() {
Log.d(TAG, "zwm, thread: " + getName() + " run start");
for(int i=0; i<10000; i++) {
synchronized (MyThread.class) {
count++;
}
}
Log.d(TAG, "zwm, count: " + count);
Log.d(TAG, "zwm, thread: " + getName() + " run end");
}
}
//测试代码
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.start();
MyThread thread3 = new MyThread("thread3");
thread3.start();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Log.d(TAG, "zwm, count: " + MyThread.count);
}
}, 5000);
//输出
2019-02-11 17:22:35.842 zwm, thread: thread1 run start
2019-02-11 17:22:35.842 zwm, thread: thread2 run start
2019-02-11 17:22:35.844 zwm, thread: thread3 run start
2019-02-11 17:22:35.858 zwm, count: 26193
2019-02-11 17:22:35.859 zwm, thread: thread1 run end
2019-02-11 17:22:35.860 zwm, count: 29166
2019-02-11 17:22:35.860 zwm, thread: thread2 run end
2019-02-11 17:22:35.860 zwm, count: 30000
2019-02-11 17:22:35.861 zwm, thread: thread3 run end
2019-02-11 17:22:40.851 zwm, count: 30000
使用Lock保证原子性:
static class MyThread extends Thread {
private static int count = 0;
private static Lock lock = new ReentrantLock();
private MyThread(String name) {
setName(name);
}
@Override
public void run() {
Log.d(TAG, "zwm, thread: " + getName() + " run start");
for(int i=0; i<10000; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
Log.d(TAG, "zwm, count: " + count);
Log.d(TAG, "zwm, thread: " + getName() + " run end");
}
}
//测试代码
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.start();
MyThread thread3 = new MyThread("thread3");
thread3.start();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Log.d(TAG, "zwm, count: " + MyThread.count);
}
}, 5000);
//输出
2019-02-11 17:27:03.621 zwm, thread: thread3 run start
2019-02-11 17:27:03.623 zwm, thread: thread1 run start
2019-02-11 17:27:03.626 zwm, thread: thread2 run start
2019-02-11 17:27:03.711 zwm, count: 18295
2019-02-11 17:27:03.711 zwm, thread: thread3 run end
2019-02-11 17:27:03.725 zwm, count: 25110
2019-02-11 17:27:03.725 zwm, thread: thread1 run end
2019-02-11 17:27:03.726 zwm, count: 30000
2019-02-11 17:27:03.726 zwm, thread: thread2 run end
2019-02-11 17:27:08.621 zwm, count: 30000
使用AtomicInteger保证原子性:
static class MyThread extends Thread {
private static AtomicInteger count = new AtomicInteger();
private MyThread(String name) {
setName(name);
}
@Override
public void run() {
Log.d(TAG, "zwm, thread: " + getName() + " run start");
for(int i=0; i<10000; i++) {
count.getAndIncrement();
}
Log.d(TAG, "zwm, count: " + count);
Log.d(TAG, "zwm, thread: " + getName() + " run end");
}
}
//测试代码
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.start();
MyThread thread3 = new MyThread("thread3");
thread3.start();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Log.d(TAG, "zwm, count: " + MyThread.count);
}
}, 5000);
//输出
2019-02-11 17:32:03.508 zwm, thread: thread1 run start
2019-02-11 17:32:03.509 zwm, thread: thread2 run start
2019-02-11 17:32:03.509 zwm, thread: thread3 run start
2019-02-11 17:32:03.516 zwm, count: 27025
2019-02-11 17:32:03.516 zwm, thread: thread2 run end
2019-02-11 17:32:03.516 zwm, count: 27579
2019-02-11 17:32:03.516 zwm, thread: thread3 run end
2019-02-11 17:32:03.517 zwm, count: 30000
2019-02-11 17:32:03.517 zwm, thread: thread1 run end
2019-02-11 17:32:08.515 zwm, count: 30000