`
coolxing
  • 浏览: 872956 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
9a45b66b-c585-3a35-8680-2e466b75e3f8
Java Concurre...
浏览量:96869
社区版块
存档分类
最新评论

Race condition--Java Concurrency In Practice C02读书笔记

阅读更多

[本文是我对Java Concurrency In Practice第二章的归纳和总结,  转载请注明作者和出处,  如有谬误, 欢迎在评论中指正. ]

 

多线程环境下,无需调用方进行任何同步处理也能保证正确性的类是线程安全的类

 

无状态的对象是线程安全的。无状态是指没有成员变量。由于方法的局部变量都是在线程私有的栈中分配的,因此在一个线程中调用无状态对象的方法,不会影响到其他线程。

 

race condition: 正确性依赖于事件发生的相对时间。

check-and-act是race condition中的一种,指的是基于check的结果进行操作。由于check和act并非是原子的,进行act时check的结果可能已经无效,那么基于check所进行的act就可能带来问题。

见如下的lazy单例类:

public class LazyInitRace {
	private static ExpensiveObject instance = null;
	public static ExpensiveObject getInstance() {
		// if语句是一个check-and-act操作
		if (instance == null) {
			instance = new ExpensiveObject();
		}
		return instance;
	}
}
 这是一个check-and-act的典型例子:首先判断instance是否为null,如果是就创建ExpensiveObject对象,否则直接返回instance。但是判断和创建并非原子操作,假设线程1判断出instance为null,要是另一个线程紧接着创建了ExpensiveObject对象,那么线程1的判断就失效了,基于判断结果所进行的创建操作就会导致程序中存在多个ExpensiveObject对象--这违背了单例模式的初衷。

 

Read‐modify‐write也是race condition中的一种,指的是读取某个变量的值,修改后写回。这显然不是一个原子操作,如果B线程在A线程read之后write之前修改了变量的值,那么A线程read的结果就失效了,基于read所做的modify就可能带来问题。

见如下的servlet:

public class CountingFactorizer implements Servlet {
	private final long count = 0;
	public void service(ServletRequest req, ServletResponse resp) {
		BigInteger i = extractFromRequest(req);
		BigInteger[] factors = factor(i);
		// // 自增语句是一个Read‐modify‐write操作
		count++;
		encodeIntoResponse(resp, factors);
	}
}
 

使用java提供的同步机制,将check-and-act或者Read‐modify‐write转换为原子操作,则可以避免race condition造成的错误。

使用同步机制改进LazyInitRace和CountingFactorizer类,使之成为线程安全的类:

public class LazyInitRace {
	private static ExpensiveObject instance = null;

	public static ExpensiveObject getInstance() {
		// 使用synchronized将check-and-act操作转换为原子操作
		if (instance == null) {
			synchronized (LazyInitRace.class) {
				if (instance == null) {
					instance = new ExpensiveObject();
				}
			}
		}
		return instance;
	}
}

public class CountingFactorizer implements Servlet {
	private final AtomicLong count = new AtomicLong(0);
	public void service(ServletRequest req, ServletResponse resp) {
		BigInteger i = extractFromRequest(req);
		BigInteger[] factors = factor(i);
		// 使用long对应的原子类AtomicLong,将Read‐modify‐write操作转换为原子操作
		count.incrementAndGet();
		encodeIntoResponse(resp, factors);
	}
}
 

对于只有一个成员变量的对象,成员的状态就是对象的状态。如果涉及到该成员的操作都是原子的,那么该类就是一个线程安全的类。比如以上的LazyInitRace类,其只有一个成员变量instance,且涉及instance的操作是原子的,因此LazyInitRace类是一个线程安全的类。

是否可以类推出,只要类的每一个成员变量的操作都是原子的,类就是线程安全的?不行。

因为每一个成员的操作都是原子的,不能保证所有涉及到成员的操作整体上是原子的。例如:

public class UnsafeCachingFactorizer implements Servlet {
	private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>();
	private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>();
	public void service(ServletRequest req, ServletResponse resp) {
		BigInteger i = extractFromRequest(req);
		// 优先使用缓存
		if (i.equals(lastNumber.get()))
			encodeIntoResponse(resp, lastFactors.get() );
		else {
			BigInteger[] factors = factor(i);
			// 将最后一次的计算结果缓存起来
			lastNumber.set(i);
			lastFactors.set(factors);
			encodeIntoResponse(resp, factors);
		}
	}
}
 UnsafeCachingFactorizer类的2个成员lastNumber和lastFactors的set()和get()方法是原子的,但是该类不是线程安全的。因为从整体上看,2次写入和2次读取都不是同时进行的,UnsafeCachingFactorizer类仍然存在race condition.

 

java的synchronized机制使用的锁是可重入锁,即同一个线程可以多次申请持有同一把锁而不会引起死锁。假设A线程持有lock,那么如果B线程申请lock锁,B线程就会被阻塞。但是如果A线程再次申请已经持有的lock锁,该申请将获得通过,这就是所谓的同一线程可多次获取同一把锁。作为锁的对象,不仅需要标识锁的所有者,也需要标识所有者持有的计数。如果所有者线程刚刚申请到锁,则计数器的值为1,每重新获取一次,计数器的值加1,每退出一个同步代码块,计数器的值减1. 当计数器的值减为0时,所有者线程才释放锁。可重入锁的设计是为了防止因申请已持有的锁而造成死锁,例如:

public class Widget {
	public synchronized void doSomething() {
		...
	}
}
public class LoggingWidget extends Widget {
	public synchronized void doSomething() {
		System.out.println(toString() + ": calling doSomething");
		super.doSomething();
	}
}
 如果java中的锁不是可重入的,那么调用LoggingWidget对象的doSomething方法就会导致死锁。

 

2
0
分享到:
评论
5 楼 yuanliangding 2016-08-02  
原来这个叫race condition哦。
4 楼 wsong 2015-10-02  
hapjin 写道
lz,请问下,“ 如果java中的锁不是可重入的,那么调用LoggingWidget对象的doSomething方法就会导致死锁。”这句话怎么理解?能详细解释下么?谢谢!

子类的doSomething()覆盖了父类的doSomething(),当子类的doSomething()执行时,synchronized 锁定了子类对象,然后再执行super.doSomething()时,父类的doSomething()的synchronized 需要锁定父类对象吧?

是不是synchronized 不区分子类对象 和 父类的对象?
这样,当锁是可重入的时,在子类LoggingWidget synchronized 锁定了一个对象,再执行super.doSomething()时,又需要锁定一个对象,这两个对象(一个是子类型,一个是父类型)对synchronized 而言,没有区别?

锁定的是同一个对象呀!
子类继承了父类的方法,然后又被重写了,所以从父类继承的方法变成隐式的不会显现,非要调用必须用super.来触发
3 楼 hapjin 2015-09-22  
lz,请问下,“ 如果java中的锁不是可重入的,那么调用LoggingWidget对象的doSomething方法就会导致死锁。”这句话怎么理解?能详细解释下么?谢谢!

子类的doSomething()覆盖了父类的doSomething(),当子类的doSomething()执行时,synchronized 锁定了子类对象,然后再执行super.doSomething()时,父类的doSomething()的synchronized 需要锁定父类对象吧?

是不是synchronized 不区分子类对象 和 父类的对象?
这样,当锁是可重入的时,在子类LoggingWidget synchronized 锁定了一个对象,再执行super.doSomething()时,又需要锁定一个对象,这两个对象(一个是子类型,一个是父类型)对synchronized 而言,没有区别?
2 楼 chuanwang66 2013-08-28  
准备转载到我空间里
1 楼 chuanwang66 2013-08-28  
博主总结得很好啊,我刚开始看这本书,收货颇大:)

相关推荐

    Java Concurrency in Practice 无水印pdf

    Java Concurrency in Practice 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者...

    Java Concurrency in Practice JAVA并发编程实践(中英文版)

    Using the concurrency building blocks in java.util.concurrent Performance optimization dos and don'ts Testing concurrent programs Advanced topics such as atomic variables, nonblocking algorithms, ...

    Java Concurrency in Practice

    Java Concurrency in practice

    Java并发编程实践(java concurrency in practice)pdf (java多线程总结.ppt)

    《Java并发编程实践》是Java开发者必读的经典之作,由Brian Goetz等多位专家共同撰写。这本书深入浅出地探讨了Java平台上的并发问题,帮助读者理解和掌握如何编写高效、可靠且可维护的多线程应用程序。以下是该书...

    Java Concurrency in Practice JAVA并发编程实践中文版(全)

    Java Concurrency in Practice JAVA并发编程实践中文版(全)第二部分

    java并行编程(Java Concurrency in Practice) 英文版chm

    &lt;&lt;java并行编程&gt;&gt;英文版chm格式,英文名称&lt;Java Concurrency in Practice&gt;,一直想买这本书,但总是缺货,找到了电子版,分享给大家。 Java Concurrency in Practice By Brian Goetz, Tim Peierls, Joshua Bloch,...

    Java Concurrency in Practice.zip

    《Java Concurrency in Practice》是Java并发编程领域的一本经典著作,由Brian Goetz、Tim Peierls、Joshua Bloch、Joseph Bowles和Doug Lea等专家共同编写。这本书深入探讨了Java平台上的多线程和并发编程,旨在...

    java concurrency in practice

    java concurrency in practice 经典的多线程编程书籍,英文版

    java_concurrency_in_practice_source源代码

    这里的"java_concurrency_in_practice_source"源代码正是书中实例的实现,它涵盖了Java多线程编程中的关键概念和技术。 1. **线程基础**:Java中创建线程有两种方式,一是通过`Thread`类的子类,二是实现`Runnable`...

    Java Concurrency In Practice Learning Note

    本笔记将深入探讨《Java Concurrency In Practice》这本书中的核心概念,结合Guava库的实际使用案例,帮助读者理解并掌握Java并发编程的精髓。 首先,我们来了解Java并发的基础知识。Java提供了丰富的并发工具类,...

    Concurrent_Programming+Java Concurrency in Practice+langspec

    首先,"Java Concurrency in Practice"是Java并发编程的经典之作,由Brian Goetz、Tim Peierls、Joshua Bloch、David Holmes和Doug Lea合著。这本书提供了一套实用的指导原则、设计模式和最佳实践,帮助Java开发者...

    Java Concurrency In Practice.pdf

    《Java Concurrency In Practice》是一本关于Java并发编程的经典著作,由Brian Göetz、Tim Peierls、Joshua Bloch、Joseph Bowbeer、David Holmes和Doug Lea共同编写。本书深入探讨了Java平台上的多线程编程技巧,...

    Java Concurrency in Practice电子书PDF加源码包

    java_concurrency_in_practice.pdf jcip-examples-src.jar jcip-annotations-src.jar 英文版是高清晰的,实战和实践都是同一帮人对英文版书的翻译,网传实战的翻译质量更好,实战是2012年出版的,应该是对前一版实践...

    Java并发实践英文版(Java Concurrency in Practice)

    - **书名**:《Java并发实践》(Java Concurrency in Practice) - **作者**:Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, Doug Lea - **出版社**:Addison Wesley Professional - **...

    Java.Concurrency.in.Practice.pdf

    通过以上章节的概述,可以看出《Java Concurrency in Practice》这本书全面地覆盖了 Java 并发编程的关键知识点和技术细节,是一本非常有价值的参考书籍。无论是对于初学者还是有经验的开发者来说,都能从中获得关于...

Global site tag (gtag.js) - Google Analytics