有 Java 编程相关的问题?

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

多线程如何使用静态变量和线程提高Java性能?

为了不深入探讨我的软件应该做什么,让我举一个例子,说明我正在尝试解决的问题,让这个简短而甜蜜

假设我有一个名为X的基类和该类的一个实现,我将调用Y。类Y自然地扩展了基类X。假设我有20个对象,它们将通过一个单独的线程为每个对象实例化类Y,每次实例化都会将一个大文件加载到内存中。这些对象中的一些可能需要使用不同的文件,但为了简单起见,假设它们都需要访问同一个文件

有没有办法定义一个特定的对象(变量),在基类中静态地指向这些文件,这样即使实现类通过20个不同的线程加载了20次,它们都可以共享同一个静态对象,这样文件只需要加载一次

提前谢谢你的帮助


共 (5) 个答案

  1. # 1 楼答案

    您可以从使用ConcurrentHashMap开始

    将映射的键设为字符串,该值应为加载的表示形式所必须的值

    请注意,如果更改加载的文件数据,即使使用ConcurrentHashMap,仍然需要确保线程安全

    在创建对象之前初始化此映射,并将其传递给对象的构造函数

  2. # 2 楼答案

    创建一个单独的对象来存储文件的缓存内容

    通过同步使此对象成为线程安全的,以便多个线程可以访问此对象。在基类X中,放置对此对象的引用。现在,可以用同一个缓存对象实例化类X的多个实例。现在,这要求每个文件只加载一次该对象,并且可以根据需要在任意多个X/Y对象之间共享该对象

    剩下的唯一问题是有一种加载这些文件的方法。这个问题的解决方案将取决于应用程序和这些文件的结构,但我将提供一个可能的解决方案

    创建一个factory类,该类将创建此新类型的对象。此工厂将在其自己的线程上运行,所有加载的文件都将通过此工厂加载。创建一个可以从此工厂请求文件的接口。工厂保留对所有已加载文件的引用,因此如果已加载,它可以立即返回引用。未加载时,使用与此文件相关的工厂中存储的占位符对象上的Object.wait()阻止进行调用的线程。工厂加载完文件后,在该文件的占位符对象上调用Object.notifyAll(),这将唤醒每个线程,这些方法将返回对加载文件的引用

    完成后,需要文件的每个线程都可以调用工厂中的方法来获取文件对象。这个线程现在将阻塞,直到文件对象被加载,然后函数将返回。只要这是正常的,这似乎是应该的,因为这些线程将等待文件加载,那么这个解决方案应该工作得很好

  3. # 3 楼答案

    如果您提前知道该文件,则可以在静态初始值设定项块中打开并加载该文件,并将内容存储在静态数据成员中。然后,该类的所有实例都可以访问该内容,而不管当前哪个线程正在访问实例对象

    // In the base class
    protected static final String fileContents;
    
    static {
        fileContents = readStuffFromFile();
    }
    
  4. # 4 楼答案

    一个非静态的内部类将满足您的所有愿望:

    public class Foo {
      protected String member;
      public Foo(String member) {
        this.member = member;
      }
      public class Bar {
        protected String member;
        public Bar(String member) {
          this.member = member;
        }
        public void show() {
          System.out.println("this.member: " + this.member + "; Foo.this.member: " + Foo.this.member);
        }
      }
      public static void main(String[] args) throws javax.mail.MessagingException, java.io.IOException {
        Foo foo_a = new Foo("a");
        Foo foo_b = new Foo("b");
        Bar bar_a1 = foo_a.new Bar("1");
        Bar bar_a2 = foo_a.new Bar("2");
        Bar bar_b1 = foo_b.new Bar("1");
        Bar bar_b2 = foo_b.new Bar("2");
        bar_a1.show();
        bar_a2.show();
        bar_b1.show();
        bar_b2.show();
      }
    }
    

    好吧,好吧,好吧(-2票后):

    首先,上述解决方案都没有解决原始问题的一部分,即可能没有一个文件被所有对象共享。一组对象可能需要共享文件A,另一组对象可能需要共享文件B,以此类推。上面的内部类解决方案正是为了满足这个需求。每个文件/组实例化一次外部类,并从同一外部对象实例化组的内部对象

    其次,静态是一个糟糕的选择:很可能需要在运行时而不是在程序启动时指定文件。上面的外部/内部类结构正好解决了这个问题。只要需要,就可以实例化外部类。不需要静态初始化(也不需要任何复杂的延迟静态初始化方案)

    第三,线程偏执症根本不是这个问题(或这个解决方案)中的一个问题。很明显,该文件是只读的,因此是不可变的,因此在这个问题上进行所有并发操作只会降低优雅解决方案的效果

    最后,说到优雅,这是,而且可能是唯一的一个

    此更新主要针对新来查看该线程的人,因为此线程中的负面投票者可能会将其设置为-5

  5. # 5 楼答案

    1. 那个文件是只读的吗
    2. 这是一大串数据吗

    如果是这样,那么a String只需将它设为protected static final String,它是线程安全的。如果它是可变的,那么在你的未来,你会受到全世界的伤害

    如果它是一个二进制文件,并且只能以只读方式使用,那么您可能可以用byte[]代替String执行相同的操作,并确保不让任何内容更改数组中的字节。更好的方法是以只读方式实现一些StreamReader接口

    使线程安全的最简单和最安全的方法是使其不可变。final关键字使引用不可变,但不会使它指向的对象不可变。由于String是不可变的,因此final也会使引用不可变,您可以继续。如果您需要在所有线程之间共享更改的可变性,java.util.concurrent包将是您的朋友

    如果您使用变量protected static final,那么子类的所有实例都将看到数据,而不管它们在哪个执行线程上