java中的锁——synchronized

2 篇文章 0 订阅
2 篇文章 0 订阅

synchronized关键字

synchronized是我们在java中最简单最常用的加锁方式。1.6后synchronized的优化,使其性能提升,因此很多同步加锁的场景,使用synchronized也是很好的选择。

synchronized的使用

  • synchronized可以修饰静态方法
public synchronized static void method()
  • 非静态方法
public synchronized void method()
  • 代码块
synchronized(obj){}
	不管是哪一种修饰,synchronized目的是给对象加锁,在多线程运行时,在调用同步代码时,只有拿到对象锁才能执行同步代码。
	当synchronized修饰的非静态方法是对this(该类的对象)上锁。
	修饰静态方法则是对当前类对象上锁,注意这里的类对象就是Class对象,不是栈中的类信息。
	synchronized(obj)显而易见,就是对括号内的obj上锁。

由于之前看过一些博客对于类加锁的情况描述的不够详细,我们通过一个示例来帮助理解类锁。

public class LockTest {
  public static void main(String[] args) throws InterruptedException {
      //占用类锁的线程
      Thread t1=new Thread(new Runnable() {
          @Override
          public void run() {
              Test.test1();
          }
      });
      //用类 调用静态上锁方法
      Thread t2=new Thread(new Runnable() {
          @Override
          public void run() {
              Test.test2();
          }
      });
      //无锁方法
      Thread t3=new Thread(new Runnable() {
          @Override
          public void run() {
              Test.test3();
          }
      });
      //调用非静态上锁方法
      Thread t4=new Thread(new Runnable() {
          @Override
          public void run() {
              new Test().test4();
          }
      });

      //使用对象调用静态上锁方法
      Thread t6=new Thread(new Runnable() {
          @Override
          public void run() {
              new Test().test6();
          }
      });
      t1.start();
      //t1先执行 先占用类锁
      Thread.sleep(100);


      t2.start();
      t3.start();
      t4.start();
      t6.start();
  }
  static class Test{
      public static synchronized void test1(){
          System.out.println("test1:类加锁");
          try {
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
      public static synchronized void test2(){
          System.out.println("test2:静态同步方法 拿锁");
      }
      public static void test3(){
          System.out.println("test3:非同步方法");
      }
      public synchronized void test4(){
          System.out.println("test4:非静态同步方法 拿锁");
      }
      public void test6(){
          synchronized(this.getClass()){
              System.out.println("test6");
          }
      }
  }
}

代码描述
示例中Test类中 test1 test2 是 synchronized修饰的静态方法;test3是个普通非同步方法;test4是synchronized修饰的非静态方法;test6是对this.getClass(即对Test类CLass对象)加锁的方法。
首先t1线程调用test1方法,由于t1中有Thread.sleep(),因此t1会持续占有类锁,其他线程依次启动查看锁的竞争情况。
运行结果

  • test3(非同步方法)test4(非静态同步方法) 会立即执行,说明这两个方法并不会去竞争类锁。
  • test2(静态同步方法)和test6(对Test类CLass对象加锁方法)会等到test1释放锁后才会执行,说明这两个方法会和test1竞争类锁。

结果说明

  • test2(静态同步方法)与test1竞争锁比较好理解,因为两者都是同步静态方法,从test6中我们可以看出同步静态方法实际上就是对类Class对象进行了加锁。
  • test4(非静态同步方法)没有去竞争锁是因为两个方法竞争的锁对象是不同的,test4是对对象加锁。
  • test3(非同步方法)没有竞争锁,是因为执行这个方法是不需要锁的。所以从这里我们可以看到在java中对象加锁只针对与需要竞争锁的代码块或方法,并不会影响普通非同步方法。

synchronized的加锁解锁

由于synchronized关键字是自动上锁,代码执行完自动解锁,因此我们不用做什么处理。并且当方法或代码块中出现异常也会自动释放锁。
synchronized也支持重入。
如:

synchronized(obj){
              synchronized(obj){
  			}
}

当一个synchronized同步方法调用另一个synchronized同步方法两个方法竞争的同一把锁,在执行第二个方法时不会重新释放锁再加锁,而是直接保持锁,并对锁中的计数器加1。第二个方法执行完计数器减一,等到计数器为0时才会将锁释放。因此再锁重入的期间,其他线程是拿不到锁的。

浅说synchronized锁膨胀

synchronized锁级别是由低到高:偏向锁,轻量级锁,重量级锁
jdk1.6前synchronized的性能是被人诟病的,就是因为当时的synchronized只单纯的使用重量级锁。
偏向锁
当synchronized修饰的方法只有一个线程调用时,对象锁状态为偏向锁。在锁第一次被拥有的时候,对象头记录下偏向线程ID。之后每当方法被调用都会去对比线程id是否发生变化,如果其他线程调用,与其记录的线程id不统一会膨胀为轻量级锁。偏向锁更像是无锁状态。
轻量级锁
多个线程交替调用同步代码时,或存在很短时间的竞争关系,轻量级锁采用cas乐观锁非阻塞+自旋的方式,尝试拿锁,如果拿不到会膨胀为重量级锁。
**轻量锁优势:**在于使线程保持活跃,不会切换线程上下文。
轻量锁缺点 不会让出cpu,如果长时间竞争锁cpu压力会比较大

重量级锁
重量级锁也就是我们普遍认知的锁。拿不到锁的线程会让出cpu阻塞。
重量锁优势: 与轻量锁相对的 ,由于重量锁采用阻塞的方式等待锁释放,会让出cpu。
重量锁缺点: 由于线程阻塞需要唤醒线程线程上下文切换的资源耗费。

总结
轻量级锁不能替代重量级锁,两者都有存在的必要,当需要同步的操作很少,线程上下文切换耗时占用总耗时比较大时,使线程一直保持活跃的状态会省掉更多的资源(所以 automic 适用cas+自旋的方式)。但如果线程上下文切换远小于同步代码执行时间,将未拿到锁的线程阻塞让出cpu是更好地选择。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Javasynchronized关键字用于实现线程同步,确保多个线程对共享资源的并发访问是安全的。它具有三个主要的作用:互斥访问、可见性和禁止指令重排。互斥访问指的是在同一时间只有一个线程可以执行被synchronized关键字包裹的代码块,从而保证了原子性。可见性指的是当一个线程修改了共享变量的值时,其他线程能够立即得知这个修改。禁止指令重排则是为了保证代码的执行顺序不会被优化或重排,从而确保程序的正确性。\[1\]\[2\] 在synchronized关键字的内部实现,它使用了的机制来实现线程的互斥访问。当一个线程进入synchronized代码块时,它会尝试获取,如果已经被其他线程持有,则该线程会被阻塞,直到被释放。当线程执行完synchronized代码块后,会释放,让其他线程有机会获取并执行相应的代码。这样就实现了线程的互斥访问。\[1\]\[2\] 总结来说,synchronized关键字是Java实现线程同步的一种基本手段,通过互斥访问、可见性和禁止指令重排来保证多线程对共享资源的安全访问。\[1\]\[2\] #### 引用[.reference_title] - *1* *3* [多线程——synchronized详解](https://blog.csdn.net/Pluto372/article/details/127304464)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] - *2* [synchronized实现线程同步的用法和实现原理](https://blog.csdn.net/u010013573/article/details/87219343)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值