Effective Item - 合理使用synchronized关键字

记得最初使用synchronized关键字时是为了singleton。

仅仅是判断field是不是null,如果为null则指向新的实例。

但这种check-and-action的方式会有同步的问题,也就是说"同时"有两个线程通过了check。

通过这种体验得出了最基本的结论:synchronized可以保证在同一时刻只有一个线程可以执行某个代码块。

很多人把同步的概念仅仅理解为互斥关系(mutual exclusion)。

比如,当一个对象被一个线程修改的时候可以阻止其他线程观察到该对象的状态。

即,对象创建之初是一致状态,被访问时则被锁定,此时其状态可能会改变,但最终还是会保持一致状态。

但这并不是synchronized的全部意义。

另一个意义在于,同步可以保证进入同步代码块的线程可以看到之前线程的修改结果,前提是由同一个锁进行保护。

JLS 17.4.7 http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html 讲道读写一个非double非long的变量是原子的,即多个线程在没有同步的情况下安全地修改这个变量,但是这并不代表一个线程写入的值对另一个线程是可见的。

所以为了可靠通信,我们仍需要进行同步。

以下面的代码为例,让一个线程轮询一个boolean field,false时继续执行,接着让主线程将其变成true,以让轮询终止:

import java.util.concurrent.TimeUnit;

public class StopThread {
	private static boolean stopRequested;

	public static void main(String[] args) throws InterruptedException {
		Thread backgroundThread = new Thread(new Runnable() {
			public void run() {
				int i = 0;
				while (!stopRequested)
					i++;
			}
		});
		backgroundThread.start();

		TimeUnit.SECONDS.sleep(1);
		stopRequested = true;
	}
}

遗憾的是主线程将stopRequested设为true并没有让backgroundThread停下来。

正如之前所说,问题在于没有进行同步backgroundThread感觉不到其他线程已经修改了stopRequested。

也就是说

while(!stopRequested)i++;

相当于

if(!stopRequested)
    while(true)i++;

于是,我们为stopRequested提供一个读方法并加上synchronized,如下:

public class StopThread {
	private static boolean stopRequested;

	private static synchronized void requestStop() {
		stopRequested = true;
	}

	private static synchronized boolean stopRequested() {
		return stopRequested;
	}

	public static void main(String[] args) throws InterruptedException {
		Thread backgroundThread = new Thread(new Runnable() {
			public void run() {
				int i = 0;
				while (!stopRequested())
					i++;
			}
		});
		backgroundThread.start();

		TimeUnit.SECONDS.sleep(1);
		requestStop();
	}
}

需要注意的是,写方法也需要进行同步,读写操作都需要加上synchronized关键字才会起作用。

遗憾的是,到这一步为止并没有合理使用synchronized关键字。

我们所做的这些不是为了互斥访问,因为读写方法中的行为即使没有加上synchronized关键字也是原子的,我们想要的效果仅仅是保证线程之间的通信效果。

我们应该有更正确的方式解决这个问题,于是将代码改成如下形式:

import java.util.concurrent.TimeUnit;

public class StopThread {
	private static volatile boolean stopRequested;

	public static void main(String[] args) throws InterruptedException {
		Thread backgroundThread = new Thread(new Runnable() {
			public void run() {
				int i = 0;
				while (!stopRequested)
					i++;
			}
		});
		backgroundThread.start();

		TimeUnit.SECONDS.sleep(1);
		stopRequested = true;
	}
}

虽然volatile关键字并不保证互斥访问,但可以保证线程可以感觉到其他线程之前的操作结果。

但是,volatile关键字也有使用不当的时候,比如下面的代码想每次都返回不同的值:

private static volatile int nextSerialNumber = 0;
 
public static int generateSerialNumber(){
    return nextSerialNumber++;
}

这段代码运行正常是运气好,因为不用于之前的boolean类型变量赋值,++操作不是原子的,它需要先读取旧值再对其进行增加再返回新值。

这就像最初check-and-action的问题,多个线程同时读到了相同的旧值。

虽然synchronized关键字可以解决这一问题,但我们有更好的选择:

private static final AtomicLong nextSerialNum = new AtomicLong();

public static long generateSerialNum(){
    return nextSerialNum.getAndIncrement();
}
时间: 2024-08-03 23:00:26

