Synchronized如何使用:
首先这是一个java的关键字,下面介绍如何使用它:
不使用Synchronized:
public class synchronizeddemo { public static int a = 0; public static void add(){ a++; } public static void main(String[] args) { ExecutorService exes = Executors.newCachedThreadPool(); for(int i=0;i<5000;i++){ exes.execute(()->{ add(); }); } exes.shutdown(); System.out.println(a); } }
结果:4879
由此可以看出add不是线程安全的方法。
使用方式一: Synchronized作用在静态方法上
public class synchronizeddemo { public static int a = 0; public synchronized static void add(){ a++; } public static void main(String[] args) { ExecutorService exes = Executors.newCachedThreadPool(); for(int i=0;i<5000;i++){ exes.execute(()->{ add(); }); } exes.shutdown(); System.out.println(a); } }
结果:5000
由此可以看出add变成线程安全的方法了。
注意:当Synchronized作用在静态方法上的时候,它锁定的是这个类synchronizeddemo.class对象锁。
使用方式二:Synchronized作用在普通方法上
public class synchronizeddemo { public int a = 0; public synchronized void add(){ a++; } public void sout(){ System.out.println(a); } public static void main(String[] args) { synchronizeddemo s = new synchronizeddemo(); ExecutorService exes = Executors.newCachedThreadPool(); for(int i=0;i<5000;i++){ exes.execute(()->{ s.add(); }); } exes.shutdown(); try { Thread.sleep(300); s.sout(); } catch (InterruptedException e) { e.printStackTrace(); } } }结果:5000
注意:注意上述两个例子,因为静态方法不属于实例对象,普通方法作用实例对象,所以一个线程调用静态方法,另一个线程调用非静态方法,二者不会互斥。
使用方法三:synchronized同步代码块
public class synchronizeddemo { public int a = 0; public void add(){ a++; } public void sout(){ System.out.println(a); } public static void main(String[] args) { synchronizeddemo s = new synchronizeddemo(); ExecutorService exes = Executors.newCachedThreadPool(); for(int j=0;j<10;j++){ exes.execute(()-> { synchronized (s){ for (int i = 0; i < 100; i++) { s.add(); } } }); } exes.shutdown(); try { Thread.sleep(300); s.sout(); } catch (InterruptedException e) { e.printStackTrace(); } } }
结果:1000
注意:当需要同步的方法比较多,可以用Synchronized圈住一个代码块,注意 synchronized (s){...}实际上锁住的是当前对象实例。
Synchronized底层原理
首先我们编写两种锁的实现方式
1、将synchronized写在方法上
2、将synchronized写在方法里的this上
直接查看字节码指令
我们可以看到红色箭头的地方,有两条字节码指令,monitorenter和monitorexit。
monitorenter表明了代码的开始位置,当前线程试图获取test2对象实例的锁,如果锁计数器为0,那么成功取得monitor,并将计数器置位1,monitorexit标明了截止位置,运行该指令后计数器减1,最后计数器为0时,其他线程才有机会获得这个对象实例的锁。如果有异常发生,会默认执行第二个monitorexit,强制释放锁。这就是JVM帮我们做到的锁的控制。
额外知识点:当一个拥有该实例锁的线程再次执行该实例的方法(i++)这是计数器会变成2(1+1),这就是可重入锁!同理释放也是不断减一,减到0后释放锁。
偏向锁:
在无竞争的环境下,当一个线程拥有一个锁,再次请求锁定的资源时,不会进行额外的申请锁释放锁步骤,而是直接使用资源。
轻量级锁:
在竞争环境下,偏向锁失效后会升级为轻量级锁,轻量级锁适用于线程交替执行同步块的场合。
自旋锁:
说白了就是暂时的自我循环等待,不让线程进入内核态的阻塞,一旦得到锁就执行程序,否则到一定时候就要进入内核态挂起。