No.1.测试Synchronized一个String字符串后的多线程同步状况

测试目的描述
Synchronized关键字锁定String字符串可能会带来严重的后果, 尽量不要使用 synchronized(String a) 因为JVM中,因为字符串常量池具有缓冲功能!
接下来, 测试使用""的形式引用字符串和使用new的String()来声明一个字符串, 再使用Synchronized关键字同步该字符串,看两个线程共同调用含有这个Synchronized锁定的字符串的代码块会发生什么同步问题.

测试1: 默认使用""的形式引用字符串 来作为同步条件两个线程

测试1工程结构如下

image.png

测试1: MyPrinter.java

package com.szs.pojo;
/**
 * 定义一个打印数字线程类,作为ThreadTest的属性类
 * @author Administrator
 *
 */
public class MyPrinter{
    public void print(String paramString){
        try {
            synchronized (paramString) {
                while(true){
                    System.out.println("字符串内容:"+paramString
                            +"; 字符串hashCode值:"+paramString.hashCode()
                            +"; 当前线程名字:"+ Thread.currentThread().getName()
                            +"\t实验1使用 \"66666\" 来同步");
                    Thread.sleep(1000);
                }
            }
            
        } catch (Exception e) {
            e.printStackTrace();
            // TODO: handle exception
        }
    }
    
}

测试1 ThreadTest #线程的实现类

package com.szs.pojo;

public class ThreadTest extends Thread {
    private MyPrinter myPrinter;
     
    public ThreadTest(MyPrinter myPrint) {
        this.myPrinter = myPrinter;
    }

    @Override
    public void run() {
        
        //测试1: 默认使用""的形式引用字符串 来同步两个线程
        myPrinter.print("66666666666666");
    }


}

测试1 Client.java #测试类
package com.szs.client;

import com.szs.pojo.MyPrinter;
import com.szs.pojo.ThreadTest;

/**
 * 实验测试Synchronized锁定字符串常量池中的一个字符串
 * 实例化两个ThreadTest线程启动类做测试
 * @author Administrator
 */
public class Client {
    public static void main(String[] args) {
        //声明两个线程,争取一个字符串
        MyPrinter myPrinter = new MyPrinter();
        
        ThreadTest testA = new ThreadTest(myPrinter);
        
        ThreadTest testB = new ThreadTest(myPrinter);   
        
        //测试1: 默认使用""的形式引用字符串 来同步
        testA.setName("a");
        testB.setName("b");
        
        testA.start();
        testB.start();
        
    }
}

测试1 测试结果

字符串内容:66666666666666; 字符串hashCode值:995606336; 当前线程名字:a  实验1使用 "66666" 来同步
字符串内容:66666666666666; 字符串hashCode值:995606336; 当前线程名字:a  实验1使用 "66666" 来同步
字符串内容:66666666666666; 字符串hashCode值:995606336; 当前线程名字:a  实验1使用 "66666" 来同步
字符串内容:66666666666666; 字符串hashCode值:995606336; 当前线程名字:a  实验1使用 "66666" 来同步
字符串内容:66666666666666; 字符串hashCode值:995606336; 当前线程名字:a  实验1使用 "66666" 来同步
字符串内容:66666666666666; 字符串hashCode值:995606336; 当前线程名字:a  实验1使用 "66666" 来同步
字符串内容:66666666666666; 字符串hashCode值:995606336; 当前线程名字:a  实验1使用 "66666" 来同步
...........

测试2: 使用new的String对象来引用字符串 来同步两个线程

测试2工程结构如下

image.png

测试2 MyPrinter.java
package com.szs.pojo;
/**
 * 定义一个打印数字线程类,作为ThreadTest的属性类,显示线程争取情况
 * @author Administrator
 *
 */
public class MyPrinter extends Thread{
    public void print(String paramString){
        try {
            synchronized (paramString) {
                while(true){
                    System.out.println("字符串内容:"+paramString
                            +"; 字符串hashCode值:"+paramString.hashCode()
                            +"; 当前线程名字:"+ Thread.currentThread().getName()
                            +"\t实验2使用new String(\"6666\")来同步");
                    Thread.sleep(1000);
                }
            }
            
        } catch (Exception e) {
            e.printStackTrace();
            // TODO: handle exception
        }
    }
}
测试2 ThreadTest.java
package com.szs.pojo;
/**
 * 定义线程启动类 ,使用MyPrint作为属性类,调用其打印数字的方法
 * @author Administrator
 *
 */
