Synchronized与ReentrantLock区别

Java互斥访问机制

  • 实现java多线程中共享的、可修改的数据,必然涉及到并发访问问题,因此需要通过互斥同步机制来实现
  • 实现方法,synchronized,锁机制

synchronized

  • 非公平,悲观,独享,互斥,可重入重量级锁
  • JVM 层面实现,不但可以通过监控工具监控synchronized的锁定,而且在代码执行异常时,jvm会自动释放锁定,但是Lock不需(需要通过finally的unLock方法释放)
  • 可以添加在代码块,方法(实际就是将方法语句全部用synchronized包裹起来)

Lock

  • 有多种类锁,按分类有自旋锁,偏向锁,阻塞锁,可重入锁,读写时,互斥锁,乐观锁,悲观锁,公平锁,偏向锁,对象锁
  • 锁存在降级(在进入safepoint时会判断是否有限制monitor,然后试图降级),消除,粗化
  • ReetrantLock,默认非公平但可实现公平(实例传参为true即可实现公平),悲观,独享,互斥,可重入,重量级锁
  • ReentranLock,默认非公平但可实现公平(实例传参为true即可实现公平),悲观,写独享,读共享,可重入,重量级锁
  • ReentranLock实现方式
    • 1.Lock(),获取锁立即返回。如果其它线程持有锁,当前线程一致处于休眠状态,直到获取锁。
    • 2.tryLock(),如果获取了锁立即返回true。如果其它线程持有锁,立即返回false
    • 3.tryLock(long timeout,TimeUnit unit),如果获取锁定立即返回true;如果别的线程正持有锁,会等待参数给的的时间,等待过程获取锁返回true;等待超时返回false

ReetranLock与Synchornized

  • 实现层面

    • synchronized,jvm层面实现,可以使用监控工具去查看监控synchronized的锁定,而且保证代码执行出现异常时,jvm会自动释放锁定,但Lock不需
    • ReetranLock,代码层面实现,无法通过监控工具去查看锁定情况,锁的实现必须将unLock()放置finally{}中同步、阻塞式API,简单实现,要点如下
  • 公平性

    • synchronized,非公平性,悲观,独享,互斥,可重入重量级锁
    • ReentrantLock,默认非公平,实例化时传入参数true即可实现公平锁
  • 中断等待

    如果A和B都要获得对象O的锁定,假设A获取对象O锁,B将等待A释放对O的锁定
      1.synchronized: 如果A不释放,B将一致等待下去,不能被中断
      2.ReentrantLock:拥有synchronized相同语义,还多了锁投票,定时锁等候,中断锁等候。
    			如果A不释放,可以使B在等待了足够长时间以后,中断等待,转而去干别的事情。
    因此在高竞态的并发环境下,并发吞吐量会高于synchronized
    
  • 更精细的同步操作

    1.相比synchronized通过wait(),notify(),sleep()等方法只能指定一个隐含条件去进行同步操作,多一个条件则需要再加一个锁
    2.ReentranLock实现精细同步操作:
    	a.带超时获取锁尝试
    	b.可以判断是否有线程,或某个特定线程在排队时等待获取锁
    	c.可以响应中断请求
    3.ReentranLock通过条件变量(Condition)对象同时绑定多个Condition对象,实现一把锁绑定多个条件
    	a.将wait,notify,notifyAll,signal等转化为响应对象,
    	b.将复杂晦涩同步操作转变为直观可控对象行为
    	c.可以进行多个条件绑定实现同步操作。			
    
  • 性能

    1.早期synchronized标记低效,相对ReentrantLock,大部分场景都比较差
    2.java6 对synchronized有非常大改进,在高竞态情况下,ReentranLock仍有一定优势(中断等待,转而去干别的事情)
    

知识扩展

线程安全的意义

  • 线程安全,在多线程环境下共享的、可修改的数据(对象,变量)的正确性
  • 多线程环境下,如果未对共享、可修改数据进行互斥访问,则会出现数据脏读、不一致等现象
  • 线程安全,保证多线程环境,多个线程访问共享、可修改数据时,不会出现脏读,不一致线性
  • 保证线程安全的方法
    1.封装:通过封装,我们可以将对象内部状态隐藏、保护起来
    2.不可变:将数据对象设置为不可变。java目前无真正意义的原生不可变。
    

线程安全的基本特性

