2016-12-26 92 views
0

因此,我正在研究java并发性,试图创建不好的并发示例,观察它们失败并修复它们。关于java并发结果

但代码似乎从未打破......我在这里错过了什么?

我有一个“共享对象”,是我的HotelWithMaximum实例。据我所知,这个类不是线程安全的:

package playground.concurrent; 

import java.util.ArrayList; 
import java.util.List; 

public class HotelWithMaximum { 

    private static final int MAXIMUM = 20; 

    private List<String> visitors = new ArrayList<String>(); 

    public void register(IsVisitor visitor) { 
     System.out.println("Registering : " + visitor.getId()); 
     System.out.println("Amount of visitors atm: " + visitors.size()); 

     if(visitors.size() < MAXIMUM) { 
      //At some point, I do expect a thread to be interfering here where the condition is actually evaluated to 
      //true, but some other thread interfered, adds another visitor, causing the previous thread to go over the limit 
      System.out.println("REGISTERING ---------------------------------------------------------------------"); 
      //The interference might also happen here i guess... 
      visitors.add(visitor.getId()); 
     } 
     else{ 
      System.out.println("We cant register anymore, we have reached our limit! " + visitors.size()); 
     } 
    } 

    public int getAmountOfRegisteredVisitors() { 
     return visitors.size(); 
    } 

    public void printVisitors() { 
     for(String visitor: visitors) { 
      System.out.println(visitors.indexOf(visitor) + " - " + visitor); 
     } 
    } 
} 

游客是“的Runnable”(它们实现从Runnable接口扩展我接口IsVisitor),他们都是这样实现的:

package playground.concurrent.runnables; 

import playground.concurrent.HotelWithMaximum; 
import playground.concurrent.IsVisitor; 

public class MaxHotelVisitor implements IsVisitor{ 

    private final String id; 
    private final HotelWithMaximum hotel; 

    public MaxHotelVisitor(String id, HotelWithMaximum hotel) { 
     this.hotel = hotel; 
     this.id = id; 
    } 

    public void run() { 
     System.out.println(String.format("My name is %s and I am trying to register...", id)); 
     hotel.register(this); 
    } 

    public String getId() { 
     return this.id; 
    } 

} 

然后,使这一切运行在一个例子,我在不同的类下面的代码:

public static void executeMaxHotelExample() { 
     ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(6); 
     HotelWithMaximum hotel = new HotelWithMaximum(); 

     for(int i = 0; i<100; i++) { 
      executor.execute(new MaxHotelVisitor("MaxHotelVisitor-" + i, hotel)); 
     } 

     executor.shutdown(); 

     try{ 
      boolean finished = executor.awaitTermination(30, TimeUnit.SECONDS); 

      if(finished) { 
       System.out.println("FINISHED WITH THE MAX HOTEL VISITORS EXAMPLE"); 
       hotel.printVisitors(); 
      } 
     } 
     catch(InterruptedException ie) { 
      System.out.println("Something interrupted me...."); 
     } 
    } 

    public static void main(String[] args) { 
     executeMaxHotelExample(); 
    } 

现在,我缺少什么?为什么这似乎永远不会失败?酒店类不是线程安全的,对吗?在这个例子中唯一使线程安全的线程是安全的(因为没有其他代码会在酒店类中使用线程不安全列表),我应该使注册方法“同步”,对吧? 的结果“printVisitors()”中的主要方法方法,始终是这样的:

FINISHED WITH THE MAX HOTEL VISITORS EXAMPLE 
0 - MaxHotelVisitor-0 
1 - MaxHotelVisitor-6 
2 - MaxHotelVisitor-7 
3 - MaxHotelVisitor-8 
4 - MaxHotelVisitor-9 
5 - MaxHotelVisitor-10 
6 - MaxHotelVisitor-11 
7 - MaxHotelVisitor-12 
8 - MaxHotelVisitor-13 
9 - MaxHotelVisitor-14 
10 - MaxHotelVisitor-15 
11 - MaxHotelVisitor-16 
12 - MaxHotelVisitor-17 
13 - MaxHotelVisitor-18 
14 - MaxHotelVisitor-19 
15 - MaxHotelVisitor-20 
16 - MaxHotelVisitor-21 
17 - MaxHotelVisitor-22 
18 - MaxHotelVisitor-23 
19 - MaxHotelVisitor-24 

有nevere更多然后在列表中20人......我觉得很奇怪......

+0