public class ThreadTest extends Thread {
    private MyPrinter myPrinter;
     
    public ThreadTest(MyPrinter myPrinter) {
        this.myPrinter = myPrinter;
    }

    @Override
    public void run() {

        String newString = new String("66666666");
        
        //这里会进行两次调用,每个线程分别new一个对象,两个String对象地址不一样!
        myPrinter.print(newString);
    }


}

测试2 Client.java 测试类
package com.szs.client;

import com.szs.pojo.MyPrinter;
import com.szs.pojo.ThreadTest;

/**
 * 实验测试Synchronized锁定字符串常量池中的一个字符串
 * 
 *  测试2: 使用new的String来引用字符串 来同步两个线程,结果显示线程可以交替执行
 * 实例化两个ThreadTest线程启动类做测试
 * @author Administrator
 */
public class Client {
    public static void main(String[] args) {
        //声明两个线程,同步争取一个字符串
        
        MyPrinter myPrinter = new MyPrinter();
        
        ThreadTest testA = new ThreadTest(myPrinter);
        
        ThreadTest testB = new ThreadTest(myPrinter);   
        
        //测试2: 
        testA.setName("a");
        testB.setName("b");
        
        testA.start();
        testB.start();
        
    }
}

测试2 测试结果

(测试显示字符串hashCode值一致,其实这两个线程分别new的对象的内存地址是不一样的,只是hashcode一致,这里不多做解释)

字符串内容:66666666; 字符串hashCode值:1900542720; 当前线程名字:a   实验2使用new String("6666")来同步
字符串内容:66666666; 字符串hashCode值:1900542720; 当前线程名字:b   实验2使用new String("6666")来同步
字符串内容:66666666; 字符串hashCode值:1900542720; 当前线程名字:a   实验2使用new String("6666")来同步
字符串内容:66666666; 字符串hashCode值:1900542720; 当前线程名字:b   实验2使用new String("6666")来同步
字符串内容:66666666; 字符串hashCode值:1900542720; 当前线程名字:a   实验2使用new String("6666")来同步
字符串内容:66666666; 字符串hashCode值:1900542720; 当前线程名字:b   实验2使用new String("6666")来同步
字符串内容:66666666; 字符串hashCode值:1900542720; 当前线程名字:b   实验2使用new String("6666")来同步
................

总结

在测试1中:

  • 使用引号直接引用的String字符串来作为Synchronized的同步条件;测试并同步两个线程,结果显示只有线程"a"一致占用打印机进行打印.
  • 简单分析
  • String类型的对象是不可变的,当你使用 " " 的形式引用字符串时(如上面测试1的"6666666666"), 如果JVM发现字符串常量池中已经有一个这样的对象, 那么它就使用那个对象而不再生成一个新的字符串对象--改为直接引用这个"6666666666"字符串.
  • 这时两个线程一块Synchronized的这个"66666"字符串就会出现问题,
    • 线程"a"先获得锁后,就一直执里面的while循环, 无限进行打印;
    • 线程"b"就一直只能干等着,处在等待状态.
    • 其实还有, System.out.println("66666"=="66666"); 输出的值为True的, 同一个"66666"同一地址.

在测试2中:

  • 使用new的String来引用字符串来作为同步条件 来Synchronized同步两个线程,结果显示线程可以交替执行.
    • 简单分析
    • 这两个线程"a"和"b"分别run()的时候,都会调用代码 String newString = new String("66666666"); 产生两个String对象,这两个String对象的地址不一样, 故不会产生进程"a"一直占用CPU资源进行打印输出的问题.
    • 同样你也可以声明两个字符串都 = new String("66666666"),然后用"=="来进行判断这两个字符串的内存地址,结果为false.
  • 最后总结, 尽量不要使用String常量加锁 ,会出现死锁问题,可以使用new String()后的字符串对象加锁.或者,换成其他更好的办法/替代方案来解决实际问题.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,100评论 1 32
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,599评论 3 83
  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,744评论 0 10
  • 一、基础知识:1、JVM、JRE和JDK的区别:JVM(Java Virtual Machine):java虚拟机...
    杀小贼阅读 2,378评论 0 4
  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,417评论 1 14