原子性

  • 定义:相关操作不会中途被其他线程干扰,一般通过同步机制实现

  • 非原子性操作

    例子:通过取两次数值然后进行对比,来模拟两次对共享状态(变量)的操作。
       	1.执行后可以看到仅仅两个线程低度并发,就非常容易碰到former,latter变量不相等情况。
       	2.原因是在取值过程中,其他线程可能已经对共享状态进行修改
       	3.代码及运行结果如下
       			public class ThreadSafeSample {
       			  public int sharedState;
       			  public void nonSafeAction() {
       				  while (sharedState < 100000) {
       					  int former = sharedState++;
       					  int latter = sharedState;
       					  if (former != latter - 1) {
       						  System.out.printf("Observed data race, former is " +
       								  former + ", " + "latter is " + latter);
       					  }
       				  }
       			  }
       			  public static void main(String[] args) throws InterruptedException {
       				  ThreadSafeSample sample = new ThreadSafeSample();
       				  Thread threadA = new Thread(){
       					  public void run(){
       						  sample.nonSafeAction();
       					  }
       				  };
       				  Thread threadB = new Thread(){
       					  public void run(){
       						  sample.nonSafeAction();
       					  }
       				  };
       				  threadA.start();
       				  threadB.start();
       				  threadA.join();
       				  threadB.join();
       			  }
       			}
       			
       			运行结果:
       			java ThreadSafeSample
       			Observed data race, former is 13097, latter is 13099
    
  • 添加synchronized,锁定最小的代码段,对类的对象级别进行互斥访问,实现原子操作

    1.代码
    public class ThreadSafeSample {
       public int sharedState;
       public void nonSafeAction() {
       		 while (sharedState < 100000) {
       				 sychronized(this){
       						int former = sharedState++;
       						int latter = sharedState;
       				}
       				if (former != latter - 1) {
       						  System.out.printf("Observed data race, former is " +
       								  former + ", " + "latter is " + latter);
       				 }
       		}
     }
    
       public static void main(String[] args) throws InterruptedException {
       				  ThreadSafeSample sample = new ThreadSafeSample();
       				  Thread threadA = new Thread(){
       					  public void run(){
       						  sample.nonSafeAction();
       					  }
       				  };
       				  Thread threadB = new Thread(){
       					  public void run(){
       						  sample.nonSafeAction();
       					  }
       				  };
       				  threadA.start();
       				  threadB.start();
       				  threadA.join();
       				  threadB.join();
       			  }
       			}
       			
    2.运行结果:未输出不相等的情况
       			java ThreadSafeSample
       			 
       			
    3.jmap反编译代码结果,可以看到其利用monitorenter/monitorexit对,实现同步语义
       			11: astore_1
       			12: monitorenter
       			13: aload_0
       			14: dup
       			15: getfield    #2                // Field sharedState:I
       			18: dup_x1
       			…
       			56: monitorexit
       			
    4.如果synchronized修饰静态方法,可以使用synchronized(className.class){}将代码块包裹起来,或者在静态方法添加 synchronized
    
  • ReentranLock再入锁实现公平性

    public class ThreadSafeSample {
    		private ReentranLock fairLock = new ReentranLock(true);
    		public int sharedState;
    		public void nonSafeAction() {
    					  while (sharedState < 100000) {
    						  try{
    							lock.lock(1000,TimeUnit.Seconds);
    							int former = sharedState++;
    							int latter = sharedState;
    						  }catch(Exception e){
    							e.printStackTrace();
    						  }finally{
    							lock.unlock(); //锁在finally中要释放
    						  }
    						  if (former != latter - 1) {
    							  System.out.printf("Observed data race, former is " +
    									  former + ", " + "latter is " + latter);
    						  }
    					  }
    				  }
    
    	  public static void main(String[] args) throws InterruptedException {
    					  ThreadSafeSample sample = new ThreadSafeSample();
    					  Thread threadA = new Thread(){
    						  public void run(){
    							  sample.nonSafeAction();
    						  }
    					  };
    					  Thread threadB = new Thread(){
    						  public void run(){
    							  sample.nonSafeAction();
    						  }
    					  };
    					  threadA.start();
    					  threadB.start();
    					  threadA.join();
    					  threadB.join();
    				  }
    				}
    
  • 锁的公平性

    1.所谓公平性,指竞态环境中,当公平性位置,会赋予等待时间最久的线程。
    2.目的是减少线程"饥饿"(个别线程长期等待锁,但无法获取)情况发生。
    3.如果使用synchronized时无法保证线程公平性获取锁,用  于不公平,也是主流操作系统调度选择。
    4.除非是有公平性要求的场景,通用场景公平不那么重要,建议在程序需要公平性时,再指定它。	
    

可见性

  • 一个线程修改了某共享变量,该共享变量状态立即被其他线程知晓,通常被理解为将线程本地状态反映到主内存中,volatile负责保证线程间共享变量可见性

有序性

  • 保证线程内部串行语义,避免指令重排。volatile变量语句通过指令优化重排到volatile变量前,但jvm内部happen-before机制可以保证volatile变量不会重排到非volatile变量前面

ReentranLock通过条件变量实现多个条件绑定的锁

  • 标准类库中的 ArrayBlockingQueue代码
1.获取锁的条件,队列不空时,不满时获取锁,来进行互斥访问,两个条件变量是从同一把再入锁创建。
				/** Condition for waiting takes */
				private final Condition notEmpty;

				/** Condition for waiting puts */
				private final Condition notFull;
				 
				public ArrayBlockingQueue(int capacity, boolean fair) {
				  if (capacity <= 0)
					  throw new IllegalArgumentException();
				  this.items = new Object[capacity];
				  lock = new ReentrantLock(fair);
				  notEmpty = lock.newCondition();
				  notFull =  lock.newCondition();
				}		
2.通过条件变量实现一把再入锁绑定两个条件,使用特定操作(take方法)时,判断和等待条件满足(获取锁后,判断为空时等待,不为空出队,最后释放锁)			   
				public E take() throws InterruptedException {
				  final ReentrantLock lock = this.lock; //1.获取锁 
				  lock.lockInterruptibly();
				  try {
					  while (count == 0)
						  //signal和await成对调用非常重要,不然只有await,线程会一直中断
						  notEmpty.await(); //2.条件(notEmpty)判断为空进入等待
					  return dequeue();//否则进行出队
				  } finally {
					  lock.unlock();//3.出队后释放锁
				  }
				}
3.通过条件变量NotEmpty判断触发enqueue操作,通过signal/await组合,完成条件判断通知等待线程,顺畅完成主体流转。
				private void enqueue(E e) {
				  // assert lock.isHeldByCurrentThread();
				  // assert lock.getHoldCount() == 1;
				  // assert items[putIndex] == null;
				  final Object[] items = this.items;
				  items[putIndex] = e;
				  if (++putIndex == items.length) putIndex = 0;
				  count++;
				  //signal和await成对调用非常重要,不然只有await,线程会一直中断
				  notEmpty.signal(); // 5.通知等待的线程,非空条件已经满足
				}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值