多线程保证数据的线程安全与数据同步
多线程开发中不可避免的要用到锁,一段被加锁的代码被一个线程执行之前,线程要先拿到执行这段代码的权限,在Java里边就是拿到某个同步对象的锁(一个对象只有一把锁),如果这个时候同步对象的锁被其他线程拿走了,这个线程就只能等了(线程阻塞在锁池等待队列中)。拿到权限(锁)后,他就开始执行同步代码,线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中等待的某个线程就可以拿到锁执行同步代码了。这样就保证了同步代码在统一时刻只有一个线程在执行。Java中常用的锁有synchronized和Lock两种。
锁的特点:每个对象只有一把锁,不管是synchronized还是Lock它们锁定的只能是某个具体对象,也就是说该对象必须是唯一的,才能被锁起,不被多个线程同时使用。
synchronized的特点
J.DK1.4前是通过synchronized实现。同步锁,当它锁定的方法或者代码块发生异常的时候,它会在自动释放锁;但是如果被它锁定的资源被线程竞争激烈的时候,它的表现就没那么好了。
Lock
JDK1.5后加入java.util.concurrent.locks包下的各种lock。由于我们提到synchronized
无法中断一个正在等候获得锁的线程,也无法通过投票得到锁,如果不想等下去,也就没法得到锁。所以JSR 166小组花时间为我们开发了java.util.concurrent.lock
框架,当Lock
锁定的方法或者代码块发生异常的时候,它不会自动释放锁;它拥有与synchronized
相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
代码层的区别
synchronized:在代码里,synchronized类似“面向对象”,修饰类、方法、对象。 Lock:不作为修饰,类似“面向过程”,在方法中需要锁的时候lock,在结束的时候unlock(一般都在finally块里)。 例如代码:
Java代码
public void method1() {
synchronized(this){//旧锁,无需人工释放
System.out.println(1); }
}
public void method2() {
Lock lock = new ReentrantLock(); //一个重入锁ReenTrantLock(为Lock的实现类)
lock.lock();//上锁
try{
System.out.println(2);
}finally{
lock.unlock();//解锁
}}
性能:在并发高是,lock性能优势很明显,在低并发时,synchronized也能取得优势
下面继续讨论怎么由代码层到native的过程。
1 synchronized -- 对象加锁
所有对象都自动含有单一的锁,JVM负责跟踪对象被加锁的次数。如果一个对象被解锁,其计数变为0。在任务(线程)第一次给对象加锁的时候, 计数变为1。每当这个相同的任务(线程)在此对象上获得锁时,计数会递增。 只有首先获得锁的任务(线程)才能继续获取该对象上的多个锁。每当任务离开时,计数递减,当计数为0的时候,锁被完全释放。synchronized就是基于这个原理,同时synchronized靠某个对象的单一锁技术的次数来判断是否被锁,所以无需(也不能)人工干预锁的获取和释放。
synchronized原理
synchronized原理是基于栈中的某对象来控制一个框架,所以对于synchronized有常用的优化是锁对象不锁方法。实际上 synchronized作用于方法时,锁住的是“this”,作用于静态方法/属性时,锁住的是存在于永久带的CLASS,相当于这个CLASS的全局 锁,锁作用于一般对象时,锁住的是对应代码块。在HotSpot中JVM实现中,锁有个专门的名字:对象监视器。
当多个线程同时请求某个对象监视器时,对象监视器会设置几种状态用来区分请求的线程 Contention List:所有请求锁的线程将被首先放置到该竞争队列,是个虚拟队列,不是实际的Queue的数据结构。
Entry List:EntryList与ContentionList逻辑上同属等待队列,ContentionList会被线程并发访问,为了降低对 ContentionList队尾的争用,而建立EntryList。
Contention List中那些有资格成为候选人的线程被移到Entry List 。Wait Set:那些调用wait方法被阻塞的线程被放置到Wait Set 。
OnDeck:任何时刻最多只能有一个线程正在竞争锁,该线程称为OnDeck 。Owner:获得锁的线程称为Owner !Owner:释放锁的线程 。
lock原理
LOCK -- 基于栈中的框架而不是对象级别
Lock不同于synchronized面向对象,它基于栈中的框架而不是某个具体对象,所以Lock只需要在栈里设置锁的开始和结束 (lock和unlock)的地方就行了(人工必须标明)。
,不用关心框架大小对象的变化等等。这么做的好处是Lock能提供无条件的、可轮询的、定时的、 可中断的锁获取操作。
综合来看对于所有的高并发情况,采用Lock加锁是最优选择,但是由于历史遗留等问题,synchronized也还是不能完全被淘汰,同时,在低并发情况下,synchronized的性能还是比Lock好的。
待完善。