Synchronized关键字

synchronzied关键字

简介

synchronized是Java中的关键字,是一种同步锁,当一个线程调用一个synchronized修饰的方法时,其他线程不能同时访问此方法,必须当前线程执行完,才能有新的线程竞争

Synchronzied的使用

修饰代码块

//修饰代码块
 public void text1(){
    synchronized (this){
    //work
     }
   }

synchronzied锁的是某一个对象,但是实际作用在代码块里

修饰普通方法

public synchronized void text2(){

}

锁的是对象实例

多个线程来竞争时,哪个线程先获取到对象实例,哪个线程先执行,执行结束后,其他线程才能执行。

看一个例子:

class text1 extends Thread {
    synchronizedDemo s1 = new synchronizedDemo();
    public text1(synchronizedDemo s){
        this.s1 = s;
    }

    @Override
    public void run() {
        s1.text1();
    }
}
class text2 extends Thread {
    synchronizedDemo s1 = new synchronizedDemo();
    public text2(synchronizedDemo s){
        this.s1 = s;
    }

    @Override
    public void run() {
        s1.text2();
    }
}
public class synchronizedDemo {
    public synchronized void text1(){
        
    }
    public synchronized void text2(){

    }
    public static void main(String[] args) {
        synchronizedDemo s = new synchronizedDemo();

        text1 text1 = new text1(s);
        text1 text2 = new text1(s);
        text1.start();
        text2.start();

    } 
}

此例子是 分别用两个类 分别调用一个实例对象的不同方法,它会并发执行吗?

答案是不会,因为Synchronzied修饰的是普通方法,锁的是对象实例,只有一个实例对象时,不会有多个线程同时访问。

还是刚才的例子 改一下

class text1 extends Thread {
    synchronizedDemo s1 = new synchronizedDemo();
    public text1(synchronizedDemo s){
        this.s1 = s;
    }

    @Override
    public void run() {
        s1.text1();
    }
}
class text2 extends Thread {
    synchronizedDemo s1 = new synchronizedDemo();
    public text2(synchronizedDemo s){
        this.s1 = s;
    }

    @Override
    public void run() {
        s1.text1();
    }
}
public class synchronizedDemo {
    public synchronized void text1(){

    }
    public synchronized void text2(){

    }

    public static void main(String[] args) {
        synchronizedDemo s1 = new synchronizedDemo();
        synchronizedDemo s2 = new synchronizedDemo();//加了一个对象实例
       
        text1 text1 = new text1(s1);//分别调用
        text1 text2 = new text1(s2);
        text1.start();
        text2.start();

    }

}

实现两个对象实例,两个类分别调用这两个对象实例的同一个方法,它会并发执行吗

答案是会的,因为是不同的对象实例,两个不影响

修饰静态方法

public synchronized static void text3(){

}

静态方法上 锁的是当前的class实例

如果两个线程调用 类名.text3 能不能同时并发执行?

答案是不行的因为这两个线程都是调用 class.text3,调用同一个class实例,所以只能串行执行

synchronized特点

  1. synchronized修饰的代码块或者方法,JVM只允许一个线程去访问,它是通过锁机制来完成同一时刻只能一个线程访问,就是临界区。
  2. synchronized满足 原子性,可见性,有序性

原理

修饰代码块

public class text{
    //修饰代码块
    public void text1(){
        synchronized (this){

        }
    }
    public void text2(){

    }
}

以上面代码为例 进行cmd反编译 javac - text.java 会生成 text.class字节码文件 然后Javap -v text 进行反编译
在这里插入图片描述
在这里插入图片描述

我们会看到加了synchronized关键字 两个方法的区别 text1 会出现 monitorentermonitorexit的东西 这就是加锁和解锁的,那么为什么会出现两个解锁,下面那个monitorexit 是为了预防处理异常的解锁

注意 里面有个 flags标志位 flags: ACC_PUBLIC 下面会看到不同

修饰普通方法

public class text{
    public synchronized  void text1(){
       
}
public void text2(){
    
}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZXstA5Fj-1613567563126)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210216210704851.png)]

红框中 flags: ACC_PUBLIC, ACC_SYNCHRONIZED

修饰静态方法

public class text{
    public synchronized static void text1(){
       
}
public void text2(){
    
}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AjZwREem-1613567563127)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210216210327387.png)]

flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED

我们可以看到无论是monitor` 还是 `ACC_SYNCHRONIZED 它本质上都是对一个对象的监视器(monitor)进行获取,而这个监视器的获取是排他的,同一时刻只能一个线程能获取到synchronized 所保护对象的监视器。

每个对象都有它的监视器,一个线程进来 先获取它的监视器,如果成功才能对对象进行操作,如果失败就会 进入到同步队列中等待(即获取锁失败,进入Blocking状态,等待获取锁资源) 获取到监视器的线程 monitorExit释放监视器,释放后才会重新竞争其对象的监视器。

monitor的排他性的实现是需要借助操作系统所提供的锁(借助硬件实现)来实现。

而synchronized也称为重量级锁,随着Java的发展,对于synchronized进行了优化,锁的优化:自旋锁,偏向锁,强重量锁,重量级锁。 实现思路:尽量通过代码层次进行加锁操作,减少系统交互。

使用场景

下列场景下线程是否安全?

场景一:两个线程同时访问同一个对象的同步方法。

线程安全

分析: 两个线程访问同一个对象锁,会互相等待b

***场景二:***两个线程同时访问两个对象的同步方法。

