Java:用于线程共享数据的框架

3
我写了一些多线程的爱好程序,在我的之前的工程/物理学习中也写了一些,因此我认为自己在同步/线程安全和原语方面具有初级以上的知识,这是普通用户在JMM和多个线程等方面感到挑战的地方。
我发现我需要的是没有适当方法将类的实例或静态成员标记为不同线程共享。想想看,我们有访问规则,如private/protected/public和命名getter/setter的约定以及很多其他事情。
但是线程呢?如果我想将变量标记为线程共享并遵循某些规则怎么办?Volatile/Atomic refs可能会完成任务,但有时您确实需要使用互斥锁。当您手动记住要使用某些内容时...您会忘记它:) - 在某个时候。
所以我有一个想法,我看到我不是第一个,我还检查了http://checkthread.org/example-threadsafe.html - 他们似乎有一个相当不错的代码分析器,我以后可能会尝试一下,这有点让我做一些我想做的事情。

但回到最初的问题。假设我们需要比消息传递框架更低级的东西,同时又需要比原始互斥锁更高级的东西...那我们有什么选择呢...嗯...没有?

所以基本上,我创造了一种纯Java超级简单的线程框架,它允许您将类成员声明为共享或非共享...嗯,有点像 :).

以下是如何使用它的示例:

public class SimClient extends AbstractLooper {

    private static final int DEFAULT_HEARTBEAT_TIMEOUT_MILLIS = 2000;
    // Accessed by single threads only
    private final SocketAddress socketAddress;
    private final Parser parser;
    private final Callback cb;
    private final Heart heart;
    private boolean lookingForFirstMsg = true;
    private BufferedInputStream is;
    // May be accessed by several threads (T*)  
    private final Shared<AllThreadsVars> shared = new Shared<>(new AllThreadsVars());

.
.
.
.

    static class AllThreadsVars {

        public boolean connected = false;
        public Socket socket = new Socket();
        public BufferedOutputStream os = null;
        public long lastMessageAt = 0;
    }

要访问标记为线程共享的变量,您必须向Shared对象发送类似于可运行函数的函数对象:

public final void transmit(final byte[] data) {
    shared.run(new SharedRunnable<AllThreadsVars, Object, Object>() {

        @Override
        public Object run(final AllThreadsVars sharedVariable, final Object input) {
            try {
                if (sharedVariable.socket.isConnected() && sharedVariable.os != null) {
                    sharedVariable.os.write(data);
                    sharedVariable.os.flush();
                }
            } catch (final Exception e) { // Disconnected
                setLastMessageAt(0);
            }
            return null;
        }
    }, null);
}

当定义一个共享的可运行对象时,如下:

public interface SharedRunnable<SHARED_TYPE, INPUT, OUTPUT> {
    OUTPUT run(final SHARED_TYPE s, final INPUT input);
}

这是什么意思? 好的,这让我有了帮助(是的,你可以泄露并破坏它,但不太可能),我可以将变量集(而不仅仅是变量)标记为线程共享,并且一旦完成,可以在编译时保证(我不能忘记同步某些方法)。 它还允许我在编译时标准化和执行测试,以查找可能的死锁(尽管目前我只在运行时实现了它,因为使用上述框架在编译时进行可能需要的不仅仅是java编译器)。
基本上,这对我非常有用,我想知道我是否正在重新发明轮子,或者这可能是我不知道的某种反模式。 我真的不知道该问谁。(哦,是的,Shared .run (SharedRunnable r,INPUT input) 就像...

private final <OUTPUT, INPUT> OUTPUT run(final SharedRunnable<SHARED_TYPE, INPUT, OUTPUT> r, final INPUT input) { 
    try {
        lock.lock();
        return r.run(sharedVariable, input);
    } finally {
        lock.unlock();
    }
}

这只是我的个人实验,因此并不算完全完成,但我现在有一个不错的项目正在使用它,它确实帮了很大的忙。


3
我不确定我是否理解了你的例子,但是在处理并发时,共享变量正是你不想要有的。带锁的共享变量将使你变得更慢,浪费更多时间等待锁,总体上反应不及时并陷入死锁。实现真正的并发的最好方式是根本没有共享状态。 - Maurício Linhares
是的,但是在您的应用程序中的某个时刻,如果需要多个线程,则需要一些共享变量,无论是消息队列还是其他什么。我的想法是,有一种方法可以构造任意原子操作(重要的是:在共享标记状态的编译时强制使用它们)。此外,我可以轻松编写死锁分析工具,因为我的所有线程都使用标准化框架。我已经编写了一个相当简化的变体,并且可以检测到代码中一些潜在的死锁点。 - gigurra
2
个人而言,在使用这种方法之前,我会先使用java.util.concurrent包中提供的工具。 - duffymo
如果我能在那里找到有用的东西,当然可以 :). 到目前为止,对于这个特定的应用程序,我还没有找到有用的东西,但也许以后会。不过我在很多其他方面都使用它们。 - gigurra
2个回答

4
你是指像这个一样的东西吗?(可以通过像findbugs这样的工具来强制执行。)

是的,就像那样,谢谢。使用自己的代码,我不需要使用代码分析工具,编译器就足够了,虽然net.jcip.annotations看起来非常好 : )。在决定是否使用它或自己的代码之前,我将需要阅读有关其合同如何工作的信息。 - gigurra

0
如果您有应该共享的值,最好的方法是将其封装在类中。这样,调用者不需要知道您正在使用哪个线程模型。如果您想要知道内部使用的模型,可以阅读源代码,但是调用者不能忘记正确访问ConcurrentMap(例如),因为它的所有方法都是线程安全的。

问题在于有时需要同步执行的任务不仅仅是向预定义的同步集合中添加/删除元素。假设您有一个映射、一个标志和一个时间戳。 public void foo() { if (flag == ...) { modify(map); setTimeStamp(); flag = newValue; } } 我的想法是将所有这些共享变量和整个操作封装为同步的。并强制进行同步,以便我不会忘记在封装类中进行同步。同步方法是不够的,您可能会忘记。 - gigurra
1
如果您实现封装类的新行为或扩展它,除非在编译时强制执行线程安全行为,否则很容易忘记保持线程安全行为。我并不是说我的解决方案是最好的,远非如此,但是既然它已经以非常相似的方式被(请参见jtahlborn和checkthread)完成,我认为它并不完全错误。尽管如此,我可能会尝试重新设计我的应用程序,以便不需要使用这些内容。毕竟,消息传递可能更好。感谢大家的所有意见。我现在需要想办法关闭这个问题 :)。 - gigurra
如果您有一个组件,它使用了许多其他组件,所有这些组件都必须以线程安全的方式使用,您仍然可以使用封装来集中管理所有同步要求。 这简化了代码的维护。公开锁定更具有强大性但危险性更高,这就是为什么许多并发库故意隐藏此锁定的原因。 - Peter Lawrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接