上篇【从入门到放弃-Java】并发编程-线程安全中,咱们了解到,能够经过加锁机制来保护共享对象,来实现线程安全。html
synchronized是java提供的一种内置的锁机制。经过synchronized关键字同步代码块。线程在进入同步代码块以前会自动得到锁,并在退出同步代码块时自动释放锁。内置锁是一种互斥锁。java
本文来深刻学习下synchronized。编程
public class Synchronized { private static int count; private synchronized void add1() { count++; System.out.println(count); } public static void main(String[] args) throws InterruptedException { Synchronized sync = new Synchronized(); Thread thread1 = new Thread(() -> { for (int i = 0; i< 10000; i++) { sync.add1(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i< 10000; i++) { sync.add1(); } }); thread1.start(); thread2.start(); Thread.sleep(1000); System.out.println(count); } }
结果符合预期:synchronized做用于非静态方法,锁定的是实例对象,如上所示锁的是sync对象,所以线程可以正确的运行,count的结果总会是20000。安全
public class Synchronized { private static int count; private synchronized void add1() { count++; System.out.println(count); } public static void main(String[] args) throws InterruptedException { Synchronized sync = new Synchronized(); Synchronized sync1 = new Synchronized(); Thread thread1 = new Thread(() -> { for (int i = 0; i< 10000; i++) { sync.add1(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i< 10000; i++) { sync1.add1(); } }); thread1.start(); thread2.start(); Thread.sleep(1000); System.out.println(count); } }
结果不符合预期:如上所示,做用于非静态方法,锁的是实例化对象,所以当sync和sync1同时运行时,仍是会出现线程安全问题,由于锁的是两个不一样的实例化对象。并发
public class Synchronized { private static int count; private static synchronized void add1() { count++; System.out.println(count); } private static synchronized void add11() { count++; System.out.println(count); } public static void main(String[] args) throws InterruptedException { Synchronized sync = new Synchronized(); Synchronized sync1 = new Synchronized(); Thread thread1 = new Thread(() -> { for (int i = 0; i< 10000; i++) { Synchronized.add1(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i< 10000; i++) { Synchronized.add11(); } }); thread1.start(); thread2.start(); Thread.sleep(1000); System.out.println(count); } }
结果符合预期:锁静态方法时,锁的是类对象。所以在不一样的线程中调用add1和add11依然会获得正确的结果。jvm
public class Synchronized { private static int count; private void add1() { synchronized (this) { count++; System.out.println(count); } } private static synchronized void add11() { count++; System.out.println(count); } public static void main(String[] args) throws InterruptedException { Synchronized sync = new Synchronized(); Synchronized sync1 = new Synchronized(); Thread thread1 = new Thread(() -> { for (int i = 0; i< 10000; i++) { sync.add1(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i< 10000; i++) { sync1.add1(); } }); thread1.start(); thread2.start(); Thread.sleep(1000); System.out.println(count); } }
结果不符合预期:当synchronized同步方法块时,锁的是实例对象时,如上示例在不一样的实例中调用此方法仍是会出现线程安全问题。ide
public class Synchronized { private static int count; public String lock = new String(); private void add1() { synchronized (lock) { count++; System.out.println(count); } } private static synchronized void add11() { count++; System.out.println(count); } public static void main(String[] args) throws InterruptedException { Synchronized sync = new Synchronized(); Synchronized sync1 = new Synchronized(); Thread thread1 = new Thread(() -> { for (int i = 0; i< 10000; i++) { sync.add1(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i< 10000; i++) { sync1.add1(); } }); thread1.start(); thread2.start(); Thread.sleep(1000); System.out.println(count); System.out.println(sync.lock == sync1.lock); } }
结果不符合预期:当synchronized同步方法块时,锁的是其它实例对象时,如上示例在不一样的实例中调用此方法仍是会出现线程安全问题。性能
public class Synchronized { private static int count; public String lock = ""; private void add1() { synchronized (lock) { count++; System.out.println(count); } } private static synchronized void add11() { count++; System.out.println(count); } public static void main(String[] args) throws InterruptedException { Synchronized sync = new Synchronized(); Synchronized sync1 = new Synchronized(); Thread thread1 = new Thread(() -> { for (int i = 0; i< 10000; i++) { sync.add1(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i< 10000; i++) { sync1.add1(); } }); thread1.start(); thread2.start(); Thread.sleep(1000); System.out.println(count); System.out.println(sync.lock == sync1.lock); } }
结果符合预期:当synchronized同步方法块时,锁的虽然是其它实例对象时,但已上实例中,由于String = "" 是存放在常量池中的,实际上锁的仍是相同的对象,所以是线程安全的学习
public class Synchronized { private static int count; private void add1() { synchronized (Synchronized.class) { count++; System.out.println(count); } } private static synchronized void add11() { count++; System.out.println(count); } public static void main(String[] args) throws InterruptedException { Synchronized sync = new Synchronized(); Synchronized sync1 = new Synchronized(); Thread thread1 = new Thread(() -> { for (int i = 0; i< 10000; i++) { sync.add1(); } }); Thread thread2 = new Thread(() -> { for (int i = 0; i< 10000; i++) { sync1.add1(); } }); thread1.start(); thread2.start(); Thread.sleep(1000); System.out.println(count); } }
结果符合预期:当synchronized同步方法块时,锁的是类对象时,如上示例在不一样的实例中调用此方法是线程安全的。this
public class Synchronized { private static int count; public static void main(String[] args) throws InterruptedException { synchronized (Synchronized.class) { count++; } } }
使用javap -v Synchronized.class反编译class文件。
能够看到synchronized其实是经过monitorenter和monitorexit来实现锁机制的。同一时刻,只能有一个线程进入监视区。从而保证线程的同步。
正常状况下在指令4进入监视区,指令14退出监视区而后指令15直接跳到指令23 return
可是在异常状况下异常都会跳转到指令18,依次执行到指令20monitorexit释放锁,防止出现异常时未释放的状况。
这其实也是synchronized的优势:不管代码执行状况如何,都不会忘记主动释放锁。
想了解Monitors更多的原理能够点击查看
由于monitor依赖操做系统的Mutex lock实现,是一个比较重的操做,须要切换系统至内核态,开销很是大。所以在jdk1.6引入了偏向锁和轻量级锁。
synchronized有四种状态:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。
没有对资源进行锁定,全部线程都能访问和修改。但同时只有一个线程能修改为功
在锁竞争不强烈的状况下,一般一个线程会屡次获取同一个锁,为了减小获取锁的代价 引入了偏向锁,会在java对象头中记录获取锁的线程的threadID。
由于偏向锁的撤销操做仍是比较重的,致使进入安全点,所以在竞争比较激烈时,会影响性能,可使用-XX:-UseBiasedLocking=false禁用偏向锁。
当偏向锁升级为轻量级锁时,其它线程尝试经过CAS方式设置对象头来获取锁。
CAS即compare and swap(比较并替换)。是一种乐观锁机制。一般有三个值
CAS可能遇到ABA问题,即内存中的值为A,变为B后,又变为了A,此时A为新值,不该该替换。
能够采起:A-1,B-2,A-3的方式来避免这个问题
自旋是消耗CPU的,所以在自旋一段时间,或者一个线程在自旋时,又有新的线程来竞争锁,则轻量级锁会膨胀为重量级锁。
重量级锁,经过monitor实现,monitor底层实际是依赖操做系统的mutex lock(互斥锁)实现。
须要从用户态,切换为内核态,成本比较高
本文咱们一块儿学习了
synchronized做为内置锁,虽然帮咱们解决了线程安全问题,可是带来了性能的损失,所以必定不能滥用。使用时请注意同步块的做用范围。一般,做用范围越小,对性能的影响也就越小(注意权衡获取、释放锁的成本,不能为了缩小做用范围,而频繁的获取、释放)。
本文为云栖社区原创内容,未经容许不得转载。