关于Java中的synchronized关键字

【内容简介】

本文主要介绍Java中如何正确的使用synchronized关键字实现线程的互斥锁。

【能力需求】

至少已经完整的掌握了Java的语法基础,基本的面向对象知识,及创建并启动线程。

【正文】

关于synchronized关键字的使用,很多说法是“锁同一个对象”就可以确保锁是正常的,今天,有人提了一个问题,我觉得非常不错,所以与各位一起分享一下。

在这里,就不提关于线程和synchronized关键字的基本使用了,以非常传统的“银行取钱”的故事为案例,直接上代码:
Ps:以下代码是直接敲的,不是在IDE工具中复制出来的,可能存在敲错的地方,请见谅。

public class ThreadSample {
    public static Object lock = new Object();
    public static int money = 1000; 

    public static void main(String args) {
        Card card1 = new Card();
        Card card2 = new Card(); 

        card1.start();
        card2.start();
    }
} 

class Card extends Thread {
    public void run() {
        synchronize(ThreadSample.lock) {
            if(ThreadSample.money >= 800) {
                ThreadSample.money -= 800;
                System.out.println("取款成功,余额:" + ThreadSample.money);
            } else {
                System.out.println("余额不足,取款失败!");
            }
        }
    }
}

以上代码是模拟取钱的,使用static成员money表示帐户,所以保证了多个线程访问到的是同一个值,同时,基于static成员是唯一且具有共享特性的,以上代码中使用static成员lock用于synchronized锁定,这个做法是可行的,所以,在很多人的观点里会认为“锁的是同一个对象就可以实现锁定”。

但是,有人测试出了以下这样的代码并提问(忽略掉相同的代码片段):

class Card extends Thread {
    public Integer i = 1; 

    public void run() {
        synchronize(i) {
            if(ThreadSample.money >= 800) {
                ThreadSample.money -= 800;
                System.out.println("取款成功,余额:" + ThreadSample.money);
            } else {
                System.out.println("余额不足,取款失败!");
            }
        }
    }
}

写到这里,我不得不发自内心的再说一句,他作为一名第1次学习到线程的人,提出这样的问题,肯定是非常优秀的!

以上代码,在Card中声明了public Integer i = 1;并使用这个Integer对象作为synchronized锁的对象,最后,锁定也是成功的。

如果参考此前所说的“锁的是同一个对象就可以实现锁定”,那么,从代码里可以看到,运行过程中创建了2个Card对象,那么这2个Card对象有各自的Integer i成员,锁的就不是同一个对象,却也成功的锁定了,所以,此前的说法是不贴切的!
也许会有人说Integer或类似的包装类很特殊,也许是对应的int这些基本数据类型在作祟,那么,以下代码仍然是可行的:

class Card extends Thread {
    public String str = "www.tedu.cn"; 

    public void run() {
        synchronize(str) {
            if(ThreadSample.money >= 800) {
                ThreadSample.money -= 800;
                System.out.println("取款成功,余额:" + ThreadSample.money);
            } else {
                System.out.println("余额不足,取款失败!");
            }
        }
    }
}

好了,用String也是可以锁的,那就跟基本数据类型没有关系了,下一步的解释会是什么呢?先别着急,再来看一段代码:

public class ThreadSample {
    public static int money = 1000; 

    public static void main(String args) {
        Integer i1 = 1;
        Integer i2 = 1; 

        Card card1 = new Card(i1);
        Card card2 = new Card(i2); 

        card1.start();
        card2.start();
    }
} 

class Card extends Thread {
    public Integer i; 

    public Card(Integer i) {
        this.i = i;
    } 

    public void run() {
        synchronize(i) {
            if(ThreadSample.money >= 800) {
                ThreadSample.money -= 800;
                System.out.println("取款成功,余额:" + ThreadSample.money);
            } else {
                System.out.println("余额不足,取款失败!");
            }
        }
    }
}

以上代码中,在Card中添加了带参数的构造方法,为成员Integer i赋值,并使用成员Integer i来实现锁定,并且,在运行线程之前,分别声明了Integer i1和Integer i2用于创建Card对象,结果,锁定依然是可行的。

如果有兴趣的话,可以把以上代码中的Integer换成String,可以发现,仍然还是可行的!

在这里,已经很明显了,在运行线程之前,声明了不同的对象用于创建Card对象,不管是Integer类型还是String类型,都是可行的!

那么,应该怎么解释这个问题呢?也许会有人想到equals()和hashCode(),是不是equals()对比出true,并且hashCode()值相同,就可以呢?这个想法是错的,请看以下代码:

public class ThreadSample {
    public static int money = 1000; 

    public static void main(String args) {
        long timeMillis = System.currentTimeMillis(); 

        Date d1 = new Date(timeMillis);
        Date d2 = new Date(timeMillis); 

        Card card1 = new Card(d1);
        Card card2 = new Card(d2); 

        card1.start();
        card2.start();
    }
}

我们都知道Date类是重写了equals()和hashCode()的,以上代码中的d1和d2表示的时间完全相同,所以,d1和d2的equals()对比结果为true,且hashCode()返回值是相同的,但是,以上代码却不能成功的实现锁定!

看完了以上这么纠结的问题,最后总结一下答案,通俗一点来说:如果使用==判断结果为true,就可以用于synchronize锁定!

比如,在使用String测试的时候,声明以下2个String类型的数据:

String str1 = "www.cnblogs.com";
String str2 = "www.cnblogs.com";

分别使用以上2个对象创建Card,并在Card中声明String成员用于锁定,结果是OK的,但是,以下代码就不行:

String str1 = "www.cnblogs.com";
String str2 = "";
str2 += "www.cnblogs.com";

