线程安全问题
/**
* 出现有些没加入list的原因:线程一拿走了这个list当时里面有10个数据,添加一个变成11个
* 线程二同时拿走了这个list,当时里面有10个数据,添加一个变成11个(但是正常应该是12个,也就是线程1添加的那个数据没有了)
*/
public class Ticket implements Runnable{
private List<String> list = new ArrayList<>();
public void run(){
for(int i=0;i<300;i++){
try {
// 调用Thread类的sleep方法,休眠50ms,由于父接口没有throws异常,so我们只能用try...catch
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("xxx,");
System.out.println(list.size());
}
}
}
结论:多线程操作同一个全局变量,加上线程锁吧
volatile字段
volatile字段的作用:往常线程都是将某个变量复制一份到自己的线程中,经过volatile字段修饰后,就统一的从内存中读取
原理:volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;
Volatile 变量具有 synchronized
的可见性特性,但是不具备原子性。这就是说线程能够自动发现 volatile 变量的最新值。
- volatile 变量不能用作线程安全计数器。虽然增量操作(
x++
)看上去类似一个单独操作,实际上它是一个由(读取-修改-写入)操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。
用处:如果读操作远远大于写操作,volatile 变量还可以提供优于锁的性能优势。
两个字段的区别
- volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
- volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化