博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Synchronized与ReentrantLock区别
阅读量:2165 次
发布时间:2019-05-01

本文共 7168 字,大约阅读时间需要 23 分钟。

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.通知等待的线程,非空条件已经满足				}

转载地址:http://zhjzb.baihongyu.com/

你可能感兴趣的文章
Redis学习笔记(三)—— 使用redis客户端连接windows和linux下的redis并解决无法连接redis的问题
查看>>
Intellij IDEA使用(一)—— 安装Intellij IDEA(ideaIU-2017.2.3)并完成Intellij IDEA的简单配置
查看>>
Intellij IDEA使用(二)—— 在Intellij IDEA中配置JDK(SDK)
查看>>
Intellij IDEA使用(三)——在Intellij IDEA中配置Tomcat服务器
查看>>
Intellij IDEA使用(四)—— 使用Intellij IDEA创建静态的web(HTML)项目
查看>>
Intellij IDEA使用(五)—— Intellij IDEA在使用中的一些其他常用功能或常用配置收集
查看>>
Intellij IDEA使用(六)—— 使用Intellij IDEA创建Java项目并配置jar包
查看>>
Eclipse使用(十)—— 使用Eclipse创建简单的Maven Java项目
查看>>
Eclipse使用(十一)—— 使用Eclipse创建简单的Maven JavaWeb项目
查看>>
Intellij IDEA使用(十三)—— 在Intellij IDEA中配置Maven
查看>>
面试题 —— 关于main方法的十个面试题
查看>>
集成测试(一)—— 使用PHP页面请求Spring项目的Java接口数据
查看>>
使用Maven构建的简单的单模块SSM项目
查看>>
Intellij IDEA使用(十四)—— 在IDEA中创建包(package)的问题
查看>>
Redis学习笔记(四)—— redis的常用命令和五大数据类型的简单使用
查看>>
Win10+VS2015编译libcurl
查看>>
Windows下使用jsoncpp
查看>>
Ubuntu下测试使用Nginx+uWsgi+Django
查看>>
Windows下编译x264
查看>>
visual studio调试内存泄漏工具
查看>>