`
aswang
  • 浏览: 847658 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

一个看似线程安全的示例

    博客分类:
  • java
阅读更多

在《java并发编程实战》第四章4.4.1节给出了一个程序示例:

 

@NotThreadSafe
class BadListHelper <E> {
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());

    public synchronized boolean putIfAbsent(E x) {
        boolean absent = !list.contains(x);
        if (absent)
            list.add(x);
        return absent;
    }
}

 

  这个示例希望实现的功能是为List提供一个原子操作:若没有则添加。因为ArrayList本身不是线程安全的,所以通过集合Collections.synchronizedList将其转换为一个线程安全的类,然后通过一个辅助的方法来为List实现这么个功能。初看起来这个方法没问题,因为也添加了synchronized关键字实现加锁了。

 

但是仔细分析,你会发现问题。首先对于synchronized关键字,需要说明的是,它是基于当前的对象来加锁的,上面的方法也可以这样写:

 

public boolean putIfAbsent(E x) {
	synchronized(this) {
		boolean absent = !list.contains(x);
		if (absent)
			list.add(x);
		return absent;
    }
}

 

  所以这里的锁其实是BadListHelper对象, 而可以肯定的是Collections.synchronizedList返回的线程安全的List内部使用的锁绝对不是BadListHelper的对象,所以BadListHelper中的putIfAbsent方法和线程安全的List使用的不是同一个锁,因此上面的这个加了synchronized关键字的方法依然不能实现线程安全性。

 

下面给出书中的另一种正确的实现:

 

 

@ThreadSafe
class GoodListHelper <E> {
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());

    public boolean putIfAbsent(E x) {
        synchronized (list) {
            boolean absent = !list.contains(x);
            if (absent)
                list.add(x);
            return absent;
        }
    }
}

 

  如果你要分析这个实现是否正确,你需要搞清楚Collections.synchronizedList返回的线程安全的List内部使用的锁是哪个对象,所以你得看看Collections.synchronizedList这个方法的源码了。该方法源码如下:

 

public static <T> List<T> synchronizedList(List<T> list) {
	return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<T>(list) :
                new SynchronizedList<T>(list));
    }
 

 

通过源码,我们还需要知道ArrayList是否实现了RandomAccess接口:

 

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

 

  查看ArrayList的源码,可以看到它实现了RandomAccess,所以上面的synchronizedList放回的应该是SynchronizedRandomAccessList的实例。接下来看看SynchronizedRandomAccessList这个类的实现:

 

 

static class SynchronizedRandomAccessList<E>
	extends SynchronizedList<E>
	implements RandomAccess {

        SynchronizedRandomAccessList(List<E> list) {
            super(list);
        }

	SynchronizedRandomAccessList(List<E> list, Object mutex) {
            super(list, mutex);
        }

	public List<E> subList(int fromIndex, int toIndex) {
	    synchronized(mutex) {
                return new SynchronizedRandomAccessList<E>(
                    list.subList(fromIndex, toIndex), mutex);
            }
        }

        static final long serialVersionUID = 1530674583602358482L;

        /**
         * Allows instances to be deserialized in pre-1.4 JREs (which do
         * not have SynchronizedRandomAccessList).  SynchronizedList has
         * a readResolve method that inverts this transformation upon
         * deserialization.
         */
        private Object writeReplace() {
            return new SynchronizedList<E>(list);
        }
    }
 

因为SynchronizedRandomAccessList这个类继承自SynchronizedList,而大部分方法都在SynchronizedList中实现了,所以源码中只包含了很少的方法,但是通过subList方法,我们可以看到这里使用的锁对象为mutex对象,而mutex是在SynchronizedCollection类中定义的,所以再看看SynchronizedCollection这个类中关于mutex的定义部分源码:

static class SynchronizedCollection<E> implements Collection<E>, Serializable {
	// use serialVersionUID from JDK 1.2.2 for interoperability
	private static final long serialVersionUID = 3053995032091335093L;

	final Collection<E> c;  // Backing Collection
	final Object mutex;     // Object on which to synchronize

	SynchronizedCollection(Collection<E> c) {
            if (c==null)
                throw new NullPointerException();
	    this.c = c;
            mutex = this;
        }
	SynchronizedCollection(Collection<E> c, Object mutex) {
	    this.c = c;
            this.mutex = mutex;
        }
}

  可以看到mutex就是当前的SynchronizedCollection对象,而SynchronizedRandomAccessList继承自SynchronizedList,SynchronizedList又继承自SynchronizedCollection,所以SynchronizedRandomAccessList中的mutex也就是SynchronizedRandomAccessList的this对象。所以在GoodListHelper中使用的锁list对象,和SynchronizedRandomAccessList内部的锁是一致的,所以它可以实现线程安全性。

 

 

分享到:
评论
1 楼 shanpao1234560 2016-09-07  
这个list不是静态的第一种情况下也会有线程安全的问题么,求指教

相关推荐

    MFC多线程示例工程文件(教程在我发的博客中有)

    在这个MFC多线程示例工程文件中,我们可以深入学习如何在Windows环境下利用MFC来创建和管理多线程程序。多线程是现代软件开发中一个重要的概念,它允许程序同时执行多个任务,从而提高系统的效率和响应性。 首先,...

    彻底明白Java的多线程-线程间的通信.doc

    首先,我们要理解一个虚假的多线程示例。在例1中,创建了两个`TestThread`对象,分别调用`go(0)`和`go(1)`方法。尽管每个方法都在无限循环中调用了`Thread.sleep(100)`,但预期的线程切换并未发生。这是因为`sleep()...

    多线程示例

    "多线程示例java"这个标题和描述指向了Java语言中实现多线程的实践案例。下面将详细讨论多线程的基本概念、Java中的线程创建方式以及多线程同步控制等相关知识点。 1. **线程的基本概念** - **线程**:线程是程序...

    C#多线程基础

    这种操作在单线程环境中是安全的,因为没有其他线程可能同时访问和修改同一个对象。 ```csharp private void Form1_Load(object sender, EventArgs e) { label1.Text = "100"; } ``` #### 知识点三:按钮点击事件...

    VC多线程编程

    通过创建一个新的线程来执行耗时操作,可以让主界面线程继续响应用户的输入事件。这样做的好处是提高了用户体验,使得程序不会因为后台任务而失去响应。 #### 四、Win32 API中的多线程支持 **创建线程:** ```cpp...

    c#多线程顺序打印1-100数字-源码.rar

    本资源"ConsoleApp1"是一个C#实现的多线程示例,它演示了如何在多线程环境中顺序打印1到100的数字。在多线程环境下实现顺序打印看似简单,实则涉及到线程同步和控制的问题,这通常通过各种同步原语来实现。在这个...

    多线程原来是这么简单

    创建一个简单的循环计数函数`CountTime`,并在UI界面上添加一个按钮,单击该按钮时调用此函数。由于在单线程环境中,所有操作都在同一个线程中执行,因此在执行长时间操作时,UI会变得无响应。 ```cpp void ...

    Java线程入门

    一个进程可以包含多个线程,这些线程看似同时执行,但实际上是通过操作系统调度来实现的。 - **线程与进程的区别**:进程是资源分配的基本单位,而线程则是处理器调度的基本单位。同一进程内的线程共享进程的资源...

    Java多线程的小例子——吃包子

    在Java编程中,多线程是一项关键技能,它允许程序同时执行多个任务,提升系统效率。这个名为"Java多线程的小例子——吃包子"的示例...这个示例是一个生动的教学工具,能够帮助初学者更好地理解和运用Java的多线程技术。

    线程_线程_

    这些文件可能是用来实现一个具体的线程示例,比如 `GetHtml.dcu` 可能包含了从网页获取数据的线程代码,而 `libeay32.dll`, `libssl32.dll` 和 `ssleay32.dll` 可能与 SSL/TLS 加密有关,用于安全地通过网络通信。...

    线程的讲解 和详细的哦

    线程是进程的一个实例,每个进程至少包含一个线程,但可以有多个线程,这些线程共享同一进程的资源,如内存空间、打开的文件等。 在多线程编程中,我们关注以下几个核心概念: 1. **线程创建**:程序可以通过创建...

    移动小球/多线程控制

    在计算机科学领域,尤其是操作系统和并发编程中,"移动小球/多线程控制"是一个经典的教学示例,用于演示和理解多线程的概念。在这个实验中,我们看到一个简单的应用,它创建了三个小球并让它们按照特定的轨迹进行...

    第 多线程PPT学习教案.pptx

    在一个单处理器系统中,操作系统通过时间片轮转的方式让多个线程看似同时运行,而在多处理器系统中,每个线程可以被分配到不同的处理器上,真正地并发执行。在Java中,我们有两种方式创建线程: 1. **扩展Thread类*...

    java线程69433.doc

    以下是一个实现Runnable接口的多线程示例: ```java public class DoSomething implements Runnable { private String name; public DoSomething(String name) { this.name = name; } public void run() { for...

    Java线程概念与原理.pdf

    以下是一个使用Runnable接口实现多线程的简单示例: ```java public class Main { public static void main(String[] args) { Thread thread1 = new Thread(new DoSomething("Thread 1")); Thread thread2 = new ...

    java多线程使用知识点问题详解.docx

    以下是一个简单的示例: ```java class Mythread extends Thread { @Override public void run() { for (int i = 0; i ; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ":...

    简单了解C语言中主线程退出对子线程的影响

    1. **主线程被子线程终止:** 在一个示例中,主线程将它的线程ID传递给子线程,子线程随后尝试取消主线程。在这种情况下,虽然主线程会被终止,但由于进程仍在运行,子线程不会立即退出,而是继续执行其循环。如果子...

Global site tag (gtag.js) - Google Analytics