String常量池和synchronized关键字

在java中String是一个比较特殊的类,String有一个自己的常量池,存放了初始化的字符串,这一个特性经常能导致一些坑,下面简单的介绍一点。

 

一,用常量赋值的字符串

String a=”abc”;

当执行这条语句时,先在常量池中查找是否有”abc”对象,如果有则创建对象a并指向这个对象,如果没有则先在常量池中创建”abc”对象,然后创建对象a并指向这个对象。

最终a是指向常量池的。

所以:

String a="abc";

String b="abc";

System.out.println(a==b);

输出的是true

 

二,new的字符串

String a=new String("abc");

先在常量池中查找是否有”abc”对象,如果有则把常量池中的对象复制一份,然后创建对象a并指向复制出来的这个对象,如果没有则先在常量池中创建”abc”对象,然后复制一份,然后创建对象a并指向复制出来的这个对象。

最终a不是指向常量池的。而且在第一次使用该字符串时,内存里存了两个这个字符串的对象,一个在堆里一个在常量池里。

所以:

String a=new String("abc");

String b="abc";

System.out.println(a==b);

输出的是false

再一个例子:

String a=new String("abc");

String b= new String("abc");

System.out.println(a==b);

输出的也是false

 

三,String的intern()方法

在这里要特别提到String的intern()方法,根据jdk对这个方法的描述,翻译一下大概是这样的:

当这个方法被调用时,如果常量池中有同值的字符串,则返回常量池中的字符串。如果常量池中没有这个字符串,则在常量池中创建这个字符串并返回。所谓同值,指的是equals()方法返回值为true。

所以,以下代码输出的是true

String a="abc";

String b=new String("abc").intern();

System.out.println(a==b);

 

四,System的identityHashCode()方法

另外提一下System.identityHashCode()方法,这个方法可以返回对象所属类的原有的HashCode,这个方法适合应用于hashCode()方法被重写的类。

比如本文中提到的String类的hashCode方法就被重写过,相同字符串的hashCode往往是一样的,而这个方法返回重写前的那个hashCode()方法的返回值,比如:

String a="abc";

String b="abc";

String c=new String("abc");

String d=new String("abc");

System.out.println(System.identityHashCode(a));

System.out.println(System.identityHashCode(b));

System.out.println(System.identityHashCode(c));

System.out.println(System.identityHashCode(d));

输出的内容是:

1502913001

1502913001

756151793

1982445652

可以看到,a和b确实是指向一个对象,而c和d都是堆中单独的对象。

 

五,synchronized关键字和String

java的synchronized关键字在生效时就和内存地址有关,特别是当我们使用类似

synchronized(某个变量){}

这种格式时,参数对象的内存地址只要相同,synchronized关键字的同步功能就会生效,所以我们可以写以下例子:

package test;

public class Test {

   public static void main(String[] args) {

      for (int i = 0; i < 10; i++) {    //新建10个线程一起抢锁

         String key = new String("abc");

         TestThread t = new TestThread(key);

         t.start();

      }

   }

   /**

    * 使用构造时传入的参数String作为锁,观察线程获得锁的情况

    */

   public static class TestThread extends Thread {

      public String key;

      public TestThread(String key) {

         this.key = key;

      }

      @Override

      public void run() {

         synchronized (key) {

            System.out.println(Thread.currentThread().getName() + " begin " + key);

            try {

                Thread.sleep(3000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }


            System.out.println(Thread.currentThread().getName() + " end " + key);

         }

      }

   }

}

这个例子中的同步语句是失效的,每个线程都拿到了锁并且并行执行了,因为每次定义一个线程时使用的String对象是用new的方式获得的,在内存中是不同的地址。

然后改一下key的定义方式,改成以下的例子:

package test;

public class Test {

   public static void main(String[] args) {

      for (int i = 0; i < 10; i++) {    //新建10个线程一起抢锁

         String key = "abc";

         TestThread t = new TestThread(key);

         t.start();

      }

   }

   /**

    * 使用构造时传入的参数String作为锁,观察线程获得锁的情况

    */

   public static class TestThread extends Thread {

      public String key;

      public TestThread(String key) {

         this.key = key;

      }

      @Override

