我有一个问题,我们通常如何处理线程冲突?例如,如果您在网站上销售图书,并且只有一本图书可用。情况是有2个客户(线程)想要购买该书,而且两者都在同一时间下订单,那么我们如何处理呢?我们如何避免它?
更具体地说,我有一个servlet接受客户端的请求,我的意思是因为我们不能只生成doPost方法synchronized
,因为这可能导致每个请求(线程)的性能问题进来了。
class Server{
private Library library = Library.getInstance(); //singleton for all books
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
synchronized(library){
Book book = library.get(request.getParameter("book_name"));
if(book != null){
int copies = book.getAvailable();
book.setAvailable(copies-1);
placeOrder();
}
}
}
}
答案 0 :(得分:2)
处理这类问题的最简单方法是使用数据库来处理并发。任何主流数据库都有并发支持,其背后有多年的开发工作。利用数据库中的ACID支持来处理这些标准业务问题相对容易。
更新:
正如@MartinJames在下面的评论中所说的那样,我不说“数据库是所有事物并发的解决方案”。但是,对于“更长”的生存交互,您已经在更新数据库并且需要以“事务”性质控制多个数据位,数据库是处理这种并发级别的理想位置。
此外,各种其他评论显示了如何使用java级别并发在内存中管理此数据。但是,在这种情况下,当您需要“向外扩展”(即添加另一个应用服务器)时,您已经死了,因为Java级别同步将不再起作用。但是,数据库解决方案仍然有效。这就是为什么EJB的东西不鼓励直接使用并发/同步。相反,EJB / JPA世界中的大多数同步都在数据库中处理。
答案 1 :(得分:0)
有多种方式。就Java中的服务器端而言,最简单的是利用java监视器,即使用synchronized关键字来保护共享变量。更好的方法是使用java.util.concurrent类来实现生产者/消费者范例,例如,使用BlockingQueue和执行器。
这是一个使用util.concurrent类的简单示例。请注意,Data类是您的自定义类。
// This can go inside a main method or your controller class.
private BlockingQueue<Data> sharedQueue = new ArrayBlockingQueue<Chunk>(BUFFER_SIZE);
for (int i = 0; i < NUMBER_OF_PRODUCERS; i++) {
(new Thread(new Producer(sharedQueue, i))).start();
}
for (int i = 0; i < NUMBER_OF_CONSUMERS; i++) {
(new Thread(new Consumer(sharedQueue, i))).start();
}
public class Producer implements Runnable {
private final BlockingQueue<Data> sharedQueue;
public Producer(BlockingQueue<Data> sharedQueue, int id) {
this.sharedQueue = sharedQueue;
}
public void run() {
try {
while (true) {
sharedQueue.put(produce());
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
private Data produce() { return new Data(); }
}
public class Consumer implements Runnable {
private final BlockingQueue<Data> sharedQueue;
public Consumer(BlockingQueue<Data> sharedQueue, int id) {
this.sharedQueue = sharedQueue;
}
public void run() {
try {
while (true) {
consume(sharedQueue.take());
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
void consume(Data c) { System.out.println(c); }
}
答案 2 :(得分:0)
你需要同步。如果一个客户已经开始在一个帖子中购买该书,那么想要购买该书的另一个客户应该等到第一个完成时购买或放弃该想法(回滚)。向最终用户指示的一个选项是显示正在使用的消息,只是等待或类似的事情。从实现角度来看,您需要了解java或数据库中的并发性。
答案 3 :(得分:0)
对您已有的内容进行微调。这将有所帮助,因为您不会在整个地点订单流程中进行同步...
class Server{
private Library library = Library.getInstance(); //singleton for all books
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
boolean isOkToPlaceOrder = false;
synchronized(library){
Book book = library.get(request.getParameter("book_name"));
if (book != null) {
copies = book.getAvailable();
book.setAvailable(copies-1);
isOkToPlaceOrder = true;
}
}
if (isOkToPlaceOrder) placeOrder();
}
}
答案 4 :(得分:0)
如果所有library.get调用都返回相同的对象(对于同一本书),则可以使用该对象同步代码。会比整个图书馆好。
class Server{
private Library library = Library.getInstance(); //singleton for all books
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
boolean isOkToPlaceOrder = false;
Book book = library.get(request.getParameter("book_name"));
synchronized(book){
if (book != null) {
copies = book.getAvailable();
book.setAvailable(copies-1);
isOkToPlaceOrder = true;
}
}
if (isOkToPlaceOrder) placeOrder();
}
}
另一方面,另一个想法是将代码与唯一的String实例同步(使用intern方法)。
Book book = library.get(request.getParameter("book_name"));
synchronized(book.getISBNString().intern()){
// reload changes made by other thread
book = library.get(request.getParameter("book_name"));
if (book != null) {
copies = book.getAvailable();
// if copies < 1 --> Error message
book.setAvailable(copies-1);
// save changes in synch
....
placeOrder();
}
}
http://docs.oracle.com/javase/6/docs/api/java/lang/String.html#intern() http://weblogs.java.net/blog/enicholas/archive/2006/06/all_about_inter.html