synchronized是JAVA中解决并发编程中最常用的方法。
synchronized的作用如下:
1、确保线程互斥访问同步代码
2、保证共享变量的修改能够及时可见
3、有效解决指令重排序问题
一、synchronized的基本使用
从语法的角度来看,synchronized的使用场景有
修饰普通方法
修饰静态方法
修饰代码块
下面通过几个例子了解一下synchronize的的使用和区别
1、没有同步的情况
package test;
public class NoSyncTest {
public void method1() {
System.out.println("method 1 start");
try {
System.out.println("method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("method 1 end");
}
public void method2() {
System.out.println("method 2 start");
try {
System.out.println("method 2 execute");
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("method 2 end");
}
public static void main(String[] args) {
final NoSyncTest test = new NoSyncTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
结果
method 1 start
method 1 execute
method 2 start
method 2 execute
method 2 end
method 1 end
两个线程异步执行,没有阻塞,method2执行的速度比1块。
2、普通方法同步
package test;
public class NoSyncTest {
public synchronized void method1() {
System.out.println("method 1 start");
try {
System.out.println("method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("method 1 end");
}
public synchronized void method2() {
System.out.println("method 2 start");
try {
System.out.println("method 2 execute");
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("method 2 end");
}
public static void main(String[] args) {
final NoSyncTest test = new NoSyncTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
结果:
method 1 start
method 1 execute
method 1 end
method 2 start
method 2 execute
method 2 end
从上面的结果可以看出两个方法是同步执行的。给普通方法加synchronized,本质上同步的是对象,即多个线程对同一个对象的同步方法的执行时线程安全的,即线程A执行该对象同步方法时,其他线程无法进行该对象的任意一个同步方法。
3、静态方法同步
package test;
public class NoSyncTest {
public static synchronized void method1() {
System.out.println("method 1 start");
try {
System.out.println("method 1 execute");
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("method 1 end");
}
public static synchronized void method2() {
System.out.println("method 2 start");
try {
System.out.println("method 2 execute");
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("method 2 end");
}
public static synchronized void method3() {
System.out.println("method 3 start");
try {
System.out.println("method 3 execute");
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("method 3 end");
}
public static void main(String[] args) {
final NoSyncTest test1 = new NoSyncTest();
final NoSyncTest test2 = new NoSyncTest();
new Thread(new Runnable() {
@Override
public void run() {
test1.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test2.method2();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
NoSyncTest.method3();
}
}).start();
}
}
结果:
method 1 start
method 1 execute
method 1 end
method 3 start
method 3 execute
method 3 end
method 2 start
method 2 execute
method 2 end
从上面的三个线程的执行看可以得知是线程同步的。synchronized关键字加在静态方法上,本质上同步的对象是该类,包括该类的所有实例以及该类直接调用静态同步方法都必须是线程安全的,即存在一个线程获得锁,正在通过该类的实例去执行静态同步方法或者直接通过类调用静态同步方法,其他线程都无法执行该类实例的所有静态同步方法或者通过类调用静态同步方法。
4、同步代码块
package test;
public class NoSyncTest {
public void method1() {
System.out.println("method 1 start");
try {
synchronized (this) {
System.out.println("method 1 execute");
Thread.sleep(3000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("method 1 end");
}
public void method2() {
System.out.println("method 2 start");
try {
synchronized (this) {
System.out.println("method 2 execute");
Thread.sleep(500);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("method 2 end");
}
public static void main(String[] args) {
final NoSyncTest test = new NoSyncTest();
new Thread(new Runnable() {
@Override
public void run() {
test.method1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.method2();
}
}).start();
}
}
结果:
method 1 start
method 1 execute
method 2 start
method 1 end
method 2 execute
method 2 end
如上,两个线程都进入了相应方法,但是线程在进入同步代码块之前,必须等待线程1同步代码块部分执行完毕。
二、synchronized实现原理
通过反编译下面的代码来看一下synchronized同步代码块如何实现同步
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
反编译结果如下:
关于这两条指令的作用:
monitorenter:
每个对象都有一个监视器锁(monitor),当monitor被占用时就会处于锁定状态。线程执行monitorenter命令获取monitor锁的过程如下:
1、如果monitor的进入数为0,则线程获取锁,并设置monitor的进入数为1
2、如果该线程已经占有该monitor,则进入数+1
3、如果其他线程占有该monitor,则monitor的进入数不为0,则该线程进入阻塞状态,直到monitor为0,重新获取monitor的所有权
monitorexit:
执行monitorexit的线程必须是monitor的所有者。
当执行该命令时,monitor的进入数-1,当monitor的进入数为0,该线程已经不再是该monitor的所有者,其他被这个monitor阻塞的线程可以尝试获取monitor的所有权。
在通过下面代码的反编译来看一下同步方法的实现原理:
public class SynchronizedDemo {
public synchronized void method() {
System.out.println("Method 1 start");
}
}
在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。synchronized既可以加在一段代码上,也可以加在方法上。
关键是,不要认为给方法或者代码段加上synchronized就万事大吉,看下面一段代码:
class Sync {
public synchronized void test() {
System.out.println("test开始..");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test结束..");
}
}
class MyThread extends Thread {
public void run() {
Sync sync = new Sync();
sync.test();
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
Thread thread = new MyThread();
thread.start();
}
}
}
运行结果: test开始.. test开始.. test开始.. test结束.. test结束.. test结束..
可以看出来,上面的程序起了三个线程,同时运行Sync类中的test()方法,虽然test()方法加上了synchronized,但是还是同时运行起来,貌似synchronized没起作用。
将test()方法上的synchronized去掉,在方法内部加上synchronized(this):
public void test() {
synchronized(this){
System.out.println("test开始..");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test结束..");
}
}
运行结果: test开始.. test开始.. test开始.. test结束.. test结束.. test结束..
一切还是这么平静,没有看到synchronized起到作用。
实际上,synchronized(this)以及非static的synchronized方法(至于static synchronized方法请往下看),只能防止多个线程同时执行同一个对象的同步代码段。
回到本文的题目上:synchronized锁住的是代码还是对象。答案是:synchronized锁住的是括号里的对象,而不是代码。对于非static的synchronized方法,锁的就是对象本身也就是this。
当synchronized锁住一个对象后,别的线程如果也想拿到这个对象的锁,就必须等待这个线程执行完成释放锁,才能再次给对象加锁,这样才达到线程同步的目的。即使两个不同的代码段,都要锁同一个对象,那么这两个代码段也不能在多线程环境下同时运行。
所以我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步。这叫减小锁的粒度,使代码更大程度的并发。原因是基于以上的思想,锁的代码段太长了,别的线程是不是要等很久,等的花儿都谢了。当然这段是题外话,与本文核心思想并无太大关联。
再看上面的代码,每个线程中都new了一个Sync类的对象,也就是产生了三个Sync对象,由于不是同一个对象,所以可以多线程同时运行synchronized方法或代码段。
为了验证上述的观点,修改一下代码,让三个线程使用同一个Sync的对象。
class MyThread extends Thread {
private Sync sync;
public MyThread(Sync sync) {
this.sync = sync;
}
public void run() {
sync.test();
}
}
public class Main {
public static void main(String[] args) {
Sync sync = new Sync();
for (int i = 0; i < 3; i++) {
Thread thread = new MyThread(sync);
thread.start();
}
}
}
运行结果: test开始.. test结束.. test开始.. test结束.. test开始.. test结束..
可以看到,此时的synchronized就起了作用。
那么,如果真的想锁住这段代码,要怎么做?也就是,如果还是最开始的那段代码,每个线程new一个Sync对象,怎么才能让test方法不会被多线程执行。
解决也很简单,只要锁住同一个对象不就行了。例如,synchronized后的括号中锁同一个固定对象,这样就行了。这样是没问题,但是,比较多的做法是让synchronized锁这个类对应的Class对象。
class Sync {
public void test() {
synchronized (Sync.class) {
System.out.println("test开始..");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test结束..");
}
}
}
class MyThread extends Thread {
public void run() {
Sync sync = new Sync();
sync.test();
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
Thread thread = new MyThread();
thread.start();
}
}
}
运行结果: test开始.. test结束.. test开始.. test结束.. test开始.. test结束..