如何正确添加Java同步

时间:2015-03-11 09:37:50

标签: java multithreading synchronization

当生成整数时,Consumer线程总和它们的值(1 + 2 + 3 ... + 10 = 55)

生产者线程生成1到10的整数

该程序旨在生成一个整数并立即使用它。但是,在程序结束时生成的结果很少等于55.这是因为线程不会等待彼此完成任务

需要向代码添加同步,以便使用者线程仅在生产者线程生成新整数后才向总计添加值

Driver.java

    public class Lab08_Driver {

      public static void main(String args[]) {

       UsingSharedInt h = new UsingSharedInt();
       Producer p = new Producer(h);
       Consumer c = new Consumer(h);

        p.start();
        c.start();
      }
    }

Consumer.java

  public class Consumer extends Thread {

     private UsingSharedInt cHold;


     public Consumer( UsingSharedInt h )
     {
       super( "ConsumeInteger" );
       cHold = h;
     }

     public void run()
     {
       int val, sum = 0;

       do {
       // sleep for a random interval
       try {
           Thread.sleep( (int) ( Math.random() * 3000 ) );
       }
       catch( InterruptedException e ) {
           System.err.println( e.toString() );
       }

       val = cHold.getSharedInt();
       sum += val;
       } while ( val != 10 );

       System.err.println(
       getName() + " retrieved values totaling: " + sum +
       "\nTerminating " + getName() );
     }
  }

Producer.java

   public class Producer extends Thread {

      private UsingSharedInt pHold;

      public Producer( UsingSharedInt h )
      {
         super( "ProduceInteger" );
         pHold = h;
      }

      public void run()
      {
          for ( int count = 1; count <= 10; count++ ) {
          // sleep for a random interval
          try {
              Thread.sleep( (int) ( Math.random() * 3000 ) );
          }
          catch( InterruptedException e ) {
              System.err.println( e.toString() );
          }

         pHold.setSharedInt( count );
       }

       System.err.println( getName() +
       " finished producing values" +
       "\nTerminating " + getName() );
     }
   }

UsingSharedInt.java

    // HoldIntegerUnsynchronized.java

    public class UsingSharedInt {

       private int sharedInt = -1;

       public void setSharedInt( int val )
       {
          System.err.println( Thread.currentThread().getName() +
          " setting sharedInt to " + val );
          sharedInt = val;
       }

       public int getSharedInt()
       {
         System.err.println( Thread.currentThread().getName() +
         " retrieving sharedInt value " + sharedInt );
         return sharedInt;
       }
   }

4 个答案:

答案 0 :(得分:1)

只需使用BlockingQueue作为生产者生产和消费者消费的元素的容器:

public class UsingSharedInt {

       private BlockingQueue<Integer> q = new ArrayBlockingQueue<>(100);

       public void setSharedInt( int val )
       {
          System.err.println( Thread.currentThread().getName() +
          " setting sharedInt to " + val );
          q.add(val); // puts val into the queue
       }

       public int getSharedInt()
       {
         int val = q.take(); // waits for element to become available in queue, then returns one
         System.err.println( Thread.currentThread().getName() +
         " retrieving sharedInt value " + val);
         return val;
       }
   }

答案 1 :(得分:0)

这不起作用,并且无法工作的原因是,您的共享int只能包含单个值。因此,即使您使getter和setter同步,(public synchronized void而不是public void,或使用私有lock对象),一个线程仍然可以在另一个线程获取之前写入两个值有机会读取任何一个,并且一个线程可以在另一个线程有机会用新值替换它之前读取相同的值两次。

所以,你有两个选择:

  • UsingSharedInt包含Integer而不是int,并且:

    • 在将值替换为非空值之前,使setter循环直到值为null。这样,一个值永远不会被覆盖。

    • 在获取之前使getter循环直到值为非null,然后在返回值之前将其设置为null。这样,一个值永远不会被读取两次。

  • 正如Sasha Salauyou建议的那样,使用BlockingQueue;一个线程将整数添加到队列中,而另一个线程从队列中删除整数并处理它们。这是最好的方法。

答案 2 :(得分:0)

问题不在于并发访问共享int。有一些排队逻辑要看。

在代码中,Producer循环并设置SharedInt的值,而不等待Consumer使用它。当Consumer读取一个值时,它会读取[1,10]之间的某些值,当然也会读取最后一个(10),因为它是while循环中的退出条件。但由于每个线程在随机时间写入/读取值,因此您将无法获得完美的写/读/写/读/等序列。但是像写/写/读/写/写/读/读/等......

为了使其工作,您需要在设置一个值后设置SharedInt中的写入(或者您需要对不同的值进行排队),直到该值被读取(消耗)为止。 Consumer主题。对于Consumer的读取,它必须等到生产者设置一个值。

实现这一目标的最简单方法是使用像BlockingQueue这样的并发集合来存储共享整数。请参阅doc中的ProducerConsumer示例。

您可以自己实现此队列机制来试验低级别同步,但这不仅仅是在synchronized周围放置SharedInt关键字......

答案 3 :(得分:0)

只需在Consumer中添加一个temp int,看它是否与上一个不同。

int val, sum, tmp = 0;
do {
// sleep for a random interval
  try { 
     Thread.sleep( (int) ( Math.random() * 3000 ) ); 
  }catch( InterruptedException e ) {
     System.err.println( e.toString() );
  }
  val = cHold.getSharedInt();
  if(val!=tmp){
    sum += val;
  }
  tmp = val;
} while ( val != 10 );