首先,小样本大小可能不太可能表现出并发性问题。也许运行同样的expoument一百万次?其次,您可能希望在打印到sysout'REGISTERING'的行之后添加一个'Thread.sleep(x)'或'Thread。yield()'试图鼓励这个问题出现在这一点上。 – vikingsteve

+0

嗯,这可能确实是一种可能性,我会研究这一点。我有一些令人不安的结果与另一个测试相当快,虽然... –

回答

1

ThreadPoolExecutorjava.util.concurrent

java.util.concurrent包的Java并发工具框架是包含用于在Java应用程序处理并发线程安全类型库

所以ThreadPoolExecutor走的是syncorinous处理的护理

请注意:ThreadPoolExecutor使用BlockingQueue管理其作业队列 java.util.concurrent.BlockingQueue是要求所有的实现是线程安全的接口。

根据我的理解,java.util.concurrent的主要目标之一是,您可以在很大程度上运行而无需使用难以使用的java的低级并发基元synchronized, volatile, wait(), notify(), and notifyAll()

还要注意的是ThreadPoolExecutor实现ExecutorService这并不能保证所有的实现是线程安全的,但根据文档 http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html

操作在一个线程之前提交一个Runnable或赎回的任务到一个ExecutorService 发生在之前发生的任何动作,这反过来发生 - 在通过Future.get()检索结果之前。

happen-before说明:

的Java™语言规范定义上存储器操作之前发生关系如读取和共享变量的写入。由一个线程写入的结果保证对另一个线程的读取可见,如果发生写入操作,则在读取操作之前,只读取

换句话说 - 通常不是线程安全的。 BUT

的java.util.concurrent和所有类的方法及其子包这些保证扩展到更高级同步

+0

嗯,我真的很努力与文件的报价。我从中得到的是执行者服务以某种方式保证事前合同......但是如何? –

+0

@ ocket-san查看更新的答案 - 我希望这能更好地澄清 – dreamer

+0

感谢您的更新!我会深入调查,因为我仍然认为我理解错了。但我肯定会回到你的答案! –

1

该代码似乎从未打破...我在这里错过了什么?

Java语言规范为实现者提供了很大的灵活性,可以最有效地使用任何给定的多处理器体系结构。

如果您遵守编写“安全”多线程代码的规则,那么应该保证一个正确实现的JVM将以您期望的方式运行您的程序。但如果你打破的规则,那不是保证你的程序会行事不端。

通过测试发现并发错误是一个难题。一个非“线程安全”程序可能在一个平台(即架构/操作系统/ JVM组合)上100%的时间内工作,它可能在其他某个平台上总是失败,并且它在某个第三平台上的性能可能取决于其他平台进程正在运行,在一天的时间内,或者只能在其他变量上运行。

+0

感谢您的回答。所以基本上,我知道它不是线程安全的,但通过做测试很难证明它......昨天晚上我开始阅读Brian Goetz和其他人的“Java Concurrency in Practice”,他们基本上在这本书的开始。它是一个耻辱,但我喜欢证明知识:-) –

+0

@ ocket-san,也许你会成为一名计算机科学家......你可以假设,没有两个内存操作在同一时间发生,当你推理多线程代码:因此,多线程程序的每个可能的执行都等同于以某种顺序执行所有内存操作的单个线程(称为_serialization_)。你正在寻找的是一种技术来证明某些程序的所有可能的序列化是否导致正确的结果。但是,大多数真实程序的序列化数量是天文数字。祝你好运! –

+0

:-)感谢您的见解。我只会接受这样一个事实,即并发编程主要是“按规则编程”,并且很难说服那些不了解规则的人说他们写的是不正确的。我还应该更多地研究规则。再次感谢! –

1

你说得对。

当您同时使用更多的执行程序时,您可以重现并发性问题,如Executors.newFixedThreadPool(100);而不是6.然后,更多的线程将同时尝试它,并且概率更高。由于竞赛状态/溢出只能发生一次,因此您必须多运行一次主队才能获得更多访问者。

此外,您需要在您期待“干扰”的两个地方添加Thread.yield(),以使其更容易发生。如果执行非常短/快,则不会有任务切换,并且执行将是原子的(但不能保证)。

您也可以使用ThreadWeaver编写代码,该代码对字节代码进行操作(增加收益),以使这些问题更可能发生。

这两个变化,我不时在酒店得到30个和更多的游客。我有2x2 CPU。

+0

非常感谢您的回答!你让我兴奋,试试看。当我拥有时,我会和你一样得到时髦的结果,除了你的爱人! –