线程不安全

​ *分析:两个线程 对 不同的 对象进行操作,这是一种锁失效的情况,这种情况不存在互相竞争的关系,互相持有一把锁,互不干扰。

如何解决锁失效的问题:将方法用static 修饰,即形成类锁,即实现多个线程竞争同一把类锁

*场景三:*两个线程同时访问一个或两个对象的静态同步方法。

线程安全

​ **分析:**相当一多个线程竞争同一把类锁。

*场景四:*两个线程分别同时访问(一个或者两个)对象的同步方法(synchronized修饰)和非同步方法。

线程不安全

例子:

class TestDemo {
    public synchronized void fun1() {
        System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1开始");
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1结束");
    }

    public void fun3() {
        System.out.println(Thread.currentThread().getName() + ":: 非同步方法func3");
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":: 非同步方法func3");
    }
     public static void main(String[] args) {
        TestDemo test1 = new TestDemo();
        new Thread("A") {
            @Override
            public void run() {
                test1.fun1();  //lock
                test1.fun3();
            }
        }.start();

        new Thread("B") {
            @Override
            public void run() {
                test1.fun1(); //lock
                test1.fun3();
            }
        }.start();
        
        }
        }

看上面的代码 实现了text1用synchronized修饰,text3是普通方法(两个方法都使线程睡眠一秒),分别用一个对象调用两个相同方法,观察是不是顺序执行。

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IApjlbLT-1613567563130)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210217140553098.png)]

可以看到是线程非安全的 可以看到 顺序没有按照顺序打印,因为非同步方法不是线程安全的。

可以得出结论:非同步方法不受其他同步方法的影响

*场景五:*两个线程访问同一个对象中的同步方法,同步方法又调用另外一个非同步方法 。

线程不安全的

代码:

class TestDemo {
    public synchronized void fun1() {
        System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1开始");
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
            fun3(); //在同步方法中调用普通方法
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1结束");

    }
     public void fun3() {
        System.out.println(Thread.currentThread().getName() + ":: 非同步方法func3开始");
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":: 非同步方法func3结束");
    }
    
    public static void main(String[] args) {
        TestDemo test1 = new TestDemo();
        TestDemo test2 = new TestDemo();
        new Thread("A") {
            @Override
            public void run() {
                test1.fun1();  //unlock
               // test1.fun3();
            }
        }.start();

        new Thread("B") {
            @Override
            public void run() {
                test1.fun1(); //lock
               // test1.fun3();
            }
        }.start();

        Thread threadc = new Thread("C"){
            @Override
            public void run() {
                test1.fun3();
            }
        };
        threadc.start();
        }
        }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZecLSvzg-1613567563133)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210217143446852.png)]

此例子中为了测试结果 新建一个线程c调用普通方法,发现结果是非线程安全的,当我们把线程c去掉后 ,只留下AB 两个线程调用同步方法,会发现它是线程安全的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e5vE5tzU-1613567563135)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210217144204348.png)]

*分析:*在同步方法中调用的非同步方法,是不会受synchronized的影响的 ,其他线程可以调用非同步方法。但是若没有其他线程调用非同步方法,它是线程安全的

*场景六:*两个线程同时访问同一个对象的不同的同步方法

线程安全

分析:两个线程分别获取的为对象锁,即为同一把锁,所以还是会串行执行。而对象锁的作用范围是对象中所有的同步方法。所以它是线程安全的

*场景七:*两个线程同时访问静态synchronized和非静态synchornized方法

线程不安全

我们来看一下代码

class TestDemo {
    public synchronized void fun1() {
        System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1开始");
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1结束");

    }

    public synchronized static void fun2()  {
        //类锁  class对象
        System.out.println(Thread.currentThread().getName() + ":: 静态同步方法func2开始");
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":: 静态同步方法func2结束");
    }
        public static void main(String[] args) {
        TestDemo test1 = new TestDemo();
        TestDemo test2 = new TestDemo();
        new Thread("A") {
            @Override
            public void run() {
                test1.fun1();  //lock
            }
        }.start();

        new Thread("B") {
            @Override
            public void run() {
                    fun2()}
        }.start();
        
        }
        }

看一下结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qjP9tYqN-1613567563136)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210217145847744.png)]

它没有按我们预想的结果 所以是非线程安全的

*分析:*静态synchronized锁的是类锁(.class)而非静态synchronized锁的是对象锁,所以互不影响,它是非线程安全的

*场景八:*同步方法抛出异常,JVM会自动释放锁

只有当一个线程的同步方法出现异常时,会释放锁,不会造成死锁现象,其他线程继续运行。

class TestDemo {
    public synchronized void fun1()  {
        System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1开始");
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":: 同步方法fun1结束");
    }
        public static void main(String[] args) {
        TestDemo test1 = new TestDemo();
        Thread threadc = new Thread("C"){
            @Override
            public void run() {
                test1.fun3();
            }
        };
        threadc.start();
        threadc.interrupt();

        new Thread("D"){
            @Override
            public void run() {
                test1.fun1();
            }
        }.start();
    }
}

这里使用interrupt中断线程c的睡眠,达到抛出异常的目的

来看结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TPEjn8vk-1613567563137)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20210217150906582.png)]

这里使用interrupt中断线程c的睡眠,达到抛出异常的目的

来看结果:

[外链图片转存中...(img-TPEjn8vk-1613567563137)]

我们看到D线程还是执行完成 说明线程是安全的

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值