java中的锁

java中的锁:
什么是锁:同步的本质是通过锁来实现的。为了实现多个线程在一个时刻同一个代码块只能有一个线程可执行,那么需要在某个地方做个标记,这个标记必须每个线程都能看到,当标记不存在时可以设置该标记,其余后续线程发现已经有标记了则等待拥有标记的线程结束同步代码块取消标记后再去尝试设置标记。这个标记可以理解为锁。
在单进程情况下,多个线程访问同一资源,可以使用synchronized和lock实现。
在多进程情况下,也就是分布式情况,对同一资源的并发请求,需要使用分布式锁实现。
(一)单进程情况的锁
1. 公平锁和非公平锁
公平锁: 指按照申请锁的顺序来获取锁
非公平锁: 线程获取锁的顺序不一定按照申请锁的顺序来(默认非公平锁)
非公平锁的优点是吞吐量大
e.g.:Synchronized就是一种非公平锁
2. 可重入锁(递归锁)
指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
可重入锁的一个好处是可一定程度避免死锁。
例如 :加锁的方法中调用另一个被加锁的方法,如果不是可重入锁不可能执行成功
e.g: Reentrant Lock Synchronized
3. 共享锁和独享锁
共享锁:线程可以被多个线程公有
独享锁:一次只能被一个线程访问
e.g.: Reentrant Lock Synchronized 是独享锁 ReadWriteLock中读锁是共享锁,写锁是独享锁
4. 乐观锁和悲观锁 (看待并发同步的角度)
乐观锁:默认对一个数据的并发操作,不发生修改。在更新数据的时候,会尝试采用不断更新重入的方式更新数据。
悲观锁:默认对一个数据的并发操作,一定会发生修改,因此对于同一个数据的并发操作,悲观锁采用加锁机制。
悲观锁认为,不加锁的操作一定会发生问题。
e.g.: 悲观锁在Java中就是利用各种锁, 乐观锁在Java中的使用,是无锁编程, 常采用CAS算法
使用场景:
悲观锁:比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
5. 分段锁(锁的设计)
CocurrentHashMap,降低锁的粒度
当需要put元素时,并不是对整个hashmap加锁,而是通过hashcode获取其存放的位置,对分段进行加锁,对于不同的线程,只要不是放在同一个分段,就实现了并行插入
6. 偏向锁 轻量级锁 重量级锁 (指的是锁的三种状态)
偏向锁: 一段同步代码一直被一个线程所访问,那么该线程自动获取锁,降低获取锁的代价。
轻量级锁:当偏向锁被另一个线程访问,偏向锁会升级为轻量级锁,其他线程会通过自旋锁的方式尝试获取锁,不会阻塞
重量级锁:当锁为轻量级锁时,另一个线程虽然自旋,但自旋不会一直持续,当自旋一定次数时,还没有获取到锁,就会进入阻塞,该锁成为重量级锁,其他线程访问时会直接进入阻塞,性能降低。
7. 自旋锁
尝试获取锁的线程不会立即阻塞,而是采用循环的方式去获取锁。优点是减少了线程的上下文切换消耗,缺点是会消耗CPU
1> synchronized  
Java 中的 synchronized 关键字可以在多线程环境下用来作为线程安全的同步锁。
Java中的对象锁和类锁:
对象锁是用于对象实例方法,或者一个对象实例上的;
类锁是用于类的静态方法或者一个类的class对象上的。
类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
synchronized 关键字主要有以下几种用法: 
- 非静态方法的同步; 
- 静态方法的同步; 
- 代码块。
对象锁:
非静态方法使用 synchronized 修饰实例方法时,锁定的是当前对象;
代码块使用 synchronized 修饰的写法,如果传入的参数是 this,那么锁定的也是当前对象。
 public synchronized void test(){}
 public void test(){synchronized (this) {}}
类锁需要 synchronized 来修饰静态 static 方法,写法如下:
    public static synchronized void test(){}