Effective Item - 合理使用synchronized关键字的相关文章

synchronized关键字以及实例锁 类锁

Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 二.然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块.

JAVA线程安全之synchronized关键字的正确用法

JAVA线程安全关于synchronized关键字的用法,今天才知道原来我一直错了.以为用了synchronized关键字包住了代码就可以线程同步安全了. 测试了下.发现是完全的错了.synchronized必须正确的使用才是真正的线程安全...虽然知道这种写法,一直以为却由于懒而用了错误的方法. 看来基础还没有打好.仍需复习加强!工作中犯这种错误是不可原谅的,要知道使用synchronized关键字的地方都是数据敏感的!汗一把... 先贴代码: [java] view plaincopypri

java中的synchronized关键字

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

java基础回顾(五)线程详解以及synchronized关键字

本文将从线程的使用方式.源码.synchronized关键字的使用方式和陷阱以及一些例子展开java线程和synchronized关键字的内容. 一.线程的概念 线程就是程序中单独顺序的流控制.线程本 身不能运行,它只能用于程序中. 二.线程的实现 线程的实现有两种方式: 1.继承Thread类并重写run方法 2.通过定义实现Runnable接口的类进而实现run方法 当用第一种方式时我们需要重写run方法因为Thread类里的run方法什么也不做(见下边的源码),当用第二种方式时我们需要实现

java synchronized关键字浅探

synchronized 是 java 多线程编程中用于使线程之间的操作串行化的关键字.这种措施类似于数据库中使用排他锁实现并发控制,但是有所不同的是,数据库中是对数据对象加锁,而 java 则是对将要执行的代码加锁. 在 java 中使用 synchronized 关键字时需要注意以下几点: 1.synchronized 是针对同一个资源的访问作出限制.这是出现该关键字的原因. 2.对于类而言,某一个线程执行到一个 synchronized 修饰的类方法或者类方法中的代码段时,该方法或者代码段

Effective Item 3 - 避免不必要的对象

通常,我们更喜欢重用一个对象而不是重新创建一个. 如果对象是不可变的,它就始终可以被重用. 下面是一个反面例子,Joshua Bloch明确指出[DON'T TO THIS]: String s = new String("stringette"); 该语句每次执行时都创建一个新的实例. String构造器中的参数"stringette"本身是一个实例,功能方面等同于那些通过构造器创建的对象. 如果这种语句放到循环里,效果会变得更糟. 于是我们只需要: String

关于Java中的synchronized关键字

[内容简介] 本文主要介绍Java中如何正确的使用synchronized关键字实现线程的互斥锁. [能力需求] 至少已经完整的掌握了Java的语法基础,基本的面向对象知识,及创建并启动线程. [正文] 关于synchronized关键字的使用,很多说法是“锁同一个对象”就可以确保锁是正常的,今天,有人提了一个问题,我觉得非常不错,所以与各位一起分享一下. 在这里,就不提关于线程和synchronized关键字的基本使用了,以非常传统的“银行取钱”的故事为案例,直接上代码:Ps:以下代码是直接敲

Effective Item 17 - 关于方法的参数声明

给方法的参数加上限制是很常见的,比如参数代表索引时不能为负数.对于某个关键对象引用不能为null,否则会进行一些处理,比如抛出相应的异常信息. 对于这些参数限制,方法的提供者必须在文档中注明,并且在方法开头时检查参数,并在失败时提供明确的信息,即detect errors as soon as possible after they occur,这将成为准确定位错误的一大保障. 如果没有做到这一点,最好的情况是方法在处理过程中失败并抛出了莫名其妙的异常,错误的源头变得难以定位,但这是最好的情况.

java synchronized关键字

Java中synchronized关键字和对象的内置锁结合使用,用来保护代码块在并发环境下的线程安全,可以使被保护的代码块操作原子性. synchronized关键字可以用于修饰方法来保护方法内的全部代码块,可以用synchronized(对象1) 的方式保护指定代码块.(这里说一下:很多书中都说synchronized可以给对象加锁,我实在不愿意这么说,这样让我概念混淆...因为,对象内置锁是本来就存在的,不是谁加给它的,"synchronized(对象1)"我更愿意解释成:执行到此