java多线程特性
线程同步具有两个特性:可见性和有序性
java线程间的通信是通过共享变量实现的,假如多个线程共享某个Object对象,该对象被创建在主内存(堆内存)中,每个线程都有自己的工作内存(线程栈). 当线程操作Object对象时,首先从主内存拷贝对象副本到工作内存中,再执行修改变量的代码,最后把工作内存中的Object刷新到主内存中. 假如多个线程都保留Object对象的副本,若某时刻修改Object变量,其他线程也能够看到被修改后的值,此为可见性. 多线程并发执行时,cpu对线程的调度是随机的,最经典的例子是银行汇款问题,我们需要保证取款线程和汇款线程有序的进行,此为有序性.
synchronized
下面用代码说明线程同步的问题
public class TraditionalThreadSynchronized {
public static void main(String[] args) {
final Outputter output = new Outputter();
new Thread() {
public void run() {
output.output("zhangsan");
};
}.start();
new Thread() {
public void run() {
output.output("lisi");
};
}.start();
}
}
class Outputter {
public void output(String name) {
// TODO 为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
// Thread.sleep(10);
}
}
}
程序的运行结果为zhlainsigsan
,显然与预期结果不相符,这就是线程同步问题. 我们希望线程执行完output
方法后再切换到其他线程. java使用synchronized
关键字保证某段代码在不同线程间的执行是互斥的.
- 使用
synchronized
将需要互斥的代码块包含起来
{
synchronized (this) {
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
}
}
- 将
synchronized
加在需要互斥的方法上
public synchronized void output(String name) {
// TODO 线程输出方法
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
}
这种方式相当于用this锁住整个方法内的代码块,当修饰静态方法时,等价于用xxx.class锁住整个方法内的代码块,用synchronized
修饰的代码块可以看做原子操作.
线程执行互斥方法的过程如下:
1. 获取同步锁
2. 清空工作内存
3. 从主内存拷贝对象副本到工作内存
4. 执行代码
5. 刷新主内存
6. 是放同步锁
由此可见synchronized
既保证共享内存的可见性,又保证程序的有序性.
volatile
volatile是第二种java实现线程同步的机制.
class Test {
static volatile int i = 0, j = 0;
static void one() {
i++;
j++;
}
static void two() {
System.out.println("i=" + i + " j=" + j);
}
}
one方法和two方法还会并发执行,但加上volatile
修饰后,可以将共享变量i和j的改变直接响应到主内存中,但在two方法获取到变量i和获取变量j的值的这段时间内,one方法可能会被执行多次,导致j的值大于i. 所以volatile
仅保证线程间共享内存的可见性,不能保证并发的有序性.