zoukankan      html  css  js  c++  java
  • 多线程

    synchronized、lock和Atomic的区别:

    synchronized:
    synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;
    在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize;

    ReentrantLock: 
    lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中;
    ReentrantLock提供了多样化的同步,它是有时间限制的同步,是可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等;
    在资源竞争不激烈的情形下,性能稍微比synchronized差点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock却还能维持常态; 

    Atomic: 
    和上面的类似,不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。激烈的时候,Atomic的性能会优于ReentrantLock一倍左右。
    但是其有一个缺点,就是只能同步一个值,一段代码中只能出现一个Atomic的变量,多于一个同步无效。因为他不能在多个Atomic之间同步。 


    关于ReentrantLock:
    公平性
    ReentrantLock的构造器提供了两种公平性选择:创建非公平的锁(默认)或者一个公平锁。
    在利用ReentrantLock构造函数的时候,可以传递一个boolean,当不传入模式是false,即创建一个非公平锁,当传入true的时候,创建一个公平的锁。
    所谓的公平的锁,线程按照它们发出请求的顺序来获得锁。但在非公平的锁上,允许刚请求的锁直接马上获取锁。一般非公平锁的性能要高于公平锁的性能,这是因为恢复一个被挂起的线程以及真正开始运行需要一点时间,如果在此刻能将这段时间让立刻要获取锁的线程,则就会提高吞吐量。
    在公平锁上,线程将按照它们发出请求的顺序来获得锁,但在非公平的锁上,则允许“插队”:当一个线程请求非公平锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁。
    其实非公平的ReentrantLock并不提倡“插队”。在公平锁中,如果有另一个线程持有这个锁或者有其他线程在队列中等待这个锁,那么新发出的请求的线程将被放入队列中。非公平的锁中,只有当锁被某个线程持有时,新发出的请求的线程才会被放入队列中。
    多数情况下,非公平锁的性能要高于公平锁。原因就在于,在激烈竞争的情况下,在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。
    当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,那么应该使用公平锁。


    ReentrantLock的几个关键方法说明:
    void lock();  //尝试获取锁,若得不到着等待(不可中断,类似于synchronized方式)
    void lockInterruptibly() ; //可中断的尝试获取锁
    boolean tryLock();尝试获取锁,不管得到与否立即返回
    boolean tryLock(long time, TimeUnit unit)尝试获取锁,若得不到等到一段时间
    void unlock();// 释放锁
    Condition newCondition();//创建于该锁相关的条件变量,实现精确等待/唤醒
    lock():阻塞的方法
    如果该锁没有被任何线程所拥有,则获取锁,立即返回,锁计数器加1.
    如果该锁已被当前线程所持有,则立即返回,锁计数器加1.
    如果该锁已被其他线程所拥有,则禁用当前线程,使得其休眠阻塞。
    tryLock(time):可阻塞可中断的方法
    如果该锁在给定时间内没有被任何线程所持有,且当前线程没有被中断,则获取锁,立即返回true,锁计数器加1.
    如果该锁已被当前线程所持有,则立即返回true,锁计数器加1.
    如果该锁已被其他线程所拥有,则禁用当前线程,使其休眠阻塞,直到发生以下情况:
    当前线程获取锁,当前线程被中断,时间到了
    返回状态:成功获取锁或者已经持有,则返回true。时间到了还没有获取锁,则返回false
    lockInterruptibly():可阻塞可中断的方法
    这种锁的获取必须在当前线程没有被中断的时候才能尝试获取锁
    如果该锁没有被任何线程所拥有,则获取锁,立即返回,锁计数器加1.
    如果该锁已被当前线程所持有,则立即返回,锁计数器加1.
    如果该锁已被其他线程所拥有,则禁用当前线程,使得其休眠阻塞,直到发生以下情况:
    当前线程获取锁,当前线程被中断
    tryLock():非阻塞的方法
    如果该锁没有被任何线程保持,则获取锁,立即返回true,锁计数器加1.
    如果该锁已被当前线程所持有,则立即返回true,锁计数器加1.
    如果该锁已被其他线程所拥有,则立即返回false。
    unlock():释放锁
    如果当前线程是此锁所有者,则将保持计数减 1。如果保持计数现在为 0,则释放该锁


    关于ReentrantReadWriteLock:
    说到ReentrantReadWriteLock,首先要做的是与ReentrantLock划清界限。它和后者都是单独的实现,彼此之间没有继承或实现的关系。
    特性:
    (a).重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则永远都不要想。
     (b).WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能,为什么?参看(a)
    (c).ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。
     (d).不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。
    (e).WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常。


    对long的操作是非原子的,原子操作只针对32位
    因为long是64位,底层操作的时候分2个32位读写,所以不是线程安全
     
    活跃度失败:当一个活动进入某种它永远无法再继续执行的状态时
     
    线程对某个变量的操作步骤:
    1.从主内存中复制数据到工作内存
    2.执行代码,对数据进行各种操作和计算
    3.把操作后的变量值重新写回主内存中
     
    锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。
    互斥 即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。
    可见性 要更加复杂一些,它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。
     
    当多个线程共享可变数据的时候,每个读或写数据的线程都必须执行同步。
    如果没有同步,就无法保证一个线程所做的修改可以被另一个线程获知。未能同步共享可变数据会造成程序的“活性失败”和“安全性失败”。
    如果只是需要线程之间的交互通信,而不需要互斥,用volatile关键字修饰则是一种可以接受的同步形式。
     
    Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离
    在同步竞争不激烈时用synchronized,激烈时用ReentrantLock
     
    “volatile” — 保证读写的都是主内存的变量
    “synchronized” — 保证在块开始时都同步主内存的值到工作内存,而块结束时将变量同步回主内存
     
    volatile负责线程中的变量与主存储区同步.但不负责每个线程之间的同步。
    volatile的含义是:线程在试图读取一个volatile变量时,会从主内存区中读取最新的值。
    要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
    1.对变量的写操作不依赖于当前值;
    2.该变量没有包含在具有其他变量的不变式中。
    第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然自增操作(i++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。实现正确的操作需要使 x 的值在操作期间保持不变,而 volatile 变量无法实现这点。(然而,如果将值调整为只从单个线程写入,那么可以忽略第一个条件。)
     
    虽然自从Java平台早期以来,同步的成本已经下降了,但更重要的是,永远不要过多同步。在这个多核时代,过多同步的实际成本并不是指获取锁所花费的CPU时间,而是指失去了并行的机会。另外潜在的开销在于,它会限制VM优化代码执行的能力。
     
    synchronized:
    访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁
     
    以上代码导致states数组的引用也被传递到外部,因此它的值会被外部修改所影响,违背了private的初衷
  • 相关阅读:
    双端队列广搜
    多源bfs
    leetcode刷题-67二进制求和
    leetcode刷题-66加一
    leetcode刷题-64最小路径和
    leetcode刷题-62不同路径2
    leetcode刷题-62不同路径
    leetcode刷题-61旋转链表
    leetcode刷题-60第k个队列
    leetcode刷题-59螺旋矩阵2
  • 原文地址:https://www.cnblogs.com/xianDan/p/4292867.html
Copyright © 2011-2022 走看看