对于锁一直处于比较模糊的状态,最近一天晚上偶然想看看,就翻了几本书,然后弄明白了一些概念,有一些仍然没明白,例如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中的锁(转)的更多相关文章

  1. 深入介绍Java中的锁[原理、锁优化、CAS、AQS]

    1.为什么要用锁? 锁-是为了解决并发操作引起的脏读.数据不一致的问题. 2.锁实现的基本原理 2.1.volatile Java编程语言允许线程访问共享变量, 为了确保共享变量能被准确和一致地更新, ...

  2. 探究Java中的锁

    一.锁的作用和比较 1.Lock接口及其类图 Lock接口:是Java提供的用来控制多个线程访问共享资源的方式. ReentrantLock:Lock的实现类,提供了可重入的加锁语义 ReadWrit ...

  3. java 中的锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁(转载)

    之前做过一个测试,详情见这篇文章<多线程 +1操作的几种实现方式,及效率对比>,当时对这个测试结果很疑惑,反复执行过多次,发现结果是一样的: 1. 单线程下synchronized效率最高 ...

  4. Java 中的锁

    Java中的锁分类 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分 ...

  5. Java中的锁(转)

    Java中的锁 锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂.因为锁(以及其它更高级的线程同步机制)是由synchronized同步 ...

  6. java 中的锁 -- 偏向锁、轻量级锁、自旋锁、重量级锁

    之前做过一个测试,详情见这篇文章<多线程 +1操作的几种实现方式,及效率对比>,当时对这个测试结果很疑惑,反复执行过多次,发现结果是一样的: 1. 单线程下synchronized效率最高 ...

  7. 深入理解Java中的锁

    转载:https://www.jianshu.com/p/2eb5ad8da4dc Java中的锁 常见的锁有synchronized.volatile.偏向锁.轻量级锁.重量级锁 1.synchro ...

  8. JAVA 中无锁的线程安全整数 AtomicInteger介绍和使用

    Java 中无锁的线程安全整数 AtomicInteger,一个提供原子操作的Integer的类.在Java语言中,++i和i++操作并不是线程安全的,在使用的时候, 不可避免的会用到synchron ...

  9. Java中的锁[原理、锁优化、CAS、AQS]

    1.为什么要用锁? 锁-是为了解决并发操作引起的脏读.数据不一致的问题. 2.锁实现的基本原理 2.1.volatile Java编程语言允许线程访问共享变量, 为了确保共享变量能被准确和一致地更新, ...

  10. 史上最全 Java 中各种锁的介绍

    更多精彩原创内容请关注:JavaInterview,欢迎 star,支持鼓励以下作者,万分感谢. 锁的分类介绍 乐观锁与悲观锁 锁的一种宏观分类是乐观锁与悲观锁.乐观锁与悲观锁并不是特定的指哪个锁(J ...

随机推荐

  1. 20155304 2016-2017-2 《Java程序设计》第七周学习总结

    20155304 2016-2017-2 <Java程序设计>第七周学习总结 教材学习内容总结 1.时间的度量: 格林威治标准时间(GMT)通过观察太阳而得,其正午是太阳抵达天空最高点之时 ...

  2. the error about “no such file or directory”

    CHENYILONG Blog the error about "no such file or directory" when you get the question like ...

  3. Nessus扫描策略

    本篇将简单介绍下Nessus的扫描策略设置.选用plugins及如何使用定制的策略来进行扫描任务. Step 1: 启动Nessus服务 root@kali:~# /etc/init.d/nessus ...

  4. Sql语句 表中相同的记录(某个字段)只显示一条,按照时间排序显示最大或最小

    原始表数据:

  5. DM816X 实现 USB HID Gadget 鼠标键盘功能【转】

    转自:https://my.oschina.net/renyongke/blog/410695 开发环境: 平台: DM8168 内核 :linux 2.6.32 RDK:DVRRDK_04.00.0 ...

  6. Linux下利用backtrace追踪函数调用堆栈以及定位段错误【转】

    转自:https://www.linuxidc.com/Linux/2012-11/73470p2.htm 通常情况系,程序发生段错误时系统会发送SIGSEGV信号给程序,缺省处理是退出函数.我们可以 ...

  7. centos6 安装EPEL

    一.安装 32位系统: rpm -ivh http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm rpm --i ...

  8. linux挂载mount参数优化

    一. 1) 蓝色:表示经过优化的xfs mount时的参数defaults,noatime,nodiratime,nobarrier,discard,allocsize=256m,logbufs=8, ...

  9. 原生js实现ajax跨域(兼容IE8,IE9)

    html设置meta标签兼容360兼容模式和IE怪异模式 <meta http-equiv="X-UA-Compatible" content="IE=9;IE=8 ...

  10. Laravel Model 的 fillable (白名单)与 guarded (黑名单)

    例如 protected $fillable = ['name']; protected $guarded = ['price']; 定义了 name 字段可以写入/修改,而 price 字段不可以. ...