楔子
上一篇文章,我们学习了volatile关键字,这边文章我们继续学习并发编程中常用的另一个关键字——synchronized。
synchronized使用方法
首先我们来看synchronized常见的几种使用方法。
使用synchronized(this)修饰代码块
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized (this){
for (int i = 0; i < 50; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class Test {
public static void main(String[] args) {
System.out.println("同一个对象,使用synchronized关键字");
SyncThread syncThread = new SyncThread();
Thread thread1 = new Thread(syncThread, "同一个对象:线程一");
Thread thread2 = new Thread(syncThread, "同一个对象:线程二");
thread1.start();
thread2.start();
}
}
执行结果
同一个对象:线程一:0
......
同一个对象:线程二:99
使用synchronized修饰普通方法
我们修改SyncThread类,为run方法加上synchronized关键字
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public synchronized void run() {
for (int i = 0; i < 50; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果
同一个对象:线程一:0
......
同一个对象:线程二:99
使用synchronized修饰静态方法
我们修改SyncThread类,提取方法为静态方法,并且为提取的方法加上synchronized关键字
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
doSome();
}
private synchronized static void doSome() {
for (int i = 0; i < 50; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
执行结果
同一个对象:线程一:0
......
同一个对象:线程二:99
使用synchronized(xxx.class)修饰一个类
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized (SyncThread.class){
for (int i = 0; i < 50; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
执行结果
同一个对象:线程一:0
......
同一个对象:线程二:99
小结
通过以上基础使用,我们可以大胆猜想synchronized其实本质上是对对象进行加锁的,无论是实例对象还是类对象,那为什么他会对对象加锁呢?其实synchronized底层的原理,是跟jvm指令和monitor有关系的。
synchronized底层原理
每个对象都有一个monitor,你如果用到了synchronized关键字,在底层编译后的jvm指令中,会有monitorenter和monitorexit两个指令。
monitor里面有一个从0开始的计数器。如果一个线程要获取monitor的锁,需要先看看对象的计数器是不是0,如果是0的话,那么说明没人获取锁,他就可以获取锁了,然后对计数器加1;如果计数器是1,那么说明当前锁已经被别人占有了,那么此时他就会等待一定时间后,再去尝试获取锁;如果出了synchronized修饰的范围,那么就会有一个monitorexit指令,那么该对象的monitor计数器就会减1。值的一提的是,monitor是可重入的,也就是说一个对象可以被加多次锁,加几次锁,计数器就递增几,释放锁的时候也递减,最后变为0。
下面是一个简单的例子
public class SynchronizedDemo {
public static void main(String[] args) {
synchronized (SynchronizedDemo.class) {
}
method();
}
private static void method() {
}
}
编译上面文件,并在target对应目录下执行javap -v SynchronizedDemo.class,你可以看到对应字节码如下:
wait与notify
首先我们看以下简单例子
class ThreadA extends Thread{
public ThreadA(String name) {
super(name);
}
public void run() {
synchronized (this) {
try {
// 使当前线阻塞 1 s,确保主程序的 t1.wait(); 执行之后再执行 notify()
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("等待5s结束 "+Thread.currentThread().getName()+" 被唤醒");
// 唤醒当前的wait线程
this.notify();
}
}
}
public class WaitTest {
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
synchronized(t1) {
try {
// 启动“线程t1”
System.out.println(Thread.currentThread().getName()+" 执行t1线程");
t1.start();
// 主线程等待t1通过notify()唤醒。
System.out.println(Thread.currentThread().getName()+" 开始等待");
// 不是使t1线程等待,而是当前执行wait的线程等待
t1.wait();
System.out.println(Thread.currentThread().getName()+" 等待结束,继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果为:
main 执行t1线程
main 开始等待
等待5s结束 t1 被唤醒
main 等待结束,继续执行
wait与notify的底层原理,同样是基于monitor的,底层分别调用了wait和set方法,所以必须对同一个对象进行加锁、wait、notify,这样的话,他们才是通一个对象里的monitor相关的计数器、wait、set进行操作的,才能够有效果。
思考
思考一
下面的例子count的值是99吗?为什么?
public static void main(String[] args) throws InterruptedException {
System.out.println("不同的对象,使用synchronized关键字");
Thread thread3 = new Thread(new SyncThread(), "不同的对象:线程三");
Thread thread4 = new Thread(new SyncThread(), "不同的对象:线程四");
thread3.start();
thread4.start();
}
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized (this){
for (int i = 0; i < 50; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
}
}
}
思考二
下面的例子count的值是99吗?为什么?
public static void main(String[] args) throws InterruptedException {
System.out.println("不同的对象,使用synchronized关键字");
Thread thread3 = new Thread(new SyncThread(), "不同的对象:线程三");
Thread thread4 = new Thread(new SyncThread(), "不同的对象:线程四");
thread3.start();
thread4.start();
}
class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
public void run() {
synchronized (SyncThread.class){
for (int i = 0; i < 50; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}