【问题标题】:Java Multithreading exampleJava 多线程示例
【发布时间】:2017-11-05 13:50:41
【问题描述】:

我是一名 Java 学生,这是我实现 StackExchange 的尝试(有一个 pusher 线程和一个 popper 线程、一个堆栈资源和两个用于堆栈内容和时间传递的控制线程)。

我希望有人可以评论我的代码以进行改进或错误\不良做法,即使代码似乎可以工作。

这个程序的主要目的是弄清楚如何在多线程环境中控制资源访问。

我担心使用 ScheduledThreadPoolExecutor 而不是锁定(堆栈),以及我在 StackExchange 类方法中使用同步(用于访问堆栈),我想生成处理动态锁定资源的空闲线程.有什么建议吗?

注意:“幻数和 syso 的格式对于测试 porpuses 可能很糟糕

代码在这里:

package examples;

import java.util.Random;

import java.util.Stack;

import java.util.concurrent.ScheduledThreadPoolExecutor;

import java.util.concurrent.TimeUnit;

import javax.swing.JOptionPane;


public class StackExchange {

    /*
     * Two Threads playing with a stack, a timer and a controller for the stack that permits to exit
     * */
    public class Popper implements Runnable
    {
        StackExchange sEx;
        public Popper(StackExchange sex)
        {
            this.sEx=sex;

        }
        @Override
        public void run() {
            System.out.println("Popper: popping!\t"+sEx.getPeek());
            sEx.callTheStack(this, null);
        }
    }
    public class Pusher implements Runnable
    {
        StackExchange sEx;
        public Pusher(StackExchange sex)
        {
            sEx=sex;        
        }
        @Override
        public void run() {
            System.out.println("Pusher: pushing!\t");
            sEx.callTheStack(this, "Hi!");
        }
    }
    public class StackController implements Runnable
    {
        private Stack<String> theStack;
        public int waiting=5;
        public StackController(Stack<String> theStack, String name) {
            this.theStack =  theStack;
            Thread.currentThread().setName(name);
        }
        @Override
        public void run() 
        {
            Random rand = new Random();
            waiting = rand.nextInt(10);
            StringBuilder buffer = new StringBuilder();
            int i=0;
            for(String string: theStack)
            {
                buffer.append(string+"\n");
                i++;
            }
            buffer.append("\nFound "+i+" elements\nIWillWait4:\t"+waiting);

            System.out.println("\t\t\t\t\t\t\t\t"+Thread.currentThread().getName().toString()+" Says:" + buffer.toString());
            if(i>1)
            {
                System.out.println("ERRER");
                System.exit(0);
            }
            if(i==1 && JOptionPane.showConfirmDialog(null,  "found 1 element\nWannaStop?")==0)
                System.exit(0);


        }
    }
    public class Timer implements Runnable{

        @Override
        public void run() {
            StackExchange.time++;
            System.out.println("Time Passed:\t"+StackExchange.time+" seconds");
        }

    }
    /*
     * implementation of the StackExchange class
     * */
    private Popper popper;
    private Pusher pusher;  
    private StackController stackController;
    private StackController secondSC;
    private Timer timer;

    static int time=0;

    private Stack<String> stack;

    public StackExchange()
    {
        timer = new Timer();
        stack = new Stack<String>();
        pusher = new Pusher(this);
        popper = new Popper(this);
        stackController = new StackController(this.getStack(), "FirstStackController");
    }
    public static void main(String[] args) {
        StackExchange sex = new StackExchange();
        sex.start();
        System.out.println("Num of Threads:"+Thread.activeCount());
    }
    public void start()
    {
        ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(5);
        exec.scheduleAtFixedRate(timer, 0, 1, TimeUnit.SECONDS);
        exec.scheduleAtFixedRate(pusher, 0, 2, TimeUnit.SECONDS);
        exec.scheduleAtFixedRate(popper, 1, 2, TimeUnit.SECONDS);
        exec.scheduleAtFixedRate(stackController, 0, stackController.waiting, TimeUnit.SECONDS);        
    }
    public Stack<String >getStack()
    {
        return this.stack;
    }
    public void callTheStack(Object caller, String pushedString)
    {
        synchronized(this)
        {
            if(caller instanceof Popper)
                stack.pop();

            else if(caller instanceof Pusher)
                stack.push(pushedString);
        }
    }
    public String getPeek()
    {
        synchronized(this)
        {
            return stack.peek();
        }
    }
}

【问题讨论】:

  • 你应该把你的问题放在Code Review
  • 我投票决定将此问题作为离题结束,因为我要求进行代码审查。

标签: java multithreading runnable


【解决方案1】:

可能有帮助的事情:

  • 不要使用java.util.Stack

一组更完整和一致的 LIFO 堆栈操作是 由 Deque 接口及其实现提供,应该 优先于此类使用。 http://docs.oracle.com/javase/8/docs/api/java/util/Stack.html

  • StackExchange 的嵌套子类都是内部类,所以 这意味着他们已经引用了包含 StackExchange 实例
  • 及其成员stack实例,应该是final
  • 所以不要将它们作为参数传递。这简化了逻辑、维护、 和 GC。
  • caller instanceof Popper这种反映完全 不必要并且破坏了面向对象。
  • 你知道ObjectcallTheStack 来说是一个过于宽泛的类型(弱 姓名)。实际上,您知道该对象将是一个Runnable,但是 更重要的是,Runnable 应该已经知道该做什么了。
  • 应将同步保持在最低限度,只限于共享数据的关键部分,如下所示,使用 synchronized 关键字
  • 或只是内存边界,如下所示,使用volatile关键字
  • 包含类的成员变量是在类中的线程之间共享数据的好方法。

例子

public class StackExchange {

private final Deque<String> stack = new ArrayDeque<>();
private volatile boolean running;

private void consume(String item) {
  // ...
}

private String obtain() {
  // ...
}

private boolean getPermission() {
  // ...
}

// getters, setters, ... 

private final Runnable consumer = new Runnable() {
  @Override
  public void run() {
    while (running) {
      final String popped;
      synchronized(stack) {
        popped = stack.pollFirst();
      }
      consume(popped);
    }
  }
};

private final Runnable producer = new Runnable() {
  @Override
  public void run() {
   while (running) {
     final String pushing = obtain();
      synchronized(stack) {
        stack.offerFirst(pushing);
      }
    }
  }
};

public static void main(String ... args) {
  StackExchange exchange = new StackExchange();

  exchange.setRunning(true);
  new Thread(exchange.getConsumer()).start();
  new Thread(exchange.getProducer()).start();

  do  {
  } while (exchange.getPermission());

  exchange.setRunning(false);
}
}

在成员方法之前声明成员变量是个好主意。

我将Runnable 代码放在匿名类中,以使代码处于使用 lambdas 的最边缘。

consumeobtaingetPermission 背后的想法是暗示代码将如何与不了解线程的业务逻辑进行交互。这些可以实现为回调或抽象方法。

Deque 的一个优点是它可以轻松设置为 FIFO 队列。

只是为了好玩,将那些 Runnable 实例转换为 lambda,并使 StackExchange 类通用。

热门问题:Deque&lt;E&gt; 的其他哪些子类型可能适合,它们有哪些优点或缺点?可能需要进行哪些代码更改以适应它们?

【讨论】: