Java对多线程的支持与同步机制似乎使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题.到底如何?――还得对synchronized关键字的作用进行深入了解才可定论.
总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块.如果再细的分类,synchronized可作用于instance变量,object reference(对象引用),static函数和class literals(类名称字面常量)身上.
在进一步阐述之前,我们需要明确几点:
A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问.
B.每个对象只有一个锁(lock)与之相关联.
C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制.
接着来讨论synchronized用到不同地方对代码产生的影响:
假设P1,P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1,P2就都可以调用它们.
1. 把synchronized当作函数修饰符时,示例代码如下:
Public synchronized void methodAAA()
{
//….
}
这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象.也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果.但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法.
上边的示例代码等同于如下代码:
public void methodAAA()
{
synchronized (this) // (1)
{
//…..
}
}
(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1.可见同步方法实质是将synchronized作用于object reference.――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(
2.同步块,示例代码如下:
public void method3(SomeObject so)
{
synchronized(so)
{
//…..
}
}
这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码.当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:
class Foo implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance变量
Public void methodA()
{
synchronized(lock) { //… }
}
//…..
}
注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码.
3.将synchronized作用于static 函数,示例代码如下:
Class Foo
{
public synchronized static void methodAAA() // 同步的static 函数
{
//….
}
public void methodBBB()
{
synchronized(Foo.class) // class literal(类名称字面常量)
}
}
代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了).
记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的.P1指的是由Foo类产生的对象.
可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样.A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class.
小结如下:
搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序.
还有一些技巧可以让我们对共享资源的同步访问更加安全:
1. 定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量.如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它.这也是JavaBean的标准实现方式之一.
2. 如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险. 这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了.
对synchronized(this)的一些理解
一,当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块.
二,然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块.
三,尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞.
四,第三个例子同样适用其它同步代码块.也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁.结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞.
五,以上规则对其它对象锁同样适用.
举例说明:
一,当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块.
package ths;
public class Thread1 implements Runnable {
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
}
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread ta = new Thread(t1, "A");
Thread tb = new Thread(t1, "B");
ta.start();
tb.start();
}
}
结果:
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
二,然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块.
package ths;
public class Thread2 {
public void m4t1() {
synchronized(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
final Thread2 myt2 = new Thread2();
Thread t1 = new Thread(
new Runnable() {
public void run() {
myt2.m4t1();
}
}, "t1"
);
Thread t2 = new Thread(
new Runnable() {
public void run() {
myt2.m4t2();
}
}, "t2"
);
t1.start();
t2.start();
}
}
结果:
t1 : 4
t2 : 4
t1 : 3
t2 : 3
t1 : 2
t2 : 2
t1 : 1
t2 : 1
t1 : 0
t2 : 0
三,尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞.
//修改Thread2.m4t2()方法:
public void m4t2() {
synchronized(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
结果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
四,第三个例子同样适用其它同步代码块.也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁.结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞.
//修改Thread2.m4t2()方法如下:
public synchronized void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
结果:
t1 : 4
t1 : 3
t1 : 2
t1 : 1
t1 : 0
t2 : 4
t2 : 3
t2 : 2
t2 : 1
t2 : 0
五,以上规则对其它对象锁同样适用:
package ths;
public class Thread3 {
class Inner {
private void m4t1() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t1()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
}
}
}
private void m4t2() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
}
}
}
}
private void m4t1(Inner inner) {
synchronized(inner) { //使用对象锁
inner.m4t1();
}
}
private void m4t2(Inner inner) {
inner.m4t2();
}
public static void main(String[] args) {
final Thread3 myt3 = new Thread3();
final Inner inner = myt3.new Inner();
Thread t1 = new Thread(
new Runnable() {
public void run() {
myt3.m4t1(inner);
}
}, "t1"
);
Thread t2 = new Thread(
new Runnable() {
public void run() {
myt3.m4t2(inner);
}
}, "t2"
);
t1.start();
t2.start();
}
}
结果:
尽管线程t1获得了对Inner的对象锁,但由于线程t2访问的是同一个Inner中的非同步部分.所以两个线程互不干扰.
t1 : Inner.m4t1()=4
t2 : Inner.m4t2()=4
t1 : Inner.m4t1()=3
t2 : Inner.m4t2()=3
t1 : Inner.m4t1()=2
t2 : Inner.m4t2()=2
t1 : Inner.m4t1()=1
t2 : Inner.m4t2()=1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=0
现在在Inner.m4t2()前面加上synchronized:
private synchronized void m4t2() {
int i = 5;
while(i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i);
try {
Thread.sleep(500);
} catch(InterruptedException ie) {
}
}
}
结果:
尽管线程t1与t2访问了同一个Inner对象中两个毫不相关的部分,但因为t1先获得了对Inner的对象锁,所以t2对Inner.m4t2()的访问也被阻塞,因为m4t2()是Inner中的一个同步方法.
t1 : Inner.m4t1()=4
t1 : Inner.m4t1()=3
t1 : Inner.m4t1()=2
t1 : Inner.m4t1()=1
t1 : Inner.m4t1()=0
t2 : Inner.m4t2()=4
t2 : Inner.m4t2()=3
t2 : Inner.m4t2()=2
t2 : Inner.m4t2()=1
t2 : Inner.m4t2()=0
线程安全
一样的),而且对于访问方法同样存在线程安全问题。
我们知道Hashtable是线程安全的,因为所有的方法都同步了,也就是说最多只能同
那就有可能你调用size方法还没有返回,一个线程又添加了一个数据,这个时候你读取到的值应该是正确
public class SynHashtable{
public static void main(String[] args)
{
final Hashtable ht=new Hashtable();
ht.put(new Object(),"Quick to Death");
new Thread(){
public void run(){
while(true){
if(ht.size()<10){
ht.put(new
}
else{
ht.clear();
}
}
}
}.start();
new Thread(){
public void run(){
unsafe(ht);
}
}.start();
}
while(true){
//synchronized(ht){
Iterator it=ht.values().iterator();
while(it.hasNext())
System.out.println(it.next());
}
//}
}
}
Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。
所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。
发表评论
-
java面试总结(1)——基础知识
2012-07-05 15:54 8661、什么是多态? 多态,父类的引用指向子类的对象,使父类的引 ... -
递归-组合(用背包问题解)
2011-04-01 21:23 748求指定数据的组合,这里的指定数据用一个数组模拟所有可以选择 ... -
Servlet和JSP的线程安全问题
2011-03-25 10:50 773不要定义servlet的类变量,不要使用jsp中的s ... -
代码复用的规则
2011-03-24 17:25 650李炜 北京杰合伟业软件公司产品技术部经理2001 年 7 ... -
java多进程——Java Multiple process (PID)
2011-03-24 10:48 1538Java的多进程运行模式分析 一般我们在java中运行其 ... -
用JSP实现假分页
2008-08-08 10:29 7361.使用MySql数据库建一个表,并添加记录: -- 删除表 ... -
用MVC模式实现真分页
2008-08-23 21:01 524·我们在之前代码上实现一个搜索框 输入查询关键字:< ... -
位运算符
2008-08-24 19:35 339Java 定义的位运算(bitwise operators ) ... -
struts高级技术——解决重复提交和上传文件
2008-08-24 19:44 433·解决重复提交的工具——Token令牌<?xml:nam ... -
从class.forName()说java中的reflection(反射/映像)
2008-09-21 21:19 530先通过我们一个熟悉的例子来说明: 使用JDBC时,我们都会很 ... -
eclipse 自动补全的设置,不用按 alt-/ 了
2011-03-23 11:07 702偶然间看到了这个,或许有和我一样不喜欢按 alt-/ 兄弟用得 ... -
用JSP实现假分页
2008-08-08 10:29 13441.使用MySql数据库建一个表,并添加记录: -- 删除表 ... -
用MVC模式实现真分页
2008-08-23 21:01 1154·我们在之前代码上实现一个搜索框 输入查询关键字:< ... -
位运算符
2008-08-24 19:35 751Java 定义的位运算(bitwise operators ) ... -
java排序大全
2008-08-24 19:42 648java排序大全 插入排序: packag ... -
struts高级技术——解决重复提交和上传文件
2008-08-24 19:44 840·解决重复提交的工具——Token令牌<?xml:nam ... -
从class.forName()说java中的reflection(反射/映像)
2008-09-21 21:19 904先通过我们一个熟悉的例子来说明: 使用JDBC时,我们都会很 ...
相关推荐
Java 对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized 关键 字就可以轻松地解决多线程共享数据同步问题。到底如何?――还得对synchronized 关键字 的作用进行深入了解才可定论。 总的说来,...
java锁机制Synchronized java锁机制Synchronized java锁机制Synchronized java锁机制Synchronized
java同步synchronized关键字用法示例
java的线程同步机制synchronized关键字的理解_.docx
java锁机制Synchronized.pdf
java中synchronized用法
Java线程及同步(synchronized)样例代码
在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。
在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。
在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。
【Java基础知识 第四节 多线程复习】中,同步代码块(synchronized关键字)的两个练习代码。
主要介绍了Java 同步锁(synchronized)详解及实例的相关资料,需要的朋友可以参考下
Javasynchronized机制.pdf
java锁机制Synchronized[归纳].pdf
在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。