深入理解Synchronized关键字

    技术2022-07-13  82

    SynChronized

    官方定义

    防止线程看绕和内存一致性的错误,如果一个对象对多个线程课件,则该对象的变量的所有读取或写入方法都是通过同步方法来完成的。

    作用

    能保证在同一时刻最多只有一个线程执行该段代码,以达到并发安全的效果。

    地位

     ◆ Java的关键字,被Java原生支持  ◆ 最基本的互斥同步手段  ◆ 并发编程中元老级别角色

    不使用的后果

    通过一个小Demo体现

    public class Threadtest implements Runnable{ static Threadtest instance = new Threadtest(); static int i=0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } public void run() { for (int j=0;j<10000;j++){ i++; } } }

    最终输出的计算结果少于预期值20000

    Synchronized的两个用法

    作为对象锁使用

    { ◇ 方法锁(默认锁对象是this当前实例对象) ◇ 同步代码块锁(自动锁定对象) }

    作为类锁使用

    { ◇ 静态方法锁 ◇ Class对象 }

    类锁补充:只能在同一时刻被一个对象拥有,一个Java类可能有很多对象,但只有一个Class对象

    Synchronized的性质

    ◇ 可重入

    指的是同一线程的外层函数获得锁喉,内层函数可以直接再次获取该锁。又称递归锁 好处: 避免死锁 提升封装性 何为死锁? 既想拿到外部的新锁,又不释放本身的锁,造成永久等待的现象。

    ◇ 不可中断

    一旦这个锁被别人获得,如果自己本身还想获得,只能选择等待或者阻塞,直到别的线程释放掉这个锁,如果不释放,那将永远等待。

    缺陷

    ◆ 效率低

    锁的释放情况少[代码块执行结束、抛异常、同步对象的wait方法],识图获得锁时不能设定超时时间,不能中断一个正在试图获取锁的线程

    ◆ 不够灵活

    很难掌握加锁解锁的时机 以读写锁为例,读数据时不加锁,写数据时才加

    ◆ 无法知道是否成功拿到锁

    针对这些缺陷,Lock锁应运而生
    可以主动设置加锁解锁、设置超时时间等

    Synchronized的原理

    加锁和释放锁的底层是通过JVM字节码的monitor实现的

    可重入:通过加锁次数计时器来实现 线程拿到锁后,计数器+1,当相同线程在此对象上再次获得该锁时,再+1。任务离开时,计数-1。JVM负责跟踪对象被加锁的次数。

    Synchronized如何保证可见性?

    一旦代码块或方法被Synchronized修饰,在执行完毕后,被锁住的对象做的任何修改,在锁的释放前,都要从线程内存写回到主内存中,不会存在线程内存和主线程内存不一致的情况。由此保证可见性。

    使用Synchronized注意点:

    锁的对象不能为空
    作用域不宜过大,串行会影响程序执行效率
    避免死锁

    如何选择Synchronized和lock关键字?

    在有线程包的情况下优先使用包优先使用Synchronized,好处是减少代码量在特有情况下需要用到lock独有特性时,使用lock

    多线程访问同步方法的7种情况

    ◇ 两个线程同时访问一个对象的同步方法
    public class Demo1 implements Runnable { static Demo1 instance = new Demo1 (); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()){ } System.out.println("-----finished-----"); } public void run() { method(); } public synchronized void method(){ System.out.println("我是对象锁的方法修饰符形式,我叫"+Thread.currentThread().getName()); try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"运行结束"); } }

    结果

    我是对象锁的方法修饰符形式,我叫Thread-0
    Thread-0运行结束
    我是对象锁的方法修饰符形式,我叫Thread-1
    Thread-1运行结束
    -----finished-----

    解析

    因为拿到的是同一把锁[对象锁里的方法锁] 故线程安全

    ◇ 两个线程访问的是两个对象的同步方法
    public class Demo2 implements Runnable{ static Demo2 instance1 = new Demo2(); static Demo2 instance2= new Demo2(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance2); t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()){ } System.out.println("-----finished-----"); } public void run() { Synchronized(this){ System.out.println("我是对象锁的代码块形式,我叫"+Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"运行结束"); } } }

    结果

    我是对象锁的方法修饰符形式,我叫Thread-0
    我是对象锁的代码块形式,我叫Thread-1
    Thread-1运行结束
    Thread-0运行结束
    -----finished-----

    解析

    因为他们的采用锁对象不是同一个,所以线程互不干扰。

    ◇ 两个线程访问的是Synchronized的静态方法
    public class SynchronizedClassStatic4 implements Runnable{ static SynchronizedClassStatic4 instance1 = new SynchronizedClassStatic4(); static SynchronizedClassStatic4 instance2= new SynchronizedClassStatic4(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance2); t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()){ } System.out.println("-----finished-----"); } public void run() { method(); } public static synchronized void method(){ System.out.println("我是类锁的第一种形式,static,我叫"+Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("finished"); } }

    结果

    我是类锁的第一种形式,static,我叫Thread-0
    finished
    我是类锁的第一种形式,static,我叫Thread-1
    finished
    -----finished-----

    解析

    即使是两个实例,但是由于方法是静态方法,所以实际上隶属于同一把类锁,线程安全。

    ◇ 同时访问同步方法和非同步方法
    public class Method2Difference implements Runnable{ static Method2Difference instance = new Method2Difference(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()){ } System.out.println("-----finished-----"); } public void run() { if(Thread.currentThread().getName().equals("Thread-0")){ method1(); }else { method2(); } } public synchronized void method1() { System.out.println("我是加锁的方法1 我叫" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"运行完毕"); } public void method2(){ System.out.println("我是没加锁的方法2 我叫" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"运行完毕"); } }

    结果

    我是加锁的方法1 我叫Thread-0
    我是没加锁的方法2 我叫Thread-1
    Thread-0运行完毕
    Thread-1运行完毕
    -----finished-----

    解析

    Synchronized只作用于指定方法中,其他没加修饰符的方法不受影响。

    ◇ 访问同一个对象的不同的普通同步方法
    public class DifferentMethod implements Runnable{ static DifferentMethod instance = new DifferentMethod(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()){ } System.out.println("-----finished-----"); } public void run() { if(Thread.currentThread().getName().equals("Thread-0")){ method1(); }else { method2(); } } public synchronized void method1() { System.out.println("我是加锁的方法1 我叫" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"运行完毕"); } public synchronized void method2(){ System.out.println("我是加锁的方法2 我叫" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"运行完毕"); } }

    结果

    我是加锁的方法1 我叫Thread-0
    Thread-0运行完毕
    我是加锁的方法2 我叫Thread-1
    Thread-1运行完毕
    -----finished-----

    解析

    同一个实例,这两个方法拿到的锁(this)是一样的,故没法同时执行。

    ◇ 同时访问静态Synchronized和非静态Synchronized方法
    public class Method2Difference implements Runnable{ static Method2Difference instance = new Method2Difference(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()){ } System.out.println("-----finished-----"); } public void run() { if(Thread.currentThread().getName().equals("Thread-0")){ method1(); }else { method2(); } } public synchronized static void method1() { System.out.println("我是静态加锁的方法1 我叫" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"运行完毕"); } public synchronized void method2(){ System.out.println("我是非静态加锁的方法2 我叫" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"运行完毕"); } }

    结果

    我是非静态加锁的方法2 我叫Thread-1
    我是静态加锁的方法1 我叫Thread-0
    Thread-0运行完毕
    Thread-1运行完毕
    -----finished-----

    解析

    他们拿到的锁不一样,一个类锁,一个对象锁

    ◇ 方法抛出异常后,会释放锁
    public class ExceptionMethod implements Runnable{ static ExceptionMethod instance = new ExceptionMethod (); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while (t1.isAlive() || t2.isAlive()){ } System.out.println("-----finished-----"); } public void run() { if(Thread.currentThread().getName().equals("Thread-0")){ method1(); }else { method2(); } } public synchronized void method1() { System.out.println("我是抛异常的方法1 我叫" + Thread.currentThread().getName()); try { Thread.sleep(1000); throw new Exception (); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"运行完毕"); } public synchronized void method2(){ System.out.println("我是加锁的方法2 我叫" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"运行完毕"); } }

    真正的异常不该用Exception,而是用RunTimeException,加在catch后面

    结果

    我是抛异常的方法1 我叫Thread-0
    java.lang.Exception
    at newnew.ExceptionMethod.method1(ExceptionMethod.java:31)
    at newnew.ExceptionMethod.run(ExceptionMethod.java:21)
    at java.lang.Thread.run(Thread.java:748)
    Thread-0运行完毕
    我是加锁的方法2 我叫Thread-1
    Thread-1运行完毕
    -----finished-----
    Processed: 0.017, SQL: 9