synchronizedjava
用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一个时间内只能有一个线程获得执行。另外一个线程必须等待当前线程执行完这个代码块之后才能执行该代码块。并发
要理解synchoronized原理,咱们先讲讲虚拟机(hotspot为例,如下同)对象头部分的布局。对象头分为两部分信息,第一部分用于存储对象自身运行时的数据,如hashcode,GC分代年龄等,总计64位(64位虚拟机,32位虚拟机32位),官方成为mark word,另外一部分用于存储指向方法区的对象类型指针。性能
normal object headerspa
说是normal object header,是相对于加过偏向锁的object(下文再说)。能够看到对象头里面有1bit偏向锁的描述,2bit锁的描述。究竟是不是64位,咱们能够经过Unsafe(一个颇有趣的类)来验证下:.net
public class Cvolatile { private volatile boolean flag=false; //unsafe不能直接构造,经过反射获得 public static Unsafe getUnsafe() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{ Field field=Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe=(Unsafe) field.get(null); return unsafe; } public static void headerByte() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{ Unsafe unsafe=getUnsafe(); Field field=Cvolatile.class.getDeclaredField("flag"); System.out.println(unsafe.objectFieldOffset(field)); } public static void main(String args[]) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{ headerByte(); } }
以上程序段新建了一个只有boolean变量的类,而后经过unsafe获取了flag的偏移量,控制台输出是8,说明在64位jdk中object header的确是8*8便是64bit。线程
同步代码块是经过MonitorEnter和MonitorExit专用字节码指令来实现,即在入口区加上MonitorEnter,在出口处加上MonitorExit。那JVM又是怎样维护竞争同一个对象锁的线程呢,如下贴出各大博客都有的流程:指针
Contention List:全部请求锁的线程将被首先放置到该竞争队列。code
Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List。orm
Wait Set:那些调用wait方法被阻塞的线程被放置到Wait Set。
OnDeck:任什么时候刻最多只能有一个线程正在竞争锁,该线程称为OnDeck
Owner:得到锁的线程称为Owner。
新请求锁的线程将首先被加入到ConetentionList中,当某个拥有锁的线程(Owner状态)调用unlock以后,若是发现EntryList为空则从ContentionList中移动线程到EntryList。ContentionList是个后进先出的队列,ContentionList会被线程并发访问,为了下降对ContentionList队尾的争用,而创建EntryList。Owner线程在unlock时会从ContentionList中迁移线程到EntryList,并会指定EntryList中的某个线程(通常为Head)为Ready(OnDeck)线程。Owner线程并非把锁传递给OnDeck线程,只是把竞争锁的权利交给OnDeck,OnDeck线程须要从新竞争锁。OnDeck线程得到锁后即变为owner线程,没法得到锁则会依然留在EntryList中,考虑到公平性,在EntryList中的位置不发生变化(依然在队头)。若是Owner线程被wait方法阻塞,则转移到WaitSet队列;若是在某个时刻被notify/notifyAll唤醒,则再次转移到EntryList。
自旋锁
基于互斥同步的锁对性能最大的影响是阻塞的实现,挂起线程和恢复线程都须要装入内核态中完成。同时,共享数据的锁定状态只会持续一会,为了这段时间挂起和恢复线程都不值得。若是物理机器上有1个以上处理器,能让2个或2个线程同时执行,咱们可让后面请求锁的线程"稍等一会“,但并不放弃处理器的执行时间,看看当前持有锁的线程是否很快就会释放锁,为了让线程等待,咱们让线程执行一个忙循环(能够是执行几条空的汇编指令,执行几回循环,就是占着CPU不干事--),这就是所谓的自旋锁。
synchronized中线程在进入ContentionList时首先进行自旋尝试得到锁,若是不成功再进入等待队列。这对那些已经在等待队列中的线程来讲,稍微显得不公平。
偏向锁
偏向锁就是为提升性能在无竞争的状况下把整个同步都给取消掉。它会偏向于第一个获取它的线程,若是在接下来的执行过程当中,没有其余线程竞争这个锁,则持有锁的线程永远不须要再进行同步。当虚拟机启用偏向锁,锁对象第一次被线程获取的时候,虚拟机会将biased lock设置为1即偏向模式,同时使用CAS把得到锁的线程ID写入mark word,若是CAS操做成功,持有锁的线程进入这个锁相关的同步块时候,虚拟机将不进行任何操做。
成功加了偏向锁后的对象头
—————————————————我是分割线—————————————————————
好玩的Unsafe类又出现了,咱们能够利用Unsafe来实现简单的sychronized功能
public static void csynchronized(Object syn) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InterruptedException{ Unsafe unsafe=getUnsafe(); unsafe.monitorEnter(syn); for(int i=0;i<10;i++){ Thread.currentThread().sleep(100l); System.out.println(Thread.currentThread().getId()); } unsafe.monitorExit(syn); } public static void main(String args[]) { final Object syn=new Object(); new Thread(new Runnable(){ public void run() { try { csynchronized(syn); } catch (NoSuchFieldException e) { } catch (SecurityException e) { } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } catch (InterruptedException e) { } } }).start(); new Thread(new Runnable(){ public void run() { try { csynchronized(syn); } catch (NoSuchFieldException e) { } catch (SecurityException e) { } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } catch (InterruptedException e) { } } }).start(); }
从控制台结果能够看出线程ID之间并无穿插,说明实现了synchronized基本功能。固然咱们若是只保留csynchronized中的for循环再次运行能够发现线程ID之间有穿插。
———synchronized先到此,接下来将要说说Lock