Java并发编程-03 Synchronized

本文详细介绍了Java中的同步机制,包括Synchronized的使用方式,如对象锁和类锁,并通过实例展示了其在代码块和方法上的应用。同时,探讨了Synchronized的加锁和释放原理,以及可重入和可见性保证。此外,文章还讨论了Synchronized的局限性和Java Lock如何弥补这些不足,比如Lock提供了更灵活的锁操作和超时、中断功能。最后,提到了锁的优化策略和JVM中锁的升级与降级机制,如偏向锁、轻量级锁和重量级锁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Synchronized可以作用在哪里? 分别通过对象锁和类锁进行举例。

  • 对象锁
  1. 作用在代码块(这里可以手动指定对象,可以使this,也开始是自定义的锁)
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence = new SynchronizedObjectLock();
	
	//自定义的锁
	Object block1 = new Object();
    Object block2 = new Object();

    @Override
    public void run() {
        // 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
        // this 可以替换成 自定义的锁
        synchronized (this) {
            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(instence);
        Thread t2 = new Thread(instence);
        t1.start();
        t2.start();
    }
}
  1. 方法锁形式 synchronized修饰普通方法锁对象默认是this
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence = new SynchronizedObjectLock();

    @Override
    public void run() {
        method();
    }

    public synchronized void method() {
        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(instence);
        Thread t2 = new Thread(instence);
        t1.start();
        t2.start();
    }
}
  • 类锁
  1. synchornized 修饰静态方法
著作权归https://pdai.tech所有。
链接:https://www.pdai.tech/md/java/thread/java-thread-x-key-synchronized.html

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        method();
    }

    // synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把
    public static synchronized void method() {
        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(instence1);
        Thread t2 = new Thread(instence2);
        t1.start();
        t2.start();
    }
}
  1. synchornized修饰所对象是class对象
public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        // 所有线程需要的锁都是同一把
        synchronized(SynchronizedObjectLock.class){
            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(instence1);
        Thread t2 = new Thread(instence2);
        t1.start();
        t2.start();
    }
}

Synchronized本质上是通过什么保证线程安全的? 分三个方面回答:加锁和释放锁的原理,可重入原理,保证可见性原理。

synchornized加锁原理分析

加锁释放锁的原理
MonitorenterMonitorexit指令,会让对象在执行,使其锁计数器加1或者减1

在一个对象被执行时,在同一时间只会与一个monitor(锁)相关联,而一个monitor在同一时间只会被一个线程获得,所以在一个对象尝试获得与这个对象
相关联的Monitor锁的所有权时 ,会有三种情况
1.  monitor计数器为0 说明该对象还没有被获取,然后当前线程就会立即获得,然后monitor计数器+1,一旦+1 其他线程想要获得就必须得等待
2. 如果这个monitor已经拿到了该锁的所有权,又重入了这把锁,那么计数器就会反复+1
3. 这把锁已经被别的线程获取了,等待锁释放

monitorexit指令:释放对于monitor的所有权,释放过程很简单,就是讲monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁。

可重入的原理:加锁次数计数器
通俗来讲就是在同一个锁程中不需要重新获取锁

保证可见性的原理

内存模型,和haapens-before原则

Synchronized由什么样的缺陷?Java Lock是怎么弥补这些缺陷的。

  1. 效率低 : 锁的释放情况太少了,只有代码执行完毕,或者异常结束后才会释放锁,试图获取锁的进程不能设置超时,不能中断一个正在使用锁的线程,lock可以中断你和设置超时
  2. 不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件,相对,读写锁更加灵活
  3. 无法知道是否成功获取锁:Lock可以拿到状态

lock(): 加锁
unlock(): 解锁
tryLock(): 尝试获取锁,返回一个boolean值
tryLock(long,TimeUtil): 尝试获取锁,可以设置超时

Synchronized和Lock的对比,和选择? Synchronized在使用时有何注意事项?

  1. 作用域不要太大
  2. 避免死锁
  3. 所对象不能为空,因为锁的信息都保存在信息头里面
  4. 在能选择的情况下,尽量不要用lock,也不要用synchronized关键字,尽量使用java.util.concurrent包中的各种各样的类

