Java,这个线程安全吗

标签 java multithreading

我是并发领域的初学者,所以如果有人检查一下这段代码,我会很高兴。

我有一个列表,我不是锁定整个列表,而是锁定第一个元素,然后锁定第二个元素,移动到第二个元素,释放第一个元素的锁定,依此类推。

public class List {
    private Node head;
    private Random random = new Random();

    public List(int o) {
        this.head = new Node(o);
    }

    public void addToList(int o) {      
        Node actual = head;
        actual.setLock();
        Node next;
        while( (next = actual.next() )!= null) {
            next.setLock();
            System.out.println(actual.getElement());
            actual.unlock();
            actual = next;

        }
        Node newNode = new Node(random.nextInt());
        newNode.setLock();
        actual.setNext(newNode);
        actual.unlock();
        newNode.unlock();
    }

    public void printList() {
        Node actual = head;
        actual.setLock();
        Node next;
        while( (next = actual.next() )!= null) {
            next.setLock();
            System.out.println(actual.getElement());
            actual.unlock();
            actual = next;

        }
        System.out.println(actual.getElement());
        actual.unlock();
    }
}

和节点类

public class Node {
    private final Lock lock = new ReentrantLock();
    private final int element;
    private Node next = null;

    public Node(int element) {
        this.element = element;
    }

    public int getElement() {
       try {
           lock.lock();
           return element;
       } finally {
           lock.unlock();
       }
    }

    public Node next() {
        try {
            lock.lock();
            return next;
        } finally {
            lock.unlock();
        }
    }

    public void setLock() {
        lock.lock();
    }

    public void unlock() {
        lock.unlock();
    }

    public void setNext(Node node) {
        try {
            lock.lock();
            next = node;
        } finally {
            lock.unlock();
        }
    }
}

我知道我可以使用库中的列表,但我想了解它是如何工作的

最佳答案

就 Java 内存模型而言,它是安全的,但很容易出错。管理这么多事情,很可能会犯错误。我已经发现您只是有时使用 finally...unlock 习惯用法(我认为是因为将其放入循环中很复杂),这就是我的意思。

获取如此多的锁也是一种相对昂贵的并发编程方式。而且内存效率也不高,因为每个节点都需要一个额外的对象。

还有一件小事,即 Node 元素没有在自己的锁下分配:

public Node(int element) {
    this.element = element;
}

锁定在前一个节点上,但这意味着从技术上讲,需要遍历列表以确保分配的元素可见。

锁定相对于内存一致性的工作方式是,当获取锁时,保证可见的操作只是在持有该特定锁时所采取的操作(以及导致这些操作的操作)。

现在,当您创建一个新节点时,它的作用是:

actual.lock();
Node newNode = new Node(random.nextInt());
...
actual.unlock();

这意味着在 newNode 中分配元素时持有的锁是对 actual 的锁。因此,为了保证另一个线程看到 newNode 的正确值,它需要获取 actual 的锁。 (如果遍历列表就会发生这种情况。)

虽然实际上再次查看它,但我看到 element 被声明为最终的,因此应该在没有锁的情况下保证它的可见性。此外,您还可以在构造 newNode 后立即获取和释放它的锁。但这是一种需要注意的复杂交互。

如果 element 不是最终的,你只需这样做:

public Node(int element) {
    lock.lock();
    try {
        this.element = element;
    } finally {
        lock.unlock();
    }
}

管理如此多的锁容易出错是我想指出的更重要的缺点。目前您只有 2 个方法,但例如 java.util.List 大约有 20 个。如果您要完全实现 List,需要编写大量代码。

关于Java,这个线程安全吗,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22974230/

相关文章:

java - 在等待 "long"时间时重用 tomcat 线程

c++ - 我的双重检查锁定模式实现是否正确?

c++ - fscanf 不会阻塞调用线程

java - 使用子类作为返回类型时,抽象方法不被视为已实现

c# - 将对象从 Android 客户端应用程序发送到 C# 服务器应用程序

java - Java中如何确定字符串文件位置路径

java - JTable 标题未设置

java - 尝试让 java 套接字程序工作,但得到 "java.net.BindException: Address already in use 6666 "

Java 不释放 oracle 游标

java - 为什么我们不需要一个惰性初始化 getter 来同步(持有者习惯用法)?