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认为这都是一个对象,所以同步生效。

 

以上。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值