资讯专栏INFORMATION COLUMN

Synchronized关键字

hqman / 1937人阅读

摘要:我叫运行结束三线程不安全导致请求丢失问题解决场景前面一的作用中的计数场景。我叫运行结束方法抛异常后,是否会释放锁抛出异常之后会释放锁,后面的线程会进入同步方法。当一个线程获得了对应的锁的时候,其他线程只能等待我释放之后才能获取该锁。

一、Synchronized的作用
作用:能够保证在同一时刻最多只有一个线程执行该代码,以达到保证并发安全的效果
public class DisappearRequest implements Runnable{
    
    static DisappearRequest dr = new DisappearRequest();
    
    static int count = 0;
    
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(dr);
        Thread t2 = new Thread(dr);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count="+count);
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }

}
// 结果count小于20000(线程不安全)
二、Synchronized的两个用法

1. 对象锁:包括同步代码块锁(自己指定锁对象)和方法锁(默认锁对象为this当前实例对象)

1.1 代码块形式:手动指定锁对象
public class SynchronizedObjectCodeBlock implements Runnable {
    
    static SynchronizedObjectCodeBlock instance = 
            new SynchronizedObjectCodeBlock();
    
    
    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        
        t1.start();
        t2.start();
        
        while(t1.isAlive() || t2.isAlive()){
            
        }
        System.out.println("finished");
    }

    @Override
    public void run() {
        // 两个线程串行操作
        synchronized(this){
            System.out.println("我是对象锁的代码块形式。我叫:" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    }

}
public class SynchronizedObjectCodeBlock implements Runnable {
    
    static SynchronizedObjectCodeBlock instance = 
            new SynchronizedObjectCodeBlock();
    
    Object lock1 = new Object();
    Object lock2 = new Object();
    
    
    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        
        t1.start();
        t2.start();
        
        while(t1.isAlive() || t2.isAlive()){
            
        }
        System.out.println("finished");
    }

    @Override
    public void run() {
        synchronized(lock1){
            System.out.println("我是对象锁的代码块形式。我叫:" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
        
        synchronized(lock2){
            System.out.println("我是对象锁的代码块形式。我叫:" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "运行结束");
        }
    }

}
// CountDownLatch、信号量解决线程同步问题。
1.2 方法锁形式:synchronized修饰普通方法,锁对象默认为this
普通方法锁
public class SynchronizedMethodLock implements Runnable{

    static SynchronizedMethodLock instance = 
            new SynchronizedMethodLock();
    
    @Override
    public void run() {
        sync();
    }
    
    // 普通方法锁(不能是静态方法。锁对象默认是this)
    public synchronized void sync(){
        System.out.println("我是普通方法锁形式。我叫:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
    
    public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        
        t1.start();
        t2.start();
        
        while(t1.isAlive() || t2.isAlive()){
            
        }
        System.out.println("finished");
    }
    
    

}

2. 类锁:指synchronized修饰静态方法或指定锁为Class对象。Java类可能有很多个对象,但只有一个Class对象。
所谓的类锁,不过是Class对象的锁而已。
用法和效果:类锁只能在同一时刻被一个对象拥有,其他对象会被阻塞
对象锁如果不同的实例创建出来的,互相锁是不受影响的,你可以运行我也可以运行,并行运行,但是类锁只有一个可以运行。

2.1. synchronized加在static方法上。场景:如果需要在全局情况下同步该方法,而不是一个小范围层面,则应该用这种形式去做同步保护。
public class SynchronizedMethodLock implements Runnable{

    // 创建两个实例对象
    static SynchronizedMethodLock instance1 = 
            new SynchronizedMethodLock();
    static SynchronizedMethodLock instance2 = 
            new SynchronizedMethodLock();
    
    @Override
    public void run() {
        sync();
    }
    
    // 创建两个实例对象,如果不是static方法的话,则并行操作,否则串行执行。
    public static synchronized void sync(){
        System.out.println("我是类锁的第一种形式:static形式。我叫:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
    
    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        
        t1.start();
        t2.start();
        
        while(t1.isAlive() || t2.isAlive()){
            
        }
        System.out.println("finished");
    }
    
    

}
2.2. synchronized(*.class)代码块
public class SynchronizedMethodLock implements Runnable{

    // 创建两个实例对象
    static SynchronizedMethodLock instance1 = 
            new SynchronizedMethodLock();
    static SynchronizedMethodLock instance2 = 
            new SynchronizedMethodLock();
    
    @Override
    public void run() {
        sync();
    }
    
    // 创建两个实例对象,如果不是static方法的话,则并行操作,否则串行执行。
    public void sync(){
        // 如果是this的话则并行执行,Class对象则串行执行
        synchronized(SynchronizedMethodLock.class){
            System.out.println("我是类锁的第二种形式:synchronized(*.class)形式。我叫:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "运行结束");
            }
    }
    
    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        
        t1.start();
        t2.start();
        
        while(t1.isAlive() || t2.isAlive()){
            
        }
        System.out.println("finished");
    }
    
    

}
三、线程不安全导致请求丢失问题解决
场景:前面【一、Synchronized的作用 】中的demo计数场景。
如下四种方式解决(结果均为20000):
3.1
@Override
    public synchronized void run() {
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }
3.2
@Override
    public void run() {
        synchronized (this){
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    }
3.3
@Override
    public void run() {
        synchronized(DisappearRequest.class){
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        }
    }
3.4
@Override
    public void run() {
        add();
    }
    
    public static synchronized void add(){
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }
四、多线程访问同步方法的7中情况

4.1、 两个线程同时访问一个对象的同步方法

两个线程争抢的是同一把锁,线程间相互等待,只能有一个线程持有该锁
public static void main(String[] args) {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        while(t1.isAlive() || t2.isAlive()){
        }
        System.out.println("finished");
    }

4.2、 两个线程访问的是两个对象的同步方法

创建两个实例对象,如果不是static方法的话,则并行操作,否则串行执行,这两个实例真正采用的锁对象不是同一个,所以不会被干扰。
// 创建两个实例对象,如果不是static方法的话,则并行操作,否则串行执行。
    public void sync(){
        // 如果是this的话则并行执行,指向的是不同的实例对象,若为Class对象则串行执行
        synchronized(this){
        // TO DO...
        }
    }
public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
        while(t1.isAlive() || t2.isAlive()){    
        }
        System.out.println("finished");
    }

4.3、 两个线程访问的synchronized的静态方法

如果两个线程访问的是同一个对象的同步方法则串行执行,如果访问的是不同对象的同步方法,若该方法是非静态static方法则并行执行,否则两个线程访问的锁对象为同一把锁,串行执行。
    public static synchronized void sync(){
    // TO DO...
    }
    
    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        
        t1.start();
        t2.start();
        
        while(t1.isAlive() || t2.isAlive()){
            
        }
        System.out.println("finished");
    }

4.4 同时访问同步方法合肥同步方法

非同步方法不受到影响

4.5 访问同一个对象的不同的普通同步方法

同一个对象,两个同步方法拿到的this是一样的,同一把锁,所以串行执行

4.6 同时访问静态synchronized和非静态synchronized方法

两个线程指定的锁对象不是同一把锁,所以锁之间不冲突,并行执行
// 创建两个实例对象,如果不是static方法的话,则并行操作,否则串行执行。
    public static synchronized void sync(){
        // 如果是this的话则并行执行,Class对象则串行执行
        System.out.println("我是静态方法:synchronized(*.class)形式。我叫:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
    
    // 创建两个实例对象,如果不是static方法的话,则并行操作,否则串行执行。
    public synchronized void sync1(){
        // 如果是this的话则并行执行,Class对象则串行执行
        System.out.println("我是非静态方法:synchronized(*.class)形式。我叫:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
    
    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance1);
        
        t1.start();
        t2.start();
        
        while(t1.isAlive() || t2.isAlive()){
            
        }
        System.out.println("finished");
    }

4.7 方法抛异常后,是否会释放锁

抛出异常之后jvm会释放锁,后面的线程会进入同步方法。
// 创建两个实例对象
    static SynchronizedMethodLock instance1 = 
            new SynchronizedMethodLock();
    static SynchronizedMethodLock instance2 = 
            new SynchronizedMethodLock();
    
    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("Thread-0")){
            sync();
        }else{
            sync1();
        }
    }
    
    // 创建两个实例对象,如果不是static方法的话,则并行操作,否则串行执行。
    public synchronized void sync(){
        // 如果是this的话则并行执行,Class对象则串行执行
        System.out.println("我是方法1:我叫:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        throw new RuntimeException();
//        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
    
    // 创建两个实例对象,如果不是static方法的话,则并行操作,否则串行执行。
    public synchronized void sync1(){
        // 如果是this的话则并行执行,Class对象则串行执行
        System.out.println("我是方法2:我叫:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "运行结束");
    }
    
    public static void main(String[] args) {
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance1);
        
        t1.start();
        t2.start();
        
        while(t1.isAlive() || t2.isAlive()){
            
        }
        System.out.println("finished");
    }

五、synchronized的性质

【5.1 可重入】:指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁
好处:避免死锁,提升封装性
比如:现在有两个均被synchronized修饰的方法f1和f2,此时线程A执行到了f1,并且获得了这把锁,由于方法二f2也是synchronized修饰(也就是说要执行f2必须拿到f2的锁),如果synchronized不具备可重入性,此时线程A只是拿到了f1的锁,没有拿到f2的锁,所以线程A即想拿到f2的锁又不释放f1锁,那么就会造成永远等待,即死锁,所以synchronized是具有可重入性。

【可重入粒度如下】

5.1.1 证明同一个方法是可重入的(递归)

5.1.2 证明可重入不要求是同一个方法

public synchronized void method1(){
        System.out.println("我是method1");
        method2();
    }
    public synchronized void method2(){
        System.out.println("我是method2");
    }
5.1.3 证明同可重入不要求是同一个类中的
public class SyncSuperClass{
        public synchronized void doSomething(){
            System.out.println("我是父类方法");
        }
    }
    
    class TestClass extends SyncSuperClass{
        public synchronized void doSomething(){
            System.out.println("我是子类方法");
            super.doSomething();
        }
    }

【5.2 不可中断性】

一旦这个锁已经被别人获得了,如果我还想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁。如果别人永远不释放锁,那么我只能永远等下去。
Lock类:相比之下,Lock类,可以拥有中断的能力,第一点,如果我觉得我等的时间太长了,有权中断现在已经获取到锁的线程的执行;第二点,如果我觉得等待时间太长了不想等了,可以退出。
Lock lock = new ReentrantLock(); 
    // 下面这两种形式的锁是等价的
    public synchronized void method1(){
        System.out.println("我是Synchronized形式的锁");
    }
    
    public void method2(){
        lock.lock();
        try{
            System.out.println("我是Lock形式的锁");
        }finally{
            lock.unlock();
        }
    }
六、synchronized的缺陷

【6.1 效率低】

锁的释放情况少,试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程。
6.1.1、 当一个线程获得了对应的sync锁的时候,其他线程只能等待我释放之后才能获取该锁。
6.1.2、 只有两种情况才释放锁:1.执行完了这段代码,2.发生异常自动释放锁
6.1.3、 不能中断,但是Lock是有中断能力的

【6.2 不够灵活(如:读写锁比较灵活:读的时候不加锁,写才加锁)】

加锁和释放锁的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的

【6.3 无法知道是否成功获取到锁】

七、Lock锁常用方法
    Lock lock = new ReentrantLock(); // 非公平锁
    // Lock lock = new ReentrantLock(true); // 公平锁
    // Lock lock = new ReentrantReadWriteLock();
        
        lock.lock();
        lock.unlock();
        lock.tryLock(); // 获取锁
        lock.tryLock(10, TimeUnit.SECONDS);
八、常见面试题
8.1 使用synchroinzed注意点:锁对象不能为空、作用域不宜过大、避免死锁
注:一个对象作为锁对象,这个对象必须是被new过的,或者是被其他方法创建好的,而不是一个空对象,因为锁的信息保存在对象头中的。

8.2 如何选择Lock和synchronized关键字
尽量使用concurrent包下的CountDownLatch或者atomic包,或者信号量

九、思考
9.1 多个线程等待同一个synchronized锁的时候,JVM如何选择下一个获取锁的是哪个县城?
9.2 synchronized使得同时只能有一个线程可以执行,性能较差,有什么办法可以提升性能?
9.3 我想更灵活的控制锁的获取和释放(现在释放锁的时机都被规定死了),怎么办?
9.4 什么是锁的升级、降级?什么事JVM里的偏斜所、轻量级锁。重量级锁?

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77565.html

相关文章

  • synchronized键字使用详解

    摘要:基本使用同步代码块同步代码块延时秒,方便后面测试作用代码块时,方法中的,是指调用该方法的对象。那么这个时候使用关键字就需要注意了推荐使用同步代码块,同步的代码块中传入外部定义的一个变量。 简述 计算机单线程在执行任务时,是严格按照程序的代码逻辑,按照顺序执行的。因此单位时间内能执行的任务数量有限。为了能在相同的时间内能执行更多的任务,就必须采用多线程的方式来执行(注意:多线程模式无法减...

    Jeffrrey 评论0 收藏0
  • Java中的synchronized键字

    摘要:的关键字中的块使用关键字进行标记。由于每个类只有一个类对象存在于中,因此全局同时只有一个线程能够进入到同一个类的静态同步方法中。同步代码块使这种期望成为可能。注意同步代码块如何在括号中接受一个对象。相同的实例被传入两个不同的线程实例中。 Java的synchronized块标记一个方法或一个代码块为同步的。synchronized块能用于防止出现竞态条件。 Java的synchroni...

    lylwyy2016 评论0 收藏0
  • 值得保存的 synchronized 键字总结

    摘要:无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。另外在中引入了自适应的自旋锁。和关键字的总结推荐一 该文已加入开源文档:JavaGuide(一份涵盖大部分Java程序员所需要掌握的核心知识)。地址:https://github.com/Snailclimb... 本文是对 synchronized 关键字使用、底层原理、JD...

    miguel.jiang 评论0 收藏0
  • Java多线程学习(二)synchronized键字(2)

    摘要:关键字加到非静态方法上持有的是对象锁。线程和线程持有的锁不一样,所以和运行同步,但是和运行不同步。所以尽量不要使用而使用参考多线程编程核心技术并发编程的艺术如果你觉得博主的文章不错,欢迎转发点赞。 系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) J...

    Batkid 评论0 收藏0
  • Java多线程学习(二)synchronized键字(1)

    摘要:转载请备注地址多线程学习二将分为两篇文章介绍同步方法另一篇介绍同步语句块。如果两个线程同时操作对象中的实例变量,则会出现非线程安全,解决办法就是在方法前加上关键字即可。 转载请备注地址: https://blog.csdn.net/qq_3433... Java多线程学习(二)将分为两篇文章介绍synchronized同步方法另一篇介绍synchronized同步语句块。系列文章传送门...

    xuxueli 评论0 收藏0
  • Synchronized 键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTran

    摘要:使用可以禁止的指令重排,保证在多线程环境下也能正常运行。关键字底层原理总结关键字底层原理属于层面。另外在中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者 【强烈推荐!非广告!】阿里云双11褥羊毛活动:https://m.aliyun.com/act/team1111/#/share?params=N.FF7yxCc...

    Vixb 评论0 收藏0

发表评论

0条评论

hqman

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<