尽管组成字符是相同的,但是,str1是直接使用常量赋值的,对应的数据保存在字符串常量池中,而str2最后是通过变量运算出来的,是运行期得到的结果,对应的数据将位于堆内存中,所以,在栈内存中保存的str1和str2的引用并不相同,也就是str1 == str2的结果为false,所以,这样来锁是失败的!

上述关于String的第1段代码中,是在编译期使用String常量赋值给str1和str2,由于字符串常量池中的字符串是唯一的,所以str1、str2使用==时的结果将是true,至于Integer这些包装类,内部是通过诸多static成员来实现的,基于static的特性,所以上述代码中的i1、i2使用==判断时的结果也会是true。

所以,关于synchronized锁的对象,学术化的定义可以是“对象在栈内存中的引用相同,就是同一个对象,就可以用于锁”,通俗的表达就是“使用==判断为true就可以用于锁”,因为使用==判断其实就是判断栈内存中存储的数据。

【小结】

用于synchronized锁的对象,如果可以存在2个,并使用==判断的结果为true,就可以用于锁!

如果本文有不足或者错误的地方,欢迎随时指出,如需转载,请注明出处,Thank you !

时间: 2024-08-02 06:58:30

关于Java中的synchronized关键字的相关文章

巨人大哥谈Java中的Synchronized关键字用法

巨人大哥谈Java中的Synchronized关键字用法 认识synchronized 对于写多线程程序的人来说,经常碰到的就是并发问题,对于容易出现并发问题的地方价格synchronized基本上就搞定 了,如果说不考虑性能问题的话,这一操绝对能应对百分之九十以上的情况,若对于性能方面有要求的话就需要额外的知识比如读写锁等等.本文目的先了解透彻synchronized的基本原理. Synchronized的基本使用 Synchronized的作用主要有三个: (1)确保线程互斥的访问同步代码 

Java中利用synchronized关键字实现多线程同步问题

Java 中多线程的同步依靠的是对象锁机制,synchronized关键字就是利用了封装对象锁来实现对共享资源的互斥访问. 下面以一个简单例子来说明多线程同步问题,我们希望在run()方法里加入synchronized关键字来实现互斥访问. package com.clark.thread; public class MyThread implements Runnable{     private int threadId;          public MyThread(int id){

Java中的Synchronized关键字用法

认识synchronized 对于写多线程程序的人来说,经常碰到的就是并发问题,对于容易出现并发问题的地方加上synchronized修饰符基本上就搞定 了,如果说不考虑性能问题的话,这一招绝对能应对百分之九十以上的情况,若对于性能方面有要求的话就需要额外的知识比如读写锁等等.本文目的先了解透彻synchronized的基本原理. Synchronized的基本使用 Synchronized的作用主要有三个: (1)确保线程互斥的访问同步代码 (2)保证共享变量的修改能够及时可见 (3)有效解决

谈谈java中的synchronized关键字

1.synchronized的3种用法 public class Client { public static void main(String[] args) { testSynchronized(); } private static void testSynchronized() { new Foo().sayHello(); } static class Foo { //修饰代码块 void sayHello() { synchronized (this) { System.out.pr

java中的synchronized关键字

参考:http://www.cnblogs.com/devinzhang/archive/2011/12/14/2287675.html 多线程问题的根因: 多线程环境下,对一个对象更改的时候,一个线程A对某个变量做了改变,但是还没改变完成能,就被另外一个线程B抢去了cpu,那么A就不会再执行了,因此导致了数据不一致行为.针对上面引文中银行取款存款的例子,本来存一百取一百正好抵消,但是由于多线程的之间的肆意抢占,有些取存款的操作没有完成,自然导致结果千奇百怪. 关键点: synchronized

Java中使用同步关键字synchronized需要注意的问题

在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行.synchronized既可以加在一段代码上,也可以加在方法上. 关键是,不要认为给方法或者代码段加上synchronized就万事大吉,看下面一段代码: class Sync { public synchronized void test() { System.out.println("test开始.."); try { Thread.sle

理解java中的volatile关键字

Java语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量.这两种机制的提出都是为了 实现代码线程的安全性.Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分. volatile 写和读的内存语义: 线程 A 写一个 volatile 变量,实质上是线程 A

详细讲解 java 中的synchronized 转自 http://www.cnblogs.com/devinzhang/archive/2011/12/14/2287675.html

Java synchronized详解 第一篇: 使用synchronized 在编写一个类时,如果该类中的代码可能运行于多线程环境下,那么就要考虑同步的问题.在Java中内置了语言级的同步原语 --synchronized,这也大大简化了Java中多线程同步的使用.我们首先编写一个非常简单的多线程的程序,是模拟银行中的多个线程同时对同一 个储蓄账户进行存款.取款操作的.在程序中我们使用了一个简化版本的Account类,代表了一个银行账户的信息.在主程序中我们首先生成了 1000个线程,然后启动

Java并发之synchronized关键字深度解析(一)

前言 近期研读路神之绝世武学,徜徉于浩瀚无垠知识之海洋,偶有攫取吉光片羽,惶恐未领略其精髓即隐入岁月深处,遂急忙记录一二,顺备来日吹cow之谈资.本小系列为并发之亲儿子-独臂狂侠synchronized专场. 一.使用场景 synchronized是java中的一个关键字,用于给对象加锁,保证在单机的并发条件下线程安全.原子性和可见性是它保证线程安全的基础功能.一定要注意它锁的是对象,而且它是一个排他的非公平可重入锁.本文先从使用的场景上来展现其作用. 1.用在普通方法中 常用的格式如下所示: