多变量之间的java线程安全

多变量之间的java线程安全,java,multithreading,Java,Multithreading,我有一个Metrics类,它应该跟踪我们每秒处理多少事务以及它们需要多长时间。其结构的相关部分如下所示: public class Metrics { AtomicLong sent = new AtomicLong(); AtomicLong totalElapsedMsgTime = new AtomicLong(); AtomicLong sentLastSecond = new AtomicLong(); AtomicLong avgTimeLastSe

我有一个Metrics类,它应该跟踪我们每秒处理多少事务以及它们需要多长时间。其结构的相关部分如下所示:

public class Metrics {
    AtomicLong sent = new AtomicLong();
    AtomicLong totalElapsedMsgTime = new AtomicLong();

    AtomicLong sentLastSecond = new AtomicLong();
    AtomicLong avgTimeLastSecond = new AtomicLong();

    public void outTick(long elapsedMsgTime){
        sent.getAndIncrement();
        totalElapsedMsgTime.getAndAdd(elapsedMsgTime);
    }

    class CalcMetrics extends TimerTask {
       @Override
        public void run() {
            sentLastSecond.set(sent.getAndSet(0));
            long tmpElapsed = totalElapsedMsgTime.getAndSet(0);
            long tmpSent = sentLastSecond.longValue();

            if(tmpSent != 0) {
                avgTimeLastSecond.set(tmpElapsed / tmpSent);
            } else {
                avgTimeLastSecond.set(0);
            }
        }
    }
}
我的问题是outTick函数每秒会被许多不同的线程调用数百次。作为AtomicLong已经确保了每个变量都是线程安全的,并且它们在该函数中不会相互作用,所以我不希望一个锁会使一个对outTick的调用阻止另一个线程对outTick的调用。如果两个不同的线程增加sent变量,然后它们都添加到totalElapsedMsgTime变量中,这是非常好的

然而,一旦它进入CalcMetrics运行方法(每秒只发生一次),它们就会进行交互。我想确保我可以拾取和重置这两个变量,而不在一个Outk调用的中间,或者在拾取一个变量和下一个变量之间出现另一个Outk调用。 有没有办法做到这一点?(我的解释有意义吗?)


编辑:

我选择了詹姆斯建议的读写库。以下是我的结果,对于任何感兴趣的人来说:

public class Metrics {
    AtomicLong numSent = new AtomicLong();
    AtomicLong totalElapsedMsgTime = new AtomicLong();

    long sentLastSecond = 0;
    long avgTimeLastSecond = 0;

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final Lock readLock = readWriteLock.readLock();
    private final Lock writeLock = readWriteLock.writeLock();

    public void outTick(long elapsedMsgTime) {
        readLock.lock();
        try {
            numSent.getAndIncrement();
            totalElapsedMsgTime.getAndAdd(elapsedMsgTime);
        }
        finally
        {
            readLock.unlock();
        }
    }

    class CalcMetrics extends TimerTask {

       @Override
        public void run() {
            long elapsed;

            writeLock.lock();
            try {
                sentLastSecond = numSent.getAndSet(0);
                elapsed = totalElapsedMsgTime.getAndSet(0);
            }
            finally {
                writeLock.unlock();
            }

            if(sentLastSecond != 0) {
                avgTimeLastSecond = (elapsed / sentLastSecond);
            } else {
                avgTimeLastSecond = 0;
            }
        }
    }
}
使用锁()。一旦你实现了锁,你将拥有更好的控制。另一个副作用是,您不再需要使用AtomicLong(尽管您仍然可以);您可以改为使用volatile long,这样会更有效。在示例中,我没有进行更改

基本上只需创建一个新对象:

private Object lock = new Object();
然后,将synchronized关键字与该对象一起用于所有不应与具有相同锁的另一个同步块同时发生的代码。例如:

synchronized(lock)
{
    sent.getAndIncrement();
    totalElapsedMsgTime.getAndAdd(elapsedMsgTime);
}
因此,您的整个程序将如下所示(注意:未测试的代码)

Edit:我编写了一个快速(而且很难看)的效率测试程序,发现当我与锁同步时,总体性能会更好。请注意,前两次运行的结果将被丢弃,因为当Java JIT尚未编译到机器代码的所有代码路径时,计时结果并不代表长期运行时

结果:

  • 带锁:8365ms
  • 原子长:21254ms
代码:

import java.util.concurrent.AtomicLong;
公共班机
{
私有AtomicLong testA_1=新的AtomicLong();
私有AtomicLong testB_1=新的AtomicLong();
私有易失性长测试a_2=0;
私有易失性长测试b_2=0;
私有对象锁=新对象();
私有易失性布尔值a=false;
私有易失性布尔b=false;
私有易失性布尔值c=false;
私有静态布尔useLocks=false;
公共静态void main(字符串参数[])
{
System.out.println(“锁:”);
useLocks=true;
test();
System.out.println(“无锁:”);
useLocks=false;
test();
System.out.println(“锁:”);
useLocks=true;
test();
System.out.println(“无锁:”);
useLocks=false;
test();
}
专用静态空隙试验()
{
最终主管道=新主管道();
新线程()
{
公开募捐
{
对于(int i=0;i<8000000;++i)
主支腿(10);
main.a=真;
}
}.start();
新线程()
{
公开募捐
{
对于(int i=0;i<8000000;++i)
主支腿(10);
main.b=真;
}
}.start();
新线程()
{
公开募捐
{
对于(int i=0;i<8000000;++i)
主支腿(10);
main.c=真;
}
}.start();
long startTime=System.currentTimeMillis();
//好吧,这不是最好的方法,但已经足够好了
而(!main.a | | |!main.b | |!main.c)
{
尝试
{
睡眠(1);
}捕捉(中断异常e)
{
}
}
System.out.println(“运行时间:+(System.currentTimeMillis()-startTime)+“ms”);
System.out.println(“测试A:+main.testA_1+”+main.testA_2);
System.out.println(“测试B:+main.testB_1+”+main.testB_2);
System.out.println();
}
公共无效输出(长时间延迟)
{
如果(!useLocks)
{
testA_1.getAndIncrement();
测试B_1.获取和添加(延时);
}
其他的
{
已同步(锁定)
{
++种皮2;
testB_2+=延迟时间;
}
}
}
}

通常的解决方案是将所有变量包装为一种原子数据类型

class Data
{
    long v1, v2;

    Data add(Data another){ ... }
}

AtomicReference<Data> aData = ...;

public void outTick(long elapsedMsgTime)
{
    Data delta = new Data(1, elapsedMsgTime);

    aData.accumulateAndGet( delta, Data:add );
}    
类数据
{
长v1,v2;
数据添加(另一个数据){…}
}
原子参考数据=。。。;
公共无效输出(长时间延迟)
{
数据增量=新数据(1,elapsedMsgTime);
aData.AccumerateAndGet(增量,数据:添加);
}    
在您的情况下,它可能不会比锁定快很多


java8-中还有另一个有趣的锁。javadoc示例与您的用例非常匹配。基本上,您可以对多个变量进行乐观读取;之后,检查以确保在读取过程中没有执行任何写入操作。在您的情况下,每秒“数百”次写入,乐观的读取大部分会成功

听起来你需要一个读写器锁。(
java.util.concurrent.locks.ReentrantReadWriteLock

您的
outTick()
函数将锁定
ReaderLock
。允许任意数量的线程同时锁定ReaderLock
import java.util.concurrent.atomic.AtomicLong;

public class Main
{
    private AtomicLong testA_1 = new AtomicLong();
    private AtomicLong testB_1 = new AtomicLong();

    private volatile long testA_2 = 0;
    private volatile long testB_2 = 0;

    private Object lock = new Object();

    private volatile boolean a = false;
    private volatile boolean b = false;
    private volatile boolean c = false;

    private static boolean useLocks = false;

    public static void main(String args[])
    {
        System.out.println("Locks:");
        useLocks = true;
        test();

        System.out.println("No Locks:");
        useLocks = false;
        test();

        System.out.println("Locks:");
        useLocks = true;
        test();

        System.out.println("No Locks:");
        useLocks = false;
        test();
    }

    private static void test()
    {
        final Main main = new Main();

        new Thread()
        {
            public void run()
            {
                for (int i = 0; i < 80000000; ++i)
                    main.outTick(10);

                main.a = true;
            }
        }.start();

        new Thread()
        {
            public void run()
            {
                for (int i = 0; i < 80000000; ++i)
                    main.outTick(10);

                main.b = true;
            }
        }.start();

        new Thread()
        {
            public void run()
            {
                for (int i = 0; i < 80000000; ++i)
                    main.outTick(10);

                main.c = true;
            }
        }.start();

        long startTime = System.currentTimeMillis();

        // Okay this isn't the best way to do this, but it's good enough
        while (!main.a || !main.b || !main.c)
        {
            try
            {
                Thread.sleep(1);
            } catch (InterruptedException e)
            {
            }
        }

        System.out.println("Elapsed time: " + (System.currentTimeMillis() - startTime) + "ms");
        System.out.println("Test A: " + main.testA_1 + " " + main.testA_2);
        System.out.println("Test B: " + main.testB_1 + " " + main.testB_2);
        System.out.println();
    }

    public void outTick(long elapsedMsgTime)
    {
        if (!useLocks)
        {
            testA_1.getAndIncrement();
            testB_1.getAndAdd(elapsedMsgTime);
        }
        else
        {
            synchronized (lock)
            {
                ++testA_2;
                testB_2 += elapsedMsgTime;
            }
        }
    }
}
class Data
{
    long v1, v2;

    Data add(Data another){ ... }
}

AtomicReference<Data> aData = ...;

public void outTick(long elapsedMsgTime)
{
    Data delta = new Data(1, elapsedMsgTime);

    aData.accumulateAndGet( delta, Data:add );
}