所以我通过尝试创建错误的并发示例,观察它们失败然后修复它们来研究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);
}
}
}
访客是&#39; Runnables&#39; (它们实现了我从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();
}
现在,我错过了什么?为什么这似乎永远不会失败?酒店课程不是线程安全的,对吧?唯一能让它足够的&#39;这个例子的线程安全(因为没有其他代码搞乱酒店类中的线程不安全List),我应该只使用注册方法&#34; synchronized&#34;,对吧? &#34; printVisitors()&#34;的结果main方法中的方法总是如下所示:
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
列表中有超过20名访客...我发现这很奇怪......
答案 0 :(得分:1)
ThreadPoolExecutor
来自java.util.concurrent
包
java.util.concurrent包中的Java Concurrency Utilities框架是一个包含用于处理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
在向ExecutorService提交Runnable或Callable任务之前的一个线程中的操作在之前发生该任务所采取的任何操作,而这些操作又发生在通过Future检索结果之前。得到()。
happen-before
的说明:
Java™语言规范定义了内存操作的发生前关系,例如共享变量的读写。如果在读操作之前发生写操作,则一个线程写入的结果保证对另一个线程仅的读取可见。
换句话说 - 通常不是线程安全的。但是
java.util.concurrent 中所有类的方法及其子包将这些保证扩展为更高级别的同步。
答案 1 :(得分:1)
代码似乎永远不会破坏......我在这里缺少什么?
Java语言规范为实现者提供了很大的余地,可以最有效地使用任何给定的多处理器体系结构。
如果你遵守写作规则&#34; safe&#34;多线程代码,然后应该保证正确实现的JVM将以您期望的方式运行您的程序。但是,如果您打破规则,不确保您的程序行为不端。
通过测试查找并发错误是一个难题。非线程安全&#34;程序可能在一个平台(即架构/ OS / JVM组合)上100%的时间工作,它可能总是在某个其他平台上失败,并且它在某个第三平台上的性能可能取决于其他进程正在运行的时间当天或者,你可以猜到的其他变量。
答案 2 :(得分:1)
你是对的。
当您同时使用更多执行程序时,可以重现并发问题,比如Executors.newFixedThreadPool(100);
而不是6.然后更多线程会同时尝试它并且概率更高。因为竞争条件/溢出只能发生一次,所以你必须运行你的主要时间以获得更多访问者。
此外,您需要在预期“干扰”的两个地方添加Thread.yield()
,以使其更有可能发生。如果执行非常短/快,则不会有任务切换,执行将是原子的(但不能保证)。
你也可以使用ThreadWeaver编写代码,它会进行字节代码操作(增加产量)以使这类问题更有可能发生。
随着这两项变化,我不时会在酒店吸引30多名访客。我有2x2 CPU。