乐观锁,被悲观锁与无同步方案

一.乐观锁

乐观:总是认为自己在访问数据的时候不会遇上其他人来修改数据,因此不加锁,不会把别人阻塞住,即使用非阻塞同步。

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对象,每个线程有自己的变量副本,互不相干。

关于线程,很大程度上还只是懂理论,相当于纸上谈兵,自己实践的并不多,有空补上。

后续再补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值