并发操作:同一时间可能有多个事务对同一数据进行读写操作
他们都是用来解决并发编程中的线程安全问题的,不同的是
1、lock是一个接口,Lock是显式锁,加锁解锁都需要我们使用java代码实现。
而synchronized是java的一个关键字。是隐式锁,加锁解锁是JVM管理的。
2、synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁;
而lock发生异常时,不会主动释放占有的锁,必须手动来释放锁(finally中释放),可能引起死锁的发生。
3synchronized能锁住类、方法和代码块,而Lock是块范围内的
2什么是行锁?行锁指的是一行数据进行加锁,行锁又分为共享锁和排他锁
共享锁:允许事务读取一行数据,允许多个事务同时获取该锁。(反正是读取,管你怎么看)
排他锁:允许事务更新和删除一行数据,具有互斥性,某个事务想要获取到锁,就必须等待其他事务释放操作该数据的锁。
3悲观锁和乐观锁?悲观锁和乐观锁是两种思想,用于解决线程并发场景下资源竞争的问题
-
悲观锁,每次操作数据的时候,都会认为会出现线程并发问题(会同时对数据进行修改),一开始就会进行上锁,执行完成过后才会释放锁,线程安全,性能较低,适用于写多读少的场景
-
乐观锁,每次操作数据的时候,认为不会出现线程并发问题。不会使用加锁的形式而是在提交更新数据的时候,判断一下在操作期间有没有其他线程修改了这个数据,如果有,则重新读取,再次尝试更新(Retry),循环上述步骤直到更新成功 。否则就执行操作。线程不安全,执行效率相对较高,适用于读多写少的场景
悲观锁一般用于并发冲突概率大,对数据安全要求高的场景。【会进行加锁,不会出现线程安全问题。乐观锁在执行更新时频繁失败,需要不断重试,反而会浪费CPU资源】
乐观锁一般用于高并发,多读少写的场景【乐观锁不会进行加锁,其他线程可以进行同时访问,提高响应效率】
乐观锁的使用场景
ES是采用的乐观锁,因为ES的使用场景为读多写少,基本上不会出现线程并发问题
4什么是闭锁(CountDownLatch)?闭锁的使用场景:多个线程执行完成之后才能执行某个方法。
5什么是可重入锁(Reentrant Lock)?指的是某个线程已经获取到某个锁,可以再次获取锁而不会出现死锁【两个或两个以上的进程在执行过程中,因争夺系统资源而产生相互等待的现象。】。再次获取锁的同时会判断当前线程是否持有锁,如果有,就对锁的次数进行加1,释放锁的时候加了几次锁,就需要释放几次锁。
作用:可以解决多个线程对同一共享资源进行操作出现的线程并发问题,可以降低死锁问题。
在实际业务中,比如说抢购下单,在支付超时的时候,需要退抢购的库存的同时,下单成功了,就会导致超卖的问题。
所以采用可重入锁,在退库存的业务方法和下单的业务方法中都加上相同的Reentrant Lock,当一个业务方法获取到锁过后,另一个方法就需要等待当前锁的释放才能够进行执行,这样就避免了线程并发问题,导致数据不一致问题。
一个简单的锁分布式锁案例如下:
@Autowired private RedissonClient redissonClient; @Test public void testLock1(){ RLock rLock = redissonClient.getLock("lock_stock"); rLock.lock(); //阻塞式等待,过期时间30s try{ //这里面写真正的业务,如果30秒内没有执行完当前业务,底层会有一个监控锁的看门狗会定时去检 //测,然后当当前锁进行续期,直到执行结束。这样做的好处是防止在程序执行期间锁自动过期被删除问题 //当业务执行完成不再给锁续期,即使没有手动释放锁,锁的过期时间到了也会自动释放锁 System.out.println("加锁成功...."); System.out.println("执行业务...."); }finally { rLock.unlock(); System.out.println("释放锁...."); } }
另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了,如下
@Test public void testLock2(){ RLock rLock = redissonClient.getLock("lock_stock"); // 加锁以后10秒钟自动解锁 // 无需调用unlock方法手动解锁 rLock.lock(10, TimeUnit.SECONDS); try{ System.out.println("加锁成功...."); System.out.println("执行业务...."); }finally { rLock.unlock(); System.out.println("释放锁...."); } }6Synchronized 锁升级原理
JDK1.6之前,synchronized是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实 现,监视器锁的实现依赖于底层操作系统的Mutex lock(互斥锁)实现(只有一个线程能够拿到锁,其他就需要进行等待),它是一个重量级锁性能较低,每次操作都会执行加锁。
JDK1.6优化之后,采用锁升级的方式,就是按照1偏向锁(无锁),2轻量级锁(CAS),3重量级锁的方式来提高线程的执行效率。并不是一开始就使用重量级锁。
锁升级主要依赖Mark World中的锁标志位。
1在大多数情况下不会出现多线程竞争锁,而是同一个线程在多次获取锁,偏向锁的目的就是避免线程重复获取锁,降低资源开销,第一次线程获取到锁过后,会将当前线程id记录到Mark World的锁标志位中,第二次还是这个线程来获取锁时,就不需要再次获取锁,降低资源开销。
2当有少量来争夺锁的时候,偏向锁就会升级为轻量级锁,此时竞争的线程不会阻塞,而是进行自旋。提高程序响应速度。
3如果锁竞争严重,某个线程达到最大自旋次数(默认10次)的时候,轻量级锁会升级成重量级锁,这时候当没有获取到锁的线程,就会进行阻塞状态。
7Synchronized 加非静态和静态方法上的区别java中synchronized的普通方法与静态方法获取的锁对象是什么_junranhuigu的博客-CSDN博客
synchronized修饰的普通方法获取的是等效于this的指向对象的内置锁,即新创建的对象本身;
synchronized修饰的静态方法获取的是该方法所在类的内置锁,即XXX.class
8Synchronized(this) 和 Synchronized (User.class) 的区别?
synchronize(this)作用于当前对象,保证同一时间只有一个线程能调用该对象。
synchronize(*.class)作用于该类所属的所有实例,保证同一时间只有一个线程能调用该类的实例。
这两个关键字都是用来解决并发编程中的线程安全问题的,不同点主要有以下几点
第一:volatile的实现原理,是在每次使用变量时都必须重主存中加载,修改变量后都必须立马同步到主存;synchronized的实现原理,则是锁定当前变量,让其他线程处于阻塞状态
第二:volatile只能修饰变量,synchronized用在修饰方法和同步代码块中
第三:volatile修饰的变量,不会被编译器进行指令重排序,synchronized不会限制指令重排序
第四:volatile不会造成线程阻塞,高并发时性能更高,synchronized会造成线程阻塞,高并发效率低
第五:volatile不能保证操作的原子性,因此它不能保证线程的安全,synchronized能保证操作的原子性,保证线程的安全
10一个类中两个方法中都有Synchronized(this) 请问能锁住吗?为什么都有可能,
当创建了两个对象进行调用时,就锁不住。因为这时候this指代的是两个不同的对象,并不是同一个锁。
当创建一个对象进行调用时,就锁得住。因为这时候this指代的是当前唯一的对象,是同一把锁。