Volatile 与synchronized都是保证线程安全的,各自的作用与区别:
Volatile可以保证可见性但是无法保证原子性:
可见性:即一个线程在修改一个变量的时候,另一个线程可以读到这个值。
我们大致了解一下Volatile的工作机制:
如图,下面那一块我们模拟是主存,上面两块我们假设是cpu,因为线程实在处理器当中运行的。将下面那大块方块内的一个小方块比作两个线程目前都需要操作的共享变量。
现在开始运作,线程1线程2会将这个在主存当中要操作的变量信息拷贝一份到cpu当中,在cpu当中对其进行修改,修改完毕再将这个数据存入主存当中。因为这两个线程之间不可见并且同时对共享变量进行操作,就会造成县城内安全问题。
加入volatile关键字修饰共享变量,当线程1对这个变量进行操作并操作完成写入主存后,会通知线程2重新从主存中读数据进行操作。利用可见性保证线程安全。性能较synchronized要好很多。
**但是,由于volatile无法保证原子性,像下面这段代码,使用++,我们就无法获得想要的结果。
package atomic;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class Atomic {
volatile int count = 0;
void addm(){
for (int i = 0; i < 10000; i++) {
count++;
}
}
}
public static void main(String[] args) {
Atomic atomic = new Atomic();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(atomic::addm,"thread"+i));
}
threads.forEach((t)->t.start());
threads.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(atomic.count);
}
}
得到的结果为:
我们想要的是创建10个线程,每个线程为我们的共享区域自增10000次,按理来说得到的结果应该是100000,但是volatile只能保证可见性,无法保证原子性。
同样的代码我们利用synchronized:
package atomic;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class Atomic {
int count = 0;
synchronized void addm(){
for (int i = 0; i < 10000; i++) {
count++;
}
}
public static void main(String[] args) {
Atomic atomic = new Atomic();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(atomic::addm,"thread"+i));
}
threads.forEach((t)->t.start());
threads.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(atomic.count);
}
}
这是因为synchronized保证了一次只能有一个线程来访问这个区域,因此可以保证结果的正确。但是synchronized效率要远远低于volatile。
分析:
造成volatile 不同步的问题在于,增量操作符++不是原子的。它在nextSerialNumber域中执行两个操作:首先他读取值,然后它返回一个新的值。如果另外一个线程在这两个操作之间读取这个数,第二个线程就会和第一个线程一起看到同一个值并返回相同的序列号,这就是--安全性失败。
解决这些问题的最好办法就是不共享可变数据,就是要么共享不可变数据,要么不共享可变数据。
此外,还有一种方法解决上述问题,效率在volatile和synchronized之间:
package atomic;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class Atomic {
AtomicInteger count = new AtomicInteger(0);
void addm(){
for (int i=0;i<10000;i++){
count.incrementAndGet();//也是原子的
}
}
public static void main(String[] args) {
Atomic atomic = new Atomic();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(atomic::addm,"thread"+i));
}
threads.forEach((t)->t.start());
threads.forEach((t) -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(atomic.count);
}
}
使用AtomicInterger ,它是java.util.concurrent.atomic的一部分。但是像它的方法当中只有一个原子的操作,不能再加其他操作,再加其他操作,可以使其在每个当中是原子的但是在两个不同的方法之间不是原子的仍然是线程不安全的。