Synchronized关键字:
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
synchronized关键字是不能继承的,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要显式的指定它的某个方法为synchronized方法;
1、synchronized修饰成员方法
public synchrnized void method(...) {......}
输出结果:
2、synchronized修饰静态方法:
public synchronized static method(...) {......}
3、synchronized修饰代码块
synchronized(Object o){......}
synchronized(Object.class) {......}
三种方式比较:
synchronized修饰成员方法,锁的是当前类的对象,等同于synchronized(this){......};
synchronized修饰静态方法,锁的事当前的类对象,等同于synchronized(Object.class){......}
synchronized锁机制简介:
synchronized是基于进入和退出管程对象monitor实现的。
对象的内存简图:
- 对象头:存储对象的hashCode、锁信息或分代年龄、GC标志,类型指针指向对象的类元数据,JVM通过通过类型指针确定对象的类信息。
- 实例变量:存放类的属性数据信息,包括父类的属性信息
- 填充数据:非必须存在,仅仅为了字节对齐
当对象加锁时,数据记录在对象头中。当执行synchronized同步方法或同步代码块时,会在对象头中记录锁标记,锁标记指向monitor对象。每个对象都存在一个monitor与之关联,对象与其对对应的monitor之间的关系有多种实现方式,如monitor可以与对象一起创建,或者当线程试图获取对象锁时自动生成,但当一个monitor被某个线程持有后,它就处于锁定状态。
在JVM中,monitor是被ObjectMonitor实现的。ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,以及_Owner标记。其中_WaitSet用于管理等待队列(wait)线程,_EntryList用于管理锁池阻塞线程,_Owner标记用于记录当前执行线程。如下图:
当多线程并发访问同一同步代码时,首先会进入_EntryList,当线程获取锁标记后,monitor中的_Owner记录此线程,并在monitor中的计数器执行递增计算,代表锁定。其他线程在_EntryList中继续阻塞。若执行线程调用wait方法,则monitor中的计数器将被赋值为0,并将_Owner标记赋值为null,代表放弃锁。执行线程进入_WaitSet中阻塞。若执行线程调用notify/notifyAll方法,_WaitSet中的线程被唤醒,进入_EntryList中阻塞,等待获取锁标记。若执行线程的同步代码执行结束,同样会释放锁标记,monitor中的_Owner标记赋值为null,并且计数器赋值为0.
wait、notify、sleep:
wait()、notify()、notifyAll()是Object类的成员方法,sleep()是Thread类的类方法。
- final void notify() 唤醒在此对象监视器上等待的单个线程。
- final void notifyAll() 唤醒在此对象监视器上等待的所有线程。
- final void wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
- final void wait(long timeout) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
- static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响
输出结果:
在Thread1的同步方法中,调用wait()方法后,线程开始进入阻塞状态。在Runnable的同步方法中调用notifyAll()方法,唤醒等待的线程后,Thread1中的同步方法开始执行,而Runnabe中的同步方法,由于在唤醒其他等待的线程后,调用了sleep()方法,休眠了0.2S,这段时间内它不参与CPU资源的争夺,直到休眠时间结束。
- sleep()方法,正在执行的线程主动让出CPU去执行其他线程,在sleep()方法指定的时间过后,CPU才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep()方法并不会释放锁,即使当前线程使用sleep()方法让出了CPU,但其他被同步锁挡住了的线程也无法得到执行。
- wait()在一个已经进入了同步锁的线程内进行调用,让当前线程暂时让出同步锁,以便其他正在此锁的线程可以得到同步锁并运行。当其他线程调用了notify()方法后,调用wait()方法的线程就会解除wait状态,当再次获得同步锁后,程序可以继续向下执行。
即:如果被thread2先获取锁,则thread1在这0.2S内也是无法获取CPU资源的,这时输出的结果是:
如果想让thread1执行,thread2等待,只需更改代码如下:
输出结果:
注意:调用wait()、notify()、notifyAll()方法时,可能会抛出异常 IllegalMonitorStateException。因为调用wait(),notify()和notifyAll()的线程在调用这些方法前必须"拥有"对象的锁。当前的线程不是此对象锁的所有者,却调用该对象的notify(),notify(),wait()方法时抛出该异常。如上面调用wait()方法时,不用o.wait(),而直接用wait(),等价于this.wait(),但是锁对象并不是this,而是Object o , 这再运行时就会抛出异常。