synchronized原理
相信大家对于synchronized都不陌生,并且或许你还有一种记忆关于synchronized和Lock的优点和缺点。大家都觉得Lock怎么怎么方便,synchronized方法是重量级锁,但是其实在Java版本不断的更新中,1.6对synchronized进行了各种优化,他的情况有很大的改善,之前看到过一篇博客详细分析了synchronized的性能,其实越来越好了,有些甚至超过Lock等性能,具体博客有些忘了0-0。
实现同步
1、普通同步方法,锁的是当前实例对象
2、静态同步方法,锁的是当前类的Class对象
3、 同步代码块,锁的是Synchronized括号例配置的对象
JVM中Synchronized的实现原理是,JVM基于进入和退出Monitor对象来实现方法的同步和代码块同步,但两者实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现,monitorenter指令在编译后插入到同步代码块的开始位置,monitorexit插入到方法结束异常处,JVM保证monitorenter和monitorexit是成对出现的。任何对象都又一个monitor与之关联,当且一个monitor被持有后,他将处于锁定状态。线程执行到monitorenter指令时,会尝试获取对象对应的monitor的所有权,即尝试获取锁。
问题实例
public class ThreadDomain01 {
private int num = 0;
public void addNum(String userName)
{
try
{
if ("a".equals(userName))
{
num = 100;
System.out.println(Thread.currentThread().getName()+" set over!");
Thread.sleep(2000);
}
else
{
num = 200;
System.out.println(Thread.currentThread().getName()+" set over!");
}
System.out.println(userName + " num = " + num);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
设计线程a、b去调用上面的方法
public class MyThread01 {
public static void main(String[] args) {
ThreadDomain01 th01 = new ThreadDomain01();
Thread aThread = new Thread(new aRunner(th01), "a");
Thread bThread = new Thread(new bRunner(th01), "b");
aThread.start();
bThread.start();
}
static class aRunner implements Runnable {
private ThreadDomain01 thd;
public aRunner(ThreadDomain01 thd) {
this.thd = thd;
}
@Override
public void run() {
thd.addNum("a");
}
}
static class bRunner implements Runnable {
private ThreadDomain01 thd;
public bRunner(ThreadDomain01 thd) {
this.thd = thd;
}
@Override
public void run() {
thd.addNum("b");
}
}
}
// 运行结果
// a set over!
// b set over!
// b num = 200
// a num = 200
为什么 a num = 200呢?
1、线程a运行,给num赋值100,打印信息,开始睡觉
2、b开始运行,给num赋值200,然后打印b num = 200
3、a醒来,由于同为一个num,所以被b修改后就成了a num = 200
解决方案:addNum方法加上同步,synchronized
public synchronized void addNum(String userName)
{
try
{
if ("a".equals(userName))
{
num = 100;
System.out.println(Thread.currentThread().getName()+" set over!");
Thread.sleep(2000);
}
// 省略同样的代码
//结果
//a set over! 停顿2秒后打印下一句
//a num = 100
//b set over!
// b num = 200
我们看到是a和b是一个执行完另外一个才会执行。如果我们将线程a和b中分别传入不同的ThreadDomain01对象,则a,b会交叉打印,证明了synchronized的锁是对象锁。
同步方法域非同步方法的执行
再看一个例子,同步方法和非同步方法
public class ThreadDomain02 {
private int num = 0;
public synchronized void syncMethod() {
try {
System.out.println("Begin syncMethod, threadName = " +
Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("End syncMethod, threadName = " +
Thread.currentThread().getName() + ", end Time = " +
System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void asyncMethod() {
try {
System.out.println("Begin asyncMethod, threadName = " +
Thread.currentThread().getName() + ", begin time = " +
System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("End asyncMethod, threadName = " +
Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
两个线程执行一下
public class MyThread02 {
public static void main(String[] args) {
ThreadDomain02 th02 = new ThreadDomain02();
Thread syncThread = new Thread(new aRunner(th02), "sync");
Thread asyncThread = new Thread(new bRunner(th02), "async");
syncThread.start();
asyncThread.start();
}
static class aRunner implements Runnable {
private ThreadDomain02 thd;
public aRunner(ThreadDomain02 thd) {
this.thd = thd;
}
@Override
public void run() {
thd.syncMethod();
}
}
static class bRunner implements Runnable {
private ThreadDomain02 thd;
public bRunner(ThreadDomain02 thd) {
this.thd = thd;
}
@Override
public void run() {
thd.asyncMethod();
}
}
}
/**
*Begin syncMethod, threadName = sync
*Begin asyncMethod, threadName = async, begin time = 1533030460434
*End syncMethod, threadName = sync, end Time = 1533030465481
*End asyncMethod, threadName = async
*/
从结果可以看出,执行同步线程过程中,并不影响非同步线程的运行,当然如果非同步方法也加上synchronized 的关键子,则同步方法则因持有同样的对象锁而排队执行。
Synchronized同步代码块
用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间。这种情况下可以尝试使用synchronized同步语句块来解决问题。
使用synchronized同步代码块可以将需要锁起来的代码锁起来,而其他的任务则不需要,减少了代码的执行时间,也使锁更加轻便。
其实和synchronized方法一样,被圈起来的代码,在一线程访问时,他就对外是锁定状态,其他线程访问时会被阻塞。
public class ThreadDomain03 {
public void syncMethodA() {
synchronized (this) {
try {
System.out.println("Begin syncMethodA, threadName = " +
Thread.currentThread().getName() + ", begin Time = " +
System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("End syncMethodA, threadName = " +
Thread.currentThread().getName() + ", end Time = " +
System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void syncMethodB() {
synchronized (this) {
System.out.println("Begin syncMethodB, threadName = " +
Thread.currentThread().getName() + ", begin time = " +
System.currentTimeMillis());
System.out.println("End syncMethodB, threadName = " +
Thread.currentThread().getName() + ", end time = " +
System.currentTimeMillis());
}
}
}
public class MyThread03 {
public static void main(String[] args) {
ThreadDomain03 th = new ThreadDomain03();
Thread syncThreadA = new Thread(new aRunner(th), "syncA");
Thread syncThreadB = new Thread(new bRunner(th), "syncB");
syncThreadA.start();
syncThreadB.start();
}
static class aRunner implements Runnable {
private ThreadDomain03 thd;
public aRunner(ThreadDomain03 thd) {
this.thd = thd;
}
@Override
public void run() {
thd.syncMethodA();
}
}
static class bRunner implements Runnable {
private ThreadDomain03 thd;
public bRunner(ThreadDomain03 thd) {
this.thd = thd;
}
@Override
public void run() {
thd.syncMethodB();
}
}
}
// Begin syncMethodA, threadName = syncA, begin Time = 1533035257623
// End syncMethodA, threadName = syncA, end Time = 1533035262680
// Begin syncMethodB, threadName = syncB, begin time = 1533035262684
// End syncMethodB, threadName = syncB, end time = 1533035262687
看到syncMethodB方法的同步块访问必须等到syncMethodA方法同步块访问结束之后才可以运行。
结论
1、当A线程访问对象的synchronized代码块的时候,B线程依然可以访问对象方法中其余非synchronized块的部分(默认)
2、当A线程进入对象的synchronized代码块的时候,B线程如果要访问这段synchronized块,那么访问将会被阻塞
3、synchronized快获得的是一个对象锁,也就是说synchronized块锁定的是整个对象