前言
1) 多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现。
2)程序的主内存是多个线程共享的,每个线程都有自己的工作内存,工作内存中存储了主内存某些对象的副本。
3)原子性:系统提供的原子性操作包括:lock,unlock,read,load,use,assign,store,write。
4)线程操作共享变量的执行顺序
- 第一步:read,load,将共享变量加载到工作内存;
- 第二步:use,assign,使用和赋值;
- 第三步:store,write,用变量副本刷新共享变量。
5)可见性:当一个共享变量在多个线程工作内存中都存在副本时,其中一个线程修改了共享变量,其他线程也应该能看到被修改后的值。
6)有序性:比如一个账户里有100元,线程A往账户中存10元,线程B从账户中取10元,人们期待的最终结果是账户里的余额为100元,但由于取钱和存钱都不是原子操作,而且系统对线程的调度是随机的,所以可能会出现线程A从主内存读到100,线程B从主内存读到100,线程A存钱后变成110,更新主内存变成110,线程B取钱后变成90,更新主内存变成90,最终主内存共享变量的值为90。这就会造成很严重的错误,因此我们必须保证线程A,B的执行顺序,先存后取或者先取后存,此为有序性。
下面看一个线程同步的问题:
public static void main(String[] args) {
final Account account = new Account();
new Thread() {
public void run() {
account.printName("tailyou");
};
}.start();
new Thread() {
public void run() {
account.printName("lixueyang");
};
}.start();
}
public class Account {
public void printName(String name) {
for (int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
输出:
很显然,输出的结果被打乱了,我们期待的结果是tailyoulixueyang,这就是线程同步问题,我们希望printName()方法被线程执行完后再切换到其他线程。
synchronized
Java中使用synchronized关键字来保证一段代码在多线程执行时是互斥的。有两种方法:
同步代码块:
public void printName(String name) {
synchronized (this) {
for (int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
同步方法:
public synchronized void printName(String name) {
for (int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用synchronized关键字将需要互斥的代码包起来,或者将synchronized关键字加在需要同步的方法上,使用synchronized修饰的方法或者代码块可以看成是一个原子操作。一个线程执行互斥代码的过程如下:
- 获得同步锁;
- 清空工作内存
- 从主内存拷贝对象副本到工作内存;
- 执行临界区代码;
- 刷新主内存数据;
- 释放同步锁。
所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
volatile
对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的。
例如线程A,线程B在进行read,load操作时发现主内存中count的值为5,那么它们都会加载这个最新的值;
线程A对count+1后,更新主内存中的count为6;
由于线程B已经load,所以对count+1后,也会更新主内存中count为6.
所以,即使用volatile关键字修饰count变量,也不能解决并发带来的问题。
volatile只能保证多线程的内存可见性,不能保证多线程的并发有序性。