      public void run() {

         synchronized (key) {

            System.out.println(Thread.currentThread().getName() + " begin " + key);

            try {

                Thread.sleep(3000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }


            System.out.println(Thread.currentThread().getName() + " end " + key);

         }

      }

   }

}

这个例子中的同步语句是生效的,每个线程争抢同一把锁,虽然key是在每次循环中定义的,但是其实使用的都是常量池中的对象,synchronized认为这都是一个对象,所以同步生效。

 

以上。

<think>好的,我现在需要解释Java中`synchronized`关键字的使用。首先,我得回忆一下`synchronized`的基本概念常见用法。用户可能想知道它的作用、不同的使用方式(修饰方法、代码块)、底层实现原理,以及需要注意的问题。首先,`synchronized`是Java中用于实现线程同步的关键字,确保同一时间只有一个线程可以执行被保护的代码,避免竞态条件。接下来,我需要分几个部分来讲解:1.**基本作用**:保证原子性、可见性有序性。原子性指操作不可中断;可见性确保线程修改的变量对其他线程可见;有序性防止指令重排(但`synchronized`主要通过锁的获取释放来保证,不如volatile严格)。2.**使用方式**:-**修饰实例方法**:锁的是当前实例对象(`this`),同一实例的方法调用会互斥。-**修饰静态方法**:锁的是类的`Class`对象(如`MyClass.class`),所有实例共享同一把锁。-**修饰代码块**:指定锁对象(可以是任意对象),更细粒度控制。3.**底层实现**:JVM通过`monitorenter``monitorexit`指令实现,依赖对象头中的MarkWord存储锁信息。JDK6后引入了锁优化(偏向锁、轻量级锁、重量级锁),提升性能。4.**注意事项**:-锁的粒度:过粗影响性能,过细可能导致死锁。-锁对象的选择:避免使用`String`常量、`Integer`等不可变对象作为锁,可能导致不同代码块共享同一锁。-异常会自动释放锁:发生异常时,JVM会自动释放持有的锁,避免死锁。然后,需要举一些示例代码,比如实例方法同步、静态方法同步、代码块同步的例子,说明不同场景下的使用。还要提到常见的误区,比如多个实例调用实例方法不会互斥,因为锁的是各自的实例对象;静态方法同步锁的是类对象,所有实例都会互斥。可能用户还会关心`synchronized``Lock`的区别,但用户的问题是关于`synchronized`的使用,所以可以简要对比,但重点在`synchronized`本身。最后,总结使用`synchronized`的最佳实践,比如优先使用代码块控制细粒度锁,避免锁对象被其他代码误用等。现在需要组织这些内容,确保逻辑清晰,覆盖主要使用方式关键点,同时用示例帮助理解。</think> `synchronized`是Java中用于实现线程同步的核心关键字,通过控制对共享资源的互斥访问,确保多线程环境下的线程安全。以下从**作用与特性**、**使用方式**、**底层实现**及**注意事项**四方面详细说明: ### 一、`synchronized`的核心作用与特性 `synchronized`的核心是通过“锁”机制实现以下三个线程安全的关键特性: | 特性 | 说明 | |------------|----------------------------------------------------------------------| | **原子性** | 保证被`synchronized`修饰的代码块或方法是一个不可分割的操作单元,同一时间仅一个线程执行。 | | **可见性** | 线程获取锁时,会从主内存重新加载变量;释放锁时,会将修改的变量刷回主内存,确保多线程间的变量可见性。 | | **有序性** | 通过“锁的获取-释放”的happens-before规则,禁止指令重排对同步代码块的影响(但对非同步代码无约束)。 | ### 二、`synchronized`的三种使用方式 `synchronized`可修饰方法或代码块,锁的范围由修饰目标决定: #### 1. 修饰实例方法(对象锁) **作用**:锁的是当前实例对象(`this`),同一实例的所有`synchronized`实例方法共享同一把锁。 **适用场景**:多个线程访问同一实例的同步方法,需互斥执行。 **示例**: ```java public class Counter { private int count = 0; // 修饰实例方法:锁是当前Counter实例(this) public synchronized void increment() { count++; // 多线程调用时,同一实例的increment()互斥执行 } } ``` **关键点**: - 不同实例调用`synchronized`实例方法时,锁是各自的实例对象,不会互斥; - 若实例方法调用其他非同步方法,可能导致线程安全问题(需确保所有共享操作都被同步)。 #### 2. 修饰静态方法(类锁) **作用**:锁的是类的`Class`对象(如`Counter.class`),所有实例共享同一把锁。 **适用场景**:多个线程访问静态共享资源(如静态变量),需全局互斥。 **示例**: ```java public class Counter { private static int staticCount = 0; // 修饰静态方法:锁是Counter.class(类对象) public static synchronized void staticIncrement() { staticCount++; // 所有实例调用此方法时,共享同一把锁 } } ``` **关键点**: - 静态方法的锁与实例无关,即使通过不同实例调用,仍会互斥; - 静态方法的同步与实例方法的同步(对象锁)互不影响(锁不同)。 #### 3. 修饰代码块(对象锁/类锁) **作用**:显式指定锁对象(可以是任意对象),实现更细粒度的同步控制。 **适用场景**:仅需同步部分代码(而非整个方法),或需自定义锁对象。 **示例**: ```java public class Account { private double balance; private final Object lock = new Object(); // 自定义锁对象(避免使用this或字符串常量) public void transfer(Account target, double amount) { // 同步代码块:锁是自定义的lock对象 synchronized (lock) { if (balance >= amount) { balance -= amount; target.balance += amount; } } } } ``` **关键点**: - 锁对象需是**不可变的、唯一的**(避免使用`String`常量、`Integer`等可能被缓存的对象); - 若指定`Class`对象(如`synchronized (Account.class)`),效果等同于静态方法同步(类锁)。 ### 三、底层实现与锁优化(JDK6+) JVM通过`monitorenter``monitorexit`指令实现`synchronized`的锁机制: - **`monitorenter`**:尝试获取对象的监视器(Monitor),成功则持有锁; - **`monitorexit`**:释放监视器,允许其他线程获取锁(异常时JVM自动触发)。 JDK6后引入**锁优化**,减少锁竞争的开销,优化路径如下(锁状态随竞争升级,不可逆): | 锁状态 | 说明 | |--------------|----------------------------------------------------------------------| | **偏向锁** | 假设锁仅被一个线程反复获取,通过Mark Word记录线程ID,避免CAS操作。若其他线程竞争,升级为轻量级锁。 | | **轻量级锁** | 通过CAS尝试获取锁(无竞争时效率高)。若竞争失败(多线程交替获取),升级为重量级锁。 | | **重量级锁** | 依赖操作系统互斥量(Mutex),线程阻塞/唤醒需切换内核态,开销大(传统`synchronized`实现)。 | ### 四、使用注意事项与常见误区 #### 1. 锁的粒度需合理 - 过粗:同步范围过大(如整个方法),降低并发性能; - 过细:同步范围过小(如仅同步一行代码),可能导致锁竞争频繁,甚至死锁(如嵌套锁)。 #### 2. 避免使用不稳定的锁对象 - 禁止使用`String`常量(如`synchronized ("lock")`),因字符串常量池可能导致不同代码块共享同一锁; - 避免使用`this`作为锁对象(若对象被外部持有,可能被其他代码加锁,导致意外阻塞)。 #### 3. 异常自动释放锁 线程在同步代码块中抛出异常时,JVM会自动释放持有的锁,避免死锁(无需手动释放)。 #### 4. 与`Lock`的对比 | 特性 | `synchronized` | `Lock`接口(如`ReentrantLock`) | |--------------------|-----------------------------------------|---------------------------------------| | 锁获取方式 | JVM自动管理(隐式) | 手动调用`lock()`/`unlock()`(显式) | | 可中断性 | 不支持(线程阻塞后无法中断) | 支持(`lockInterruptibly()`) | | 超时获取锁 | 不支持 | 支持(`tryLock(long timeout, TimeUnit unit)`) | | 公平性 | 非公平(默认) | 可配置公平/非公平策略 | ### 总结 `synchronized`是Java中实现线程同步的基础工具,通过对象锁或类锁控制共享资源的互斥访问。JDK6后的锁优化使其性能大幅提升,适用于大多数线程安全场景。使用时需注意锁对象的选择、粒度控制及与其他锁机制的对比,以达到最优效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值