奇怪的Java多线程行为

奇怪的Java多线程行为,java,multithreading,testing,concurrency,locking,Java,Multithreading,Testing,Concurrency,Locking,我在一个由REST API组成的应用程序中工作,该应用程序公开了几个端点,其中一个是加载系统数据的初始化操作,可以在应用程序生命周期中多次调用,删除现有数据并加载新数据。其余端点对数据执行某些操作,并向系统中引入一些附加数据,这些数据在调用初始化端点时将被擦除。考虑到应用程序应该在并发场景下一致地工作,我决定基于可重入写锁实现一个同步器类,其中只有初始化操作将锁定写锁,其余操作将锁定读锁。然而,当涉及到测试我的实现时,我面临着一种奇怪的行为,我想有些事情我不是很了解,不确定是关于我的实现还是测试

我在一个由REST API组成的应用程序中工作,该应用程序公开了几个端点,其中一个是加载系统数据的初始化操作,可以在应用程序生命周期中多次调用,删除现有数据并加载新数据。其余端点对数据执行某些操作,并向系统中引入一些附加数据,这些数据在调用初始化端点时将被擦除。考虑到应用程序应该在并发场景下一致地工作,我决定基于
可重入写锁
实现一个
同步器
类,其中只有初始化操作将锁定写锁,其余操作将锁定读锁。然而,当涉及到测试我的实现时,我面临着一种奇怪的行为,我想有些事情我不是很了解,不确定是关于我的实现还是测试本身

public class LockBasedSynchronizer implements Synchronizer {

    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    @Override
    public void startNonBlocking() {
        lock.readLock().lock();
    }

    @Override
    public void endNonBlocking() {
        lock.readLock().unlock();
    }

    @Override
    public void startBlocking() {
        lock.writeLock().lock();
    }

    @Override
    public void endBlocking() {
        lock.writeLock().unlock();
    }
}


class LockBasedSynchronizerTest {

    private LockBasedSynchronizer synchronizer;

    @BeforeEach
    void setUp() {
        synchronizer = new LockBasedSynchronizer();
    }

    @Test
    void itDoesntBlockNonBlockingOperations() {
        List<String> outputScrapper = new ArrayList<>();
        List<Worker> threads = asList(
                new NonBlockingWorker(synchronizer, outputScrapper, 300, "first"),
                new NonBlockingWorker(synchronizer, outputScrapper, 100, "second"),
                new NonBlockingWorker(synchronizer, outputScrapper, 10, "third")
        );

        threads.parallelStream().forEach(Worker::run); // this way it works
        // threads.forEach(Worker::run); // this way it fails

        assertThat(outputScrapper).containsExactly("third", "second", "first");
    }    

    @AllArgsConstructor
    abstract class Worker extends Thread {
        protected Synchronizer synchronizer;
        private List<String> outputScraper;
        private int delayInMilliseconds;
        private String id;

        protected abstract void lock();

        protected abstract void unlock();

        @Override
        public void run() {
            try {
                System.out.println(format("Starting [%s]", id));
                sleep(delayInMilliseconds);
                lock();
                outputScraper.add(id);
                unlock();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class NonBlockingWorker extends Worker {

        public NonBlockingWorker(Synchronizer synchronizer, List<String> outputScraper, int delayInMilliseconds, String id) {
            super(synchronizer, outputScraper, delayInMilliseconds, id);
        }

        @Override
        protected void lock() {
            synchronizer.startNonBlocking();
        }

        @Override
        protected void unlock() {
            synchronizer.endNonBlocking();
        }
    }
公共类基于锁的同步器实现同步器{
私有最终ReentrantReadWriteLock锁=新的ReentrantReadWriteLock();
@凌驾
public void startNonBlocking(){
lock.readLock().lock();
}
@凌驾
公共void endNonBlocking(){
lock.readLock().unlock();
}
@凌驾
public void startBlocking(){
lock.writeLock().lock();
}
@凌驾
公共void endBlocking(){
lock.writeLock().unlock();
}
}
类LockBasedSynchronizerTest{
专用锁式同步器;
@之前
无效设置(){
同步器=新的基于锁的同步器();
}
@试验
void itDoesntBlockNonBlockingOperations(){
List outputscarser=new ArrayList();
列表线程=asList(
新的非阻塞工人(同步器,输出刮板,300,“第一”),
新的非阻塞工人(同步器,输出刮板,100,“秒”),
新的非阻塞工人(同步器,输出刮板,10,“第三”)
);
threads.parallelStream().forEach(Worker::run);//这样工作
//threads.forEach(Worker::run);//这样会失败
资产(输出报废者)。实际包含(“第三”、“第二”、“第一”);
}    
@AllArgsConstructor
抽象类工作线程扩展{
受保护的同步器;
私有列表输出刮刀;
私有整数延迟毫秒;
私有字符串id;
受保护的抽象空锁();
受保护的抽象无效解锁();
@凌驾
公开募捐{
试一试{
System.out.println(格式(“起始[%s]”,id));
睡眠(延迟毫秒);
锁();
outputScraper.add(id);
解锁();
}捕捉(中断异常e){
e、 printStackTrace();
}
}
}
类NonBlockingWorker扩展了Worker{
公共非阻塞辅助程序(同步器同步器,列表输出刮刀,整数延迟毫秒,字符串id){
超级(同步器,输出刮刀,延迟毫秒,id);
}
@凌驾
受保护的无效锁(){
同步器。启动非阻塞();
}
@凌驾
受保护的无效解锁(){
synchronizer.endNonBlocking();
}
}
正如您所看到的,当我通过并行流并行生成工作线程时,它会按预期工作,但是如果我按顺序运行它们,它们不会按预期工作,并且会按照与运行相同的顺序执行。看起来它们只使用一个线程执行。有人能解释为什么吗

另外,我脑海中关于这个实现的另一个疑问是,如果阻塞操作正在等待写锁,而非阻塞操作没有停止到达,那么会发生什么。它会无限期地等待,直到非阻塞操作停止到达吗


除此之外,如果你能为暴露的问题想出更好的解决方案,建议是非常受欢迎的。

首先,
线程的主要问题是forEach(Worker::run)
实际上并不同时运行任何东西,你只需遍历
Worker
的列表并调用它们的
run()
方法,一个接一个

现在再谈几点

一般来说,不要扩展
线程
。而是创建一些
可运行的
可调用的
或其他一些接口来在线程中运行,通常是通过
执行器
。您并没有试图重新定义
线程
,您只是试图同时运行一些东西

另外,对于您现在拥有的代码,您没有使用任何扩展
线程的方法,因为您只是为
流中的
工作者
应用
run()
方法,而
流中的
工作者
可以是流或列表中任何类的任何方法


我认为最好的方法是让
工作者
实现
可运行
,而不是扩展
线程
,并将这些
工作者
送入
执行器
。或者简单地将
运行()
应用于
并行流()的元素
也可以,我想,只要知道你在做什么。

首先,
线程的主要问题是,forEach(Worker::run)
实际上并不同时运行任何东西,你只是在遍历你的
Worker
列表,然后一个接一个地调用它们的
run()
方法

现在再谈几点

一般来说,不要扩展
线程
。而是创建一些
可运行的
可调用的
或其他一些接口,通常通过
执行器
在线程中运行。您不会试图重新定义
线程
threads.forEach(Worker::start);
Thread.sleep(1000);