乐观锁,被悲观锁与无同步方案
作者:互联网
一.乐观锁
乐观:总是认为自己在访问数据的时候不会遇上其他人来修改数据,因此不加锁,不会把别人阻塞住,即使用非阻塞同步。
1.CAS机制(compare and swap):
使用三个操作数,分别是内存地址A,预期原值B,新值C。
我理解的过程如下,如果一个线程带着新值C想要修改原值B,则该机制会先比对内存地址A中的预期原值,如果这个值是B,那么就允许这个线程把B值修改为C。如果有多个线程同时进来,例如线程1就是刚刚带着新值C进来的线程,线程2带的新值是D。上面说到线程1成功的修改了原值B,让这个内存中的值变成了C。注意!!注意!!注意!!预期原值是不会变的,他还是B!这个时候,线程2带着新值D欢欢喜喜的想要修改,但是发现内存A中的值已经变成了C,不再是原来的预期原值B了,这时候就不允许修改了,线程2就只能重新尝试或者进行其他操作了。
这就是CAS机制,他提供了原子性,能保证多个线程进来,但只有一个线程能成功完成操作修改数据,其他值都会失败。失败后的线程要如何处理就不关CAS的事了。因为要自己处理失败后的操作,所以用起来会比锁要麻烦,因此适用于多读的场景。Atom原子类是CAS机制的体现(一丝了解,不是很熟,有空再看)。
可能会出现的问题(ABA):如果一次操作将原值改成了B,然后又改成了A,那CAS就会误认为他没有被改变过。要解决可以用带标记的原子类或者传统的互斥同步。
2.volatile关键字
volatile关键字仅能提供共享变量的可见性,即在共享变量被修改了之后,这个变量的值会立即被加载回主内存,各个工作内存的就可以读到这个刚刚被修改的新值。
二.悲观锁
悲观:总是认为自己在访问数据的时候会有其他人来修改数据,因此每次都加锁,会把别人阻塞,即使用阻塞同步。
1.JVM实现的synchronized
可以声明代码块,方法,类和静态方法,实现同步
2.J.U.C中的ReentrantLock
要先获取锁对象:private Lock lock = new ReentrantLock();
再调用lock方法加锁,执行完成之后要调用unlock方法释放锁
3.synchronized和ReentrantLock的比较
1)性能差不多
2)正在等待ReentrantLock锁的线程如果长时间等不下去可以放弃等待,synchronized不行
3 ReentrantLock可以绑定多个条件(Condition对象,Condition对象可以在线程的run方法内调用await(),signal()等方法操作线程)
三.无同步方案
无同步方案就是不需要同步,就是不共享变量
1.局部变量
局部变量是线程私有的,在虚拟机栈中,多个线程访问同一个方法的时候,是不会存在线程安全问题的。
2.ThreadLocal类
字面含义是“本地线程”,我的理解是“本地”即“自己的”,“自己线程里的”,就是将共享变量放到自己的线程里,创建出一个变量副本,在自己的线程里修改变量副本(此时是自己线程里的变量了)的值,不会有线程安全的问题。实际上这个不再是共享的了,他是我线程私有的一个变量副本。
每个Thread都有一个ThreadLocal.ThreadLocalMap对象,键值对分别是ThreadLocal对象和变量副本的值
ThreadLocal对象就是共享变量对象
使用方法:
public static void main(String[] args) {
//threadLocal是String类型的共享变量
ThreadLocal<String> threadLocal = new ThreadLocal<>() ;
//主线程调用threadLocal的set方法,在线程内部创建变量副本,并设置值
threadLocal.set("我是主线程");
Thread thread1 = new Thread() {
@Override
public void run() {
//在线程1中生成变量副本并设置值
threadLocal.set("我是线程1");
try {
//设置个休眠,让thread2执行
//以此证明thread2调用threadLocal对象的set方法不会影响到thread1中的值
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1获取共享变量副本:"+threadLocal.get());
}
} ;
Thread thread2 = new Thread() {
@Override
public void run() {
//线程2生成变量副本并设置值
threadLocal.set("我是线程2");
System.out.println("线程2获取共享变量副本:"+threadLocal.get());
}
} ;
thread1.start();
thread2.start();
try {
//主线程挂起,先执行thread1和thread2
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程获取共享变量:"+threadLocal.get());
System.out.println("主线程结束");
}
输出结果:
线程2获取共享变量副本:我是线程2
线程1获取共享变量副本:我是线程1
主线程获取共享变量:我是主线程
主线程结束
证明了只需要一个threadLocal对象,每个线程有自己的变量副本,互不相干。
关于线程,很大程度上还只是懂理论,相当于纸上谈兵,自己实践的并不多,有空补上。
后续再补充
标签:线程,变量,threadLocal,副本,共享,原值,修改 来源: https://blog.csdn.net/weixin_40616523/article/details/86427962