深入理解Synchronized关键字

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-----
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值