Synchronized修饰的方法在抛出异常时,会释放锁吗?

public class TestMain implements Runnable {
    //格式化
    static SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Override
    public void run() {
        //让线程Thread-0执行同步方法1
        if ("Thread-0".equals(Thread.currentThread().getName())) {
            synchronizedMethod();
        } else {
            //让线程Thread-1执行同步方法2
            synchronizedMethod2();
        }
    }


    public synchronized void synchronizedMethod() {
        Date satrtTime = new Date();
        String time = sim.format(satrtTime);
        System.out.println(time + ":【" + Thread.currentThread().getName() + "访问了同步方法1】");
        try {
            //睡眠3秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Date endTime = new Date();
        String time2 = sim.format(endTime);
        System.out.println(time2 + ":【" + Thread.currentThread().getName() + "准备抛出异常了】");
        throw new RuntimeException();
    }

    public synchronized void synchronizedMethod2() {
        Date satrtTime = new Date();
        String time = sim.format(satrtTime);
        System.out.println(time + ":【" + Thread.currentThread().getName() + "访问了同步方法2】");
        try {
            //睡眠3秒
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Date endTime = new Date();
        String time2 = sim.format(endTime);
        System.out.println(time2 + ":【" + Thread.currentThread().getName() + "准备退出这个同步方法了2】");
    }

    public static void main(String[] args) throws Exception {
        //创建一个对象testMain1
        TestMain testMain1 = new TestMain();
        System.out.println("运行开始");
        Thread thread1 = new Thread(testMain1);
        Thread thread2 = new Thread(testMain1);
        thread1.start();
        thread2.start();
        //让主线程做个等待,等线程一和线程二都执行完它才继续执行
        thread1.join();
        thread2.join();
        System.out.println("运行结束");
    }
}

在这里插入图片描述
所以可以看出在异常抛出后会释放锁

多个线程等待同一个snchronized锁的时候,JVM如何选择下一个获取锁的线程?

Synchronized使得同时只有一个线程可以执行,性能比较差,有什么提升的方法?

  1. 消除锁(判断该对象是否有多个线程调用,若无则消除该对象上的锁)
  2. 使用轻量级锁
  3. 锁粗化(在一个代码块代码总是释放锁又加锁的情况下,又是同一线程这时就会锁粗化把这一整块代码块加锁)

我想更加灵活地控制锁的释放和获取(现在释放锁和获取锁的时机都被规定死了),怎么办?

使用Condition与Lock的结合:ReentrantLock 进行锁操作
lock(): 加锁
unlock(): 解锁
tryLock(): 尝试获取锁,返回一个boolean值
tryLock(long,TimeUtil): 尝试获取锁,可以设置超时

什么是锁的升级和降级? 什么是JVM里的偏斜锁、轻量级锁、重量级锁? 不同的JDK中对Synchronized有何优化?

  1. 锁的升级与降级:,为了减少获得锁和释放锁带来的性能开销,引入了偏向锁、轻量级锁的概念。锁的状态根据竞争激烈的程度从低到高不断升级或者降级。(偏向锁-》轻量级锁-》重量级锁)
  2. 偏向锁:当一个线程访问加了同步锁的代码块时,会在对象头中存储当前线程的id,后续这个线程进入和退出这个代码块时,不需要在次加锁,和释放锁,而是直接比较对象头里面是否存储了指向当前线程的偏向锁,相等就表示偏向锁偏向当前线程,不需要尝试获得锁
  3. 轻量级锁:轻量级意味着相比于传统的锁,它使用的是系统互斥量来实现的,目的是在 没有多线程竞争的情况下,减少系统互斥量操作产生的性能消耗(轻量级锁,先进行cas操作,然后若是没获得锁就进行自旋的操作,在短暂时间里再次去获取锁并且大几率能够获得锁,这就是轻量级锁
  4. 重量级锁:两个线程之间的竞争不能得到很好的交替(自旋之后获得不到锁),会上升为重量级锁,
  5. JDk1.6之后引入了偏向锁,和轻量级锁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值