有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java:关于多线程的一些特定于案例的问题

问题1 如果我们考虑下面的类:

public class Test {
    public static LinkedList<String> list;
}

如何使获取/设置变量“list”的线程安全

我想我可以这样做:

public class Test {
    private static LinkedList<String> list;

    public static synchronized LinkedList<String> getList() {
        return new LinkedList<>(list);
    }

    public static synchronized void setList(LinkedList<String> data) {
        list = new LinkedList<>(data);
    }
}

问题2

但这是线程安全的吗?我是否每次都必须初始化一个新列表,以确保其他副本不会影响变量

问题3

如果我们考虑这个问题:

public class Test {
    private static LinkedList<String> list;

    public static synchronized void ManipulateList() {
        // do stuff to 'list'
    }

    public static synchronized void ChangeList() {
        // do more stuff to 'list'
    }
}

其中,“操纵列表”和“变更列表”两种方法都可以向同一列表添加或删除变量

这个线安全吗?这是否意味着,如果线程1正在访问“操纵列表”,那么在线程1完成访问“操纵列表”之前,线程2无法访问“变更列表”

我只是不确定我是否正确理解了这些影响


共 (3) 个答案

  1. # 1 楼答案

    任何子类化您的测试类都可能破坏您的同步方案,因为子类可以直接访问列表,而无需方法同步-通过子类化您的测试类或通过反射

    public class MyTestClass extends Test {
    
        // blah...
    
        public static changeTheList() {
    
            this.list.add("Bypasses synchronization through direct access to the list.");
    
        }
    
    }
    

    更好的同步解决方案是使用同步包装器初始化列表,如下所示:

    public class Test {
    
        private static LinkedList<String> list = Collections.synchronizedList(new LinkedList<>());
    
        public static synchronized LinkedList<String> getList() {
    
            return list;
    
        }
    
        public static synchronized void setList(LinkedList<String> newList) {
    
            list = newList;
    
        }
    
    }
    

    在第二个代码段中,您现在可以安全地对测试类进行子类化,并以线程安全的方式访问列表,因为列表本身是同步的

    您还可以选择将测试类标记为final,但仍然需要修复实现(在getter和setter中重新初始化列表,这不是一个好主意)

    另外,我可能建议您查看一些有关同步的教程,并提出一些建议:

    https://www.baeldung.com/java-synchronized-collections

    https://howtodoinjava.com/java/collections/arraylist/synchronize-arraylist/

  2. # 2 楼答案

    问题1

    public static LinkedList<String> list;
    

    How would you make getting/setting thread-safe for the variable 'list'?

    避免全局[可变]状态。把它扔掉

    问题2

    public class Test {
        private static LinkedList<String> list;
    
        public static synchronized LinkedList<String> getList() {
            return new LinkedList<>(list);
        }
    
        public static synchronized void setList(LinkedList<String> data) {
            list = new LinkedList<>(data);
        }
    }
    

    But how thread-safe is this? Would I have to initialize a new list each time to ensure other copies don't affect the variable?

    (我将假定是指Test.list而不是传入的data,由于Java集合库的缺陷,传入的data本身是可变的

    因此,您总是使用相同的锁访问列表。当你与外界打交道时,你总是在复制清单。列表的成员是不可变的,因此不需要任何深度复制。一切都好

    该方法在不涉及变量的昂贵操作上保持锁定,因此我们应该在这里做得更好

    public static synchronized LinkedList<String> getList() {
        // The `LinkedList` list points to is never mutated after set.
        LinkedList<String> local;
        synchronized (Test.class) {
            local = list;
        }
        return new LinkedList<>(local);
    }
    
    public static void setList(LinkedList<String> data) {
        LinkedList<String> local = new LinkedList<>(data);
        synchronized (Test.class) {
            list = local;
        }
    }
    

    理论上,即使没有更改,锁也不必为整个副本持续保持。因为它是一个公共锁对象(但是很顽皮,但是很普通)data可以wait在它上面暂时释放锁。显然,这并不重要,但在现实世界中,它可能会导致陌生

    稍微隐晦一点的是list可以做成volatile,锁可以省略

    问题3

        private static LinkedList<String> list;
    
        public static synchronized void ManipulateList() {
            // do stuff to 'list'
        }
    
        public static synchronized void ChangeList() {
            // do more stuff to 'list'
        }
    

    Is this thread-safe? Does this mean that if thread 1 is accessing 'ManipulateList' then thread 2 is not able to access 'ChangeList' until thread 1 finishes accessing 'ManipulateList'?

    对。除此之外,可能存在wait,其中一个方法可能会间接调用另一个方法

    一般注释

    • 删除全局[可变]状态
    • 尽量避免共享可变对象(保持共享对象不变,不共享可变对象)
    • 减少锁定的代码量和时间
    • 复制可变的输入和输出
  3. # 3 楼答案

    I guess I could do something like this:

    这不是线程安全的

    具体而言,setter:

    public static synchronized void setList(LinkedList<String> data) {
        list = new LinkedList<>(data);
    }
    

    不强制在setList方法期间以独占方式访问data。因此,其他线程可以在隐式迭代期间修改列表


    关于列表的更新,问题3中的代码没有问题,因为方法是同步的,这意味着列表是互斥访问的,并且一个方法调用的效果对后续调用是可见的

    但这并不是完全安全的,因为恶意代码可以获取(并保持)Test的监视器,这可能导致死锁

    您可以通过使用只能在类内部获取的显式监控器来解决此特定问题:

    class Test {
      private final Object obj = new Object();
    
      public static void ManipulateList() {
        synchronized (obj) { ... }
      }
    
      public static void ChangeList() {
        synchronized (obj) { ... }
      }
    }