或者使用代码块,需引用当前的类:
    public static void test(){synchronized (TestSynchronized.class) {}}
1> synchronized 和 ReentrantLock的区别:
synchronized是java中的一个关键字,也就是说是java内置的一个特性。当一个线程访问一个被synchronized修饰的代码块,会自动获取对应的一个锁,并在执行该代码块时,其他线程想访问这个代码块,会一直处于等待状态,自有等该线程释放锁后,其他线程进行资源竞争,竞争获取到锁的线程才能访问该代码块。线程释放synchronized修饰的代码块锁的方式有两种:
  1.该线程执行完对应代码块,自动释放锁。
  2.在执行该代码块是发生了异常,JVM会自动释放锁
采用synchronized关键字来实现同步,会导致如果存在多个线程想执行该代码块,而当前获取到锁的线程又没有释放锁,可想而知,其他线程只有一直等待,这将严重影响执行效率。Lock锁机制的出现就是为了解决该现象。Lock是一个java接口,通过这个接口可以实现同步,使用Lock时,用户必须手动进行锁的释放,否则容易出现死锁。
2>Lock
Lock是一个java实现的接口,使用volatile关键字修饰的一个变量,保证对所有线程的可见性和原子修改。
(1)Lock接口的实现类有:ReentrantLock,WriteLock,ReadLock,WriteLockView,ReadLockView。
(2)Lock接口中的方法:
tryLock():尝试获取锁,能获取就获取,获取不到就休眠。
tryLock(long,TimeUnit):有时间限制的尝试获取锁。
ReentrantLock可以通过tryLock()设置超时时间,如果超时未获取到锁就会放弃
ReentrantLock可以设置公平锁和非公平锁
ReentrantLock可以获得正在等待线程的个数,计数器等。
3> synchronized和lock的区别: 
1.用法不一样。synchronized既可以加在方法上,也可以加载特定的代码块上,括号中表示需要锁的对象。而Lock需要显示地指定起始位置和终止位置。synchronzied是托管给jvm执行的,Lock锁定是通过代码实现的。 
2.在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。 
3.锁的机制不一样。synchronized获得锁和释放的方式都是在块结构中,而且是自动释放锁。而Lock则需要开发人员手动去释放,并且必须在finally块中释放,否则会引起死锁问题的发生。 
4.Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现; 
5.synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁; 
6.Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。Lock可以提高多个线程进行读操作的效率。
7.synchronized锁的范围是整个方法或synchronized块部分;而Lock因为是方法调用,可以跨方法,灵活性更大。
8.Lock是乐观锁,是CAS机制,每次不加锁,遇到重入失败就重试,直到成功为止;Synchronized是悲观锁,线程获取的是独占锁,其他线程只能被阻塞,等待线程释放锁。

synchronized的缺陷:
1、阻塞式:当某一个线程获取锁时,并执行该代码块,其他线程只能够一直等待,等待取锁的线程释放锁;
2、效率底:一旦线程执行sleep,其他线程只能干巴巴的等待,一旦大量并发进入,将会极大影响性能;
3、释放锁:只有两种情况:a.当前线程执行完毕。 b.线程执行异常,JVM自动释放锁;
Lock锁的优势:
1、Lock 是一个类,通过这个类可以实现同步访问;
2、Lock 为非内置锁,可根据具体业务控制,但必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

4> volatile关键字详解
可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。

volatile原理
Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。
当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。
volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。

当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。
而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。
当一个变量定义为 volatile 之后,将具备两种特性:
1.保证此变量对所有的线程的可见性,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。
2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障。
volatile 性能:
volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
volatile 的特性:

保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
禁止进行指令重排序。(实现有序性)
volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
当写一个volatile变量时,JMM会把该线程对应的本地中的共享变量值刷新到主内存。
当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。

(二) 多进程情况下的锁(Java分布式锁)
https://blog.csdn.net/gghh2015/article/details/95777104

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值