【java多线程】synchronized和volatile
文章目录
- 一.synchronized
- 1.synchronized使用的方法
- 2.注意
- 3.不要以字符串作为锁的对象
- 4.`synchronized`锁的是什么?
- 二、volatile
- 1.引出问题
- 2. `volatile`使用方法
- 3.volatile原理
- 4.volatile三大特性
- 三、volatile和synchronized的区别
- 1.区别
- 2.解决原子性问题--原子型数据类型
- 3.让对象类型数据具有原子型
一.synchronized
1.synchronized使用的方法
- 可以直接修饰代码块
synchronized (this) {
//代码块
}
- 调用可能出现并发问题的方法
synchronized (this) {
method();
}
- 修饰方法
public synchronized void method() {
//...
}
2.注意
当synchronized修饰方法时:
- 非静态方法中,synchronized锁当前对象:
this
- 静态方法中,synchronized锁的是当前类的字节码对象
User.class
3.不要以字符串作为锁的对象
- 有如下代码
String s1 = "hello";
String s2 = "hello";
void m1() {
synchronized (s1) {
}
}
void m2() {
synchronized (s2) {
}
}
- 因为
s1
和s2
的创建都使用的是直接赋值,此时的hello
会创建在常量池中,实际上s1
,s2
都指向的是同一个对象,被s1、s2引用。 - 所以,此时的
synchronized (s1)
和synchronized (s2)
实际上锁的都是同一个对象。 - 因此,使用
synchronized
锁的时候最好不要锁字符串类型。 - 如果非要锁字符串,那么使用
new String ("hello")
的方法来创建字符串,因为这样创建出来的字符串就是两个堆内存中的对象了。
4.synchronized
锁的是什么?
synchronized
锁的是堆内存中new
出来的对象,而不是栈中的引用。
二、volatile
1.引出问题
两个线程操作同一个主存数据时,会先将主存中的数据复制到自己的内存区域,将数据修改后,再写回主存。
两线程同时复制到自己内存后:
- 线程1将数据修改
a=1
后,写回主存 - main线程很忙,一直在进行while(true) 循环,没有时间再去主存中读取新的数据,所以main线程缓存中的数据还是
a=0
- 为了解决这个问题,就引出了
volatile
2. volatile
使用方法
- 直接将
volatile
关键字加在要操作的数据上
private volatile int a = 0;
3.volatile原理
- 当
线程1
将数据修改后,会通知main线程
:“你的数据已经过期了” - 此时
main线程
会从主存中重新读取
新的数据 - 这也就是volatile三个特性的-
保证可见性
4.volatile三大特性
- 保证可见性
- 不保证原子性
- 禁止指令重排
三、volatile和synchronized的区别
1.区别
- volatile是轻量级的,synchronized是是重量级的
- volatile保证内存可见性,而不保证原子性(解决此问题的办法:就是使用原子数据)
- synchronized既保证内存可见性,也保证原子性
2.解决原子性问题–原子型数据类型
- 在
java.util.concurrent.atomic
包下,有基本数据类型对应的原子型的数据类型,类似于基本数据类型的包装类型。
3.让对象类型数据具有原子型
- 还是在
java.util.concurrent.atomic
包下,有一个类,可以使对象类型的数据具有原子性:AtomicReference
//创建两个对象
User zhangsan = new User("zhangsan");
User sili = new User("sili");
//创建原子操作对象
AtomicReference<User> atomicReference = new AtomicReference<User>();
//比较并交换
boolean b = atomicReference.compareAndSet(zhangsan, sili);
System.out.println(b);