对于锁一直处于比较模糊的状态,最近一天晚上偶然想看看,就翻了几本书,然后弄明白了一些概念,有一些仍然没明白,例如AQS,先把搞明白的记录一下吧。
什么是线程安全?
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。代码本省封装了所有必要的正确性保障手段(互斥同步等),令调用者无需关心多线程的问题,更无需自己实现任何措施来保证多线程的正确调用。
线程之间的交互机制?
不同的线程之间会产生竞争,同样也有交互,最典型的例如数据库连接池,一组数据库连接放在一个数组中,如果线程需要数据库操作,则从池中获取链接,用完了就放回去。JVM提供了wair/notify/notifyAll方式来满足这类需求,典型的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
package lock;
public class Pool {
public Connection get(){
synchronized ( this ) {
if (free> 0 ){
free--;
} else {
this .wait();
}
return cacheConnection.poll();
}
}
public void close(Connection conn){
synchronized ( this ) {
free++;
cacheConnection.offer(conn);
this .notifyAll();
}
}
} |
在JDK5之后,并发包中提供了更多的方式来进行线程之间的交互,例如Condition中的await和signal,CountDownLatch中的countDown;
如何实现线程安全?
A、互斥同步,最常见的并发正确性保障手段,同步至多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个线程使用。
B、非阻塞同步,互斥同步的主要问题就是进行线程的阻塞和唤醒所带来的性能问题,因此这个同步也被称为阻塞同步,阻塞同步属于一种悲观的并发策略,认为只要不去做正确的同步措施,就肯定会出问题,无论共享的数据是否会出现竞争。随着硬件指令的发展,有了另外一个选择,基于冲突检测的乐观并发策略,通俗的讲就是先进性操作,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据有争用,产生了冲突,那就再进行其他的补偿措施(最常见的措施就是不断的重试,直到成功为止),这种策略不需要把线程挂起,所以这种同步也被称为非阻塞同步。
C、无同步方案,简单的理解就是没有共享变量需要不同的线程去争用,目前有两种方案,一个是“可重入代码”,这种代码可以在执行的任何时刻中断它,转而去执行其他的另外一段代码,当控制权返回时,程序继续执行,不会出现任何错误。一个是“线程本地存储”,如果变量要被多线程访问,可以使用volatile关键字来声明它为“易变的“,以此来实现多线程之间的可见性。同时也可以通过ThreadLocal来实现线程本地存储的功能,一个线程的Thread对象中都有一个ThreadLocalMap对象,来实现KV数据的存储。
主内存和工作内存?
Java内存模型中规定了所有变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了改线程使用到的变量的主内存副本拷贝,线程对于变量的所有操作(读取和赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量,不同线程之间无法直接访问对方工作内存中的变量,线程间值的传递均需要通过主内存来完成。这里的主内存和工作内存,和java中堆的模型不是一个层次,主内存主要对应java堆中对象的实例数据部分。
什么是自旋锁?
自旋锁在JDK1.6之后就默认开启了。基于之前的观察,共享数据的锁定状态只会持续很短的时间,为了这一小段时间而去挂起和恢复线程有点浪费,所以这里就做了一个处理,让后面请求锁的那个线程在稍等一会,但是不放弃处理器的执行时间,看看持有锁的线程能否快速释放。为了让线程等待,所以需要让线程执行一个忙循环也就是自旋操作。
在jdk6之后,引入了自适应的自旋锁,也就是等待的时间不再固定了,而是由上一次在同一个锁上的自旋时间及锁的拥有者状态来决定。
什么是锁消除?
虚拟机即时编译器在运行时,对于代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。如果判断一段代码,在椎上的所有数据都不会逃逸出去被其他线程访问到,那么认为他是线程私有的,同步加锁也就没有必要做了。
什么是锁粗化?
原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制的尽量小,仅仅在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作尽可能变小,如果存在锁竞争,那等待锁的线程也能尽快的拿到锁。大部分情况下,这儿原则是正确的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至锁出现在循环体内,即使没有线程竞争,频繁的进行互斥操作也会导致不必要的性能损耗。
什么是偏向锁?
在JDK1.之后引入的一项锁优化,目的是消除数据在无竞争情况下的同步原语。进一步提升程序的运行性能。 偏向锁就是偏心的偏,意思是这个锁会偏向第一个获得他的线程,如果接下来的执行过程中,改锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。偏向锁可以提高带有同步但无竞争的程序性能,也就是说他并不一定总是对程序运行有利,如果程序中大多数的锁都是被多个不同的线程访问,那偏向模式就是多余的,在具体问题具体分析的前提下,可以考虑是否使用偏向锁。
关于轻量级锁?
为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在Java SE1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率,下文会详细分析
什么场景下适合volatile?
volatile能够实现可见性,但是无法保证原子性。可见性指一个线程修改了这个变量的指,新值对于其他线程来说是可以立即得知的。而普通变量是不能做到这一点的,变量值在线程间传递均需要通过主内存完成。volatile的变量在各个线程的工作内存中不存在一致性问题(各个线程的工作内存中volatile变量也可以存在不一致的情况,但是由于每次使用之前都要先刷新,执行引擎看不到不一致的情况,因此可以认为不存在一致性问题)但是java里面的运算并非原子操作的,导致volatile变量运算在并发下一样是不安全的。
什么是CAS?
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。Java中通过Unsafe来实现了CAS。
如何实现互斥同步?
java中最基本的互斥就是synchronized关键字,synchronized在经过编译后,会在同步块的前后分别形成monitorenter和moitorexit这两个字节码指令。在执行monitorenter指令时,首先要去尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1,相应的,在执行monitorexit指令时会把锁计数器减1,当计数器为0时,锁就被释放了。如果获取对象的锁失败,当当前线程就要阻塞等待,知道对象的锁被另一个线程释放为止。synchronized对于同一个线程来说是可重入的,不会出现自己把自己锁死的问题。除了synchronized指望,JUC中的Lock也能实现互斥同步,ReentrantLock,写法上更加可见,lock和unlock配合try/finally来配合完成,ReentrantLock比synchronized有几个高级的特性。
ReentrantLock的高级特性有那几个?
1、等待可中断,当持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,改为处理其他事情;
2、可以实现公平锁,公平锁指多个线程在等待同一个锁时,必须按照申请锁的顺序依次获得锁,synchronized是非公平锁,ReentrantLock默认也是非公平的,只不过可以通过构造函数来制定实现公平锁;
3、锁绑定多个条件,ReentrantLock对象可以同时绑定多个Condition对象,在synchronized中,锁对象的wait/notify/notifyall方法可以实现一个隐含的条件,如果要多一个条件关联的时候,就需要额外的增加一个锁;
关于锁的几个使用建议?
1、使用并发包中的类,并发包中的类大多数采用了lock-free等算法,减少了多线程情况下的资源的锁竞争,因此对于线程间的共享操作的资源而言,应尽量使用并发包中的类来实现;
2、尽可能少用锁,没必要用锁的地方就不要用了;
3、拆分锁,即把独占锁拆分为多把锁(这个不一定完全适用);
4、去除读写操作的互斥锁,在修改时加锁,并复制对象进行修改,修改完毕之后切换对象的引用,而读取是则不加锁,这种方式成为CopyOnWrite,CopyOnWriteArrayList就是COW的典型实现,可以明显提升读的性能;
关于sunchronized的几个注意点?
1、当一个线程访问object的一个synchronized(this)同步代码块时, 另一个线程仍然可以访问该object中的非synchronized(this)同步代码块;
2、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时, 一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块;
3、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时, 其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞;
4、Java中的每一个对象都可以作为锁,对于同步方法,锁是当前实例对象,对于静态同步方法,锁是当前对象的Class对象,对于同步方法块,锁是Synchonized括号里配置的对象;
代码地址:https://github.com/iamzhongyong/about_java_lock
参考书籍
《分布式java应用基础和实践》
《深入java虚拟机》
《java并发编程实践》
http://www.infoq.com/cn/articles/java-se-16-synchronized
相关推荐
通过以上对Java锁机制的详细介绍,可以看出Java在并发控制方面具有丰富的工具和策略,它们能够帮助开发者在多线程编程中处理好资源竞争和线程协作的问题,从而编写出高效且线程安全的应用程序。
java中的乐观锁与悲观锁,synchronized与ReentrantLock重入锁的说明与比较
Java 读写锁是Java并发编程中的一种重要机制,它为多线程环境下的数据访问提供了更为精细的控制。在Java的`java.util.concurrent.locks`包中,`ReentrantReadWriteLock`类实现了读写锁的功能。这个锁允许多个读取者...
Java 锁是 Java 并发编程中的一种基本机制,用于确保线程安全和避免竞争条件。Java 锁可以分为两大类:synchronized 锁和 ReentrantLock 锁。 一、Synchronized 锁 1. 锁的原理:synchronized 锁是基于对象头的 ...
Java 锁机制 Synchronized 是 Java 语言中的一种同步机制,用于解决多线程并发访问共享资源时可能出现的一些问题。 Java 锁机制 Synchronized 的概念 在 Java 中,每个对象都可以被看作是一个大房子,其中有多个...
### Java中的悲观锁与乐观锁实现详解 #### 一、悲观锁(Pessimistic Locking) 悲观锁是一种基于对数据安全性的保守态度而设计的锁机制。它假设数据在处理过程中很可能被外界修改,因此在整个数据处理过程中都会将...
总之,Java锁的释放与建立是多线程编程中的核心概念,它们与_happens-before_关系紧密相连,确保了线程间的正确通信和数据一致性。正确理解和运用这些机制,能有效避免并发问题,提高程序的稳定性和性能。
#### 一、Java锁机制概览 Java中的锁机制主要用于解决多线程环境下的资源竞争问题。在并发编程中,为了保证数据一致性与正确性,通常需要采用各种锁来控制对共享资源的访问。Java语言提供了多种锁机制,包括`...
Java中的锁机制是多线程编程中不可或缺的一部分,它们用于管理对共享资源的并发访问,确保数据的一致性和完整性。本文将深入探讨Java中的几种主要锁类型及其使用。 1. **乐观锁**与**悲观锁** - **乐观锁**假设...
Java基于Redis实现分布式锁代码实例 分布式锁的必要性 在多线程环境中,资源竞争是一个常见的问题。例如,在一个简单的用户操作中,一个线程修改用户状态,首先在内存中读取用户状态,然后在内存中进行修改,然后...
Java锁机制是Java多线程编程中的核心概念之一,其主要目的是确保在多线程环境下,多个线程能够安全地访问共享资源,避免数据不一致的问题。Java锁机制的发展历经了多个版本的改进,尤其是Java 5.0引入的显示锁...
redis实现分布式锁(java/jedis),其中包含工具方法以及使用demo 本资源是利用java的jedis实现 redis实现分布式锁(java/jedis),其中包含工具方法以及使用demo 本资源是利用java的jedis实现
本文将深入探讨如何使用Redisson和Curator框架来实现Java环境中的分布式锁。 首先,让我们来看一下Redisson实现的分布式锁。Redis是一个高性能的键值数据库,常被用作分布式系统的缓存或数据存储。Redisson是基于...
:Java新IO】_文件锁笔记032003
4. **Java锁的类型**: - **内置锁**:也称为监视器锁,由`synchronized`关键字提供。 - **显式锁**:如`java.util.concurrent.locks.Lock`接口及其实现,如`ReentrantLock`,提供了更复杂的锁操作,如可中断锁...
4种常用Java线程锁的特点,性能比较、使用场景 线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发...
关于读写锁算法的Java实现及思考,是一个深入探讨了多线程环境下资源访问控制机制的主题。在现代软件开发中,尤其是并发编程领域,读写锁(ReadWriteLock)是一种非常重要的同步工具,它允许多个线程同时进行读操作...
JAVA 中锁概及运用 JAVA 中锁是指在多线程环境下,用于控制对共享资源的访问的机制。锁是 Java 并发编程的核心概念之一,锁机制可以确保在多线程环境下,共享资源不会被多个线程同时访问,从而避免数据不一致和其他...
标题和描述概述的知识点主要集中在Java的多线程机制中,特别是`wait`和`notify`方法在同步锁中的应用。这些方法对于控制线程之间的交互至关重要,尤其是在资源有限或需要确保数据一致性的情况下。 ### Java同步锁...