synchronized为java内置的关键字,用于保证一组代码的原子性以及该代码中共享变量的可见性(happens-before原则的Monitor Lock Rule),同时由于as-if-serial语义(不管怎么重排序,单线程下的执行结果不能被改变),该关键字又可以说是有序性的,所以synchronized在解决并发问题上可以说是“万能”的。从并发策略上来看,这种互斥同步(阻塞同步)锁是一种悲观锁。
那么如何使用呢?可以将该关键字加在方法(构造方法和接口方法例外)上,也可以使用synchronized(obj){}
的方式
如下代码中,m1等价于m2;m3等价于m4
public class SynchronizedTest {
public static void main(String[] args) {
SynchronizedTest test = new SynchronizedTest();
test.m1();
m3();
}
// 构造方法不支持synchronized关键字
// synchronized SynchronizedTest(){}
public synchronized void m1() {
System.out.println("m1...");
m2();
}
public void m2() {
synchronized (this) {
System.out.println("m2...");
}
}
public static synchronized void m3() {
System.out.println("m3...");
m4();
}
public static void m4() {
synchronized (SynchronizedTest.class) {
System.out.println("m4...");
}
}
}
上述代码中主线程在执行m1()时获取了test对象锁,但是在执行m2()时并不会阻塞,只是在test对象的monitor计数器中加一,执行完m2()后计数器减一,执行完m1()后计数器再减一变为0,此时释放test对象锁。同理在执行m3()时,获取了SynchronizedTest.class对象,在执行m4()时class对象计数器加一。
线程协作主要通过Object类的如下几个方法来完成,其实我一直没明白这些wait和notify方法为什么不给Thread类而是给了Object类。。。如果通过Thread.wait(lock)以及Thread.notifyAll(lock)调用方法,我感觉更符合认知直觉。
通常情况下方法1和4最为常用。同时根据源码注释,wait()方法一般伴随while循环使用。这其实也比较好理解,如果你用if判断使线程等待了,当该线程苏醒时就会继续往下执行了,但很有可能if中的条件还是true。
//As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
}
下面通过一个经典的生产者消费者程序来看下如何使用:
package com.hch.concurrency;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ProducerAndConsumer {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
int producerNum = 5;
int consumerNum = 3;
ProductQueue productQueue = new ProductQueue(5);
for (int i = 0; i < producerNum; i++) {
pool.execute(new Producer(3, productQueue));
}
for (int i = 0; i < consumerNum; i++) {
pool.execute(new Consumer(5, productQueue));
}
pool.shutdown();
}
static class Product {
private static int count = 0;
private int id = count++;
@Override
public String toString() {
return "product(id=" + id + ")";
}
}
static class ProductQueue {
private Product[] products;
private int tail;
private int head;
private int size;
ProductQueue(int size) {
if (size <= 0) {
throw new RuntimeException("size must > 0");
}
this.size = size;
this.head = (tail + 1) % size;
products = new Product[size];
}
public synchronized void put(Product product) {
while (products[(tail + 1) % size] != null) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int next = (tail + 1) % size;
products[next] = product;
tail = next;
System.out.printf("%s has been put at position %d%n", product, next);
notifyAll();
}
public synchronized Product take() {
while (products[head] == null) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Product product = products[head];
products[head] = null;
System.out.printf("%s has been taken from position %d%n", product, head);
head = (head + 1) % size;
notifyAll();
return product;
}
}
static class Producer implements Runnable {
private static int count;
private int id = count++;
private int num;
private ProductQueue queue;
Producer(int num, ProductQueue queue) {
this.num = num;
this.queue = queue;
System.out.printf("producer%d produces %d products%n", id, num);
}
@Override
public void run() {
for (int i = 0; i < num; i++) {
queue.put(new Product());
}
}
}
static class Consumer implements Runnable {
private static int count;
private int id = count++;
private int num;
private ProductQueue queue;
Consumer(int num, ProductQueue queue) {
this.num = num;
this.queue = queue;
System.out.printf("consumer%d consumes %d products%n", id, num);
}
@Override
public void run() {
for (int i = 0; i < num; i++) {
queue.take();
}
}
}
}
通过synchronized关键字将该put和take方法声明为一个原子操作(不会有其他线程干扰),生产者线程/消费者线程往队列里放/取东西时,必须获取this对象锁。
当队尾的下一个元素不为空时,可以认为队列已满,该生产者线程需要等待,并释放对象锁;当队头元素为空时,可以认为整个队列为空,该消费者线程需要等待,并释放对象锁。
当生产者线程/消费者线程放完/取出东西后需要通知其他线程,不然可能会使所有生产者消费者都处于无限期等待中。
若生产者线程T1通知到了另一个生产者线程T2并且此时队列已满,则T2进入等待状态并释放对象锁;同理若消费者线程T3通知到了另一个消费者线程T4并且此时队列为空,则T4进入等待状态并释放对象锁。
在JDK1.5之前synchronized关键字的效率非常差(向操作系统申请锁造成频繁的内核态切换),JDK1.6之后Hotspot虚拟机对这个同步锁进行了很大的优化,性能已和ReentrantLock不分上下。锁状态可以分为如下几个阶段,锁优化的过程和markword息息相关,下面通过这几个状态来分析一下
java -XX:+PrintFlagsFinal -version|grep -i biasedlocking
可以查看JVM关于偏向锁的配置,JDK8默认在JVM启动后4s后开启偏向锁。也就是说java程序启动四秒后new出来的对象都会被加上偏向标记,此时再上锁就是一个偏向锁。JVM是如何实现的呢?在锁对象的markword中存储一个指向加锁线程的指针,并将偏向位设为1.java -XX:+PrintFlagsFinal -version|grep -i PreInflateSpin
查看默认锁膨胀的条件。轻量级锁如何实现?线程在自己的栈帧中创建一个lock record,将锁对象的markword拷贝到这块区域,然后通过cas的方式在锁对象的markword中存储一个指向自己lock record的指针,将锁标志位置为00下面一段程序使用jol帮助我们查看锁和markword的对应关系。输出是按照little endian排列的,因此关于锁的标志位只需要看第一个字节(前8位)就行了。
import org.openjdk.jol.info.ClassLayout;
import java.util.concurrent.TimeUnit;
public class LockUpgradeTest {
public static void main(String[] args) throws InterruptedException {
testNonLock();
testLightWeightLock();
// testBiasedLock();
}
public static void testBiasedLock() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(4100);
System.out.printf("%n----------------可以偏向----------------%n");
Object biasedLockObj = new Object();
System.out.println(ClassLayout.parseInstance(biasedLockObj).toPrintable());
System.gc();
System.out.printf("%n----------------偏向锁(gc age + 1)----------------%n");
synchronized (biasedLockObj) {
System.out.println(ClassLayout.parseInstance(biasedLockObj).toPrintable());
}
System.out.printf("%n----------------偏向锁升级----------------%n");
Object stillBiasedLockObj = new Object();
Thread t = new Thread(() -> {
synchronized (stillBiasedLockObj) {
System.out.println("thread....");
System.out.println(ClassLayout.parseInstance(stillBiasedLockObj).toPrintable());
}
});
synchronized (stillBiasedLockObj) {
System.out.println(ClassLayout.parseInstance(stillBiasedLockObj).toPrintable());
t.start();
//只能看到偏向锁升级到重量级锁
System.out.println(ClassLayout.parseInstance(stillBiasedLockObj).toPrintable());
}
System.out.printf("%n----------------解锁----------------%n");
TimeUnit.MILLISECONDS.sleep(1000);
System.out.println(ClassLayout.parseInstance(stillBiasedLockObj).toPrintable());
}
public static void testLightWeightLock() throws InterruptedException {
System.out.printf("%n----------------无锁----------------%n");
Object lightWeightLock = new Object();
System.out.println(ClassLayout.parseInstance(lightWeightLock).toPrintable());
System.gc();
System.out.printf("%n----------------轻量级锁----------------%n");
synchronized (lightWeightLock) {
System.out.println(ClassLayout.parseInstance(lightWeightLock).toPrintable());
}
System.out.printf("%n----------------解锁(gc age+1)----------------%n");
TimeUnit.MILLISECONDS.sleep(200);
System.out.println(ClassLayout.parseInstance(lightWeightLock).toPrintable());
System.out.printf("%n----------------轻量级锁升级----------------%n");
Object LWLock = new Object();
Thread t = new Thread(() -> {
synchronized (LWLock) {
System.out.println("thread:");
System.out.println(ClassLayout.parseInstance(LWLock).toPrintable());
}
});
synchronized (LWLock) {
System.out.println(ClassLayout.parseInstance(LWLock).toPrintable());
t.start();
System.out.println(ClassLayout.parseInstance(LWLock).toPrintable());
}
// t.join();
TimeUnit.SECONDS.sleep(1);
System.out.printf("%n----------------解锁----------------%n");
System.out.println(ClassLayout.parseInstance(LWLock).toPrintable());
}
public static void testNonLock() throws InterruptedException {
System.out.printf("%n----------------无锁----------------%n");
Object nonLockObj = new Object();
System.out.println(ClassLayout.parseInstance(nonLockObj).toPrintable());
System.out.printf("%n----------------依旧无锁----------------%n");
TimeUnit.SECONDS.sleep(3);
Object stillNonLockObj = new Object();
System.out.println(ClassLayout.parseInstance(stillNonLockObj).toPrintable());
}
}
深入理解java虚拟机
Synchronized你以为你真的懂?