如何正确添加同步Java
How to add syncronization properly Java
当生成整数时,消费者线程对它们的值求和 (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;
}
}
这行不通而且不可能行得通的原因是您的共享 int
只能包含一个值。因此,即使您使 getter 和 setter 同步,(public synchronized void
而不是 public void
,或使用私有 lock
对象)一个线程仍然可以写入在另一个线程有机会读取任何值之前两个值,并且一个线程可以在另一个线程有机会用新值替换它之前两次读取相同的值。
所以,你有两个选择:
使 UsingSharedInt
包含 Integer
而不是 int
,并且:
进行 setter 循环,直到值为空,然后再将其替换为非空值。这样,一个值将永远不会被覆盖。
进行getter循环,直到取值前为非空值,然后在返回值前设置为空值。这样,一个值将永远不会被读取两次。
正如 Sasha Salauyou 建议的那样,使用 BlockingQueue
;一个线程将整数添加到队列中,而另一个线程从队列中删除整数并处理它们。这是最好的方法。
只需使用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;
}
}
问题不在于仅并发访问共享 int。有一些排队逻辑要看。
在代码中,Producer
循环并设置 SharedInt
的值,而不等待它被 Consumer
消耗。当 Consumer
读取一个值时,它将读取 [1,10] 和最后一个 (10) 之间的一些值,因为它是 while 循环中的退出条件。但是由于每个线程 write/read 的值都是随机的,所以你不会有 write/read/write/read/etc 的完美序列。但是像 write/write/read/write/write/read/read/etc...
为了让它工作,你需要在 SharedInt
中写入一个值后阻塞(或者你需要对不同的值进行排队),直到这个值被读取(消耗)通过 Consumer
线程。对于 Consumer
的读取也是如此,必须等到生产者设置一个值。
实现这一点的最简单方法是使用 BlockingQueue
之类的并发集合来存储共享整数。请参阅文档中 ProducerConsumer 的示例。
你可以自己实现这个队列机制来试验低级同步,但这不仅仅是在SharedInt
...[=21=周围放一个synchronized
关键字的问题]
在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 );
当生成整数时,消费者线程对它们的值求和 (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;
}
}
这行不通而且不可能行得通的原因是您的共享 int
只能包含一个值。因此,即使您使 getter 和 setter 同步,(public synchronized void
而不是 public void
,或使用私有 lock
对象)一个线程仍然可以写入在另一个线程有机会读取任何值之前两个值,并且一个线程可以在另一个线程有机会用新值替换它之前两次读取相同的值。
所以,你有两个选择:
使
UsingSharedInt
包含Integer
而不是int
,并且:进行 setter 循环,直到值为空,然后再将其替换为非空值。这样,一个值将永远不会被覆盖。
进行getter循环,直到取值前为非空值,然后在返回值前设置为空值。这样,一个值将永远不会被读取两次。
正如 Sasha Salauyou 建议的那样,使用
BlockingQueue
;一个线程将整数添加到队列中,而另一个线程从队列中删除整数并处理它们。这是最好的方法。
只需使用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;
}
}
问题不在于仅并发访问共享 int。有一些排队逻辑要看。
在代码中,Producer
循环并设置 SharedInt
的值,而不等待它被 Consumer
消耗。当 Consumer
读取一个值时,它将读取 [1,10] 和最后一个 (10) 之间的一些值,因为它是 while 循环中的退出条件。但是由于每个线程 write/read 的值都是随机的,所以你不会有 write/read/write/read/etc 的完美序列。但是像 write/write/read/write/write/read/read/etc...
为了让它工作,你需要在 SharedInt
中写入一个值后阻塞(或者你需要对不同的值进行排队),直到这个值被读取(消耗)通过 Consumer
线程。对于 Consumer
的读取也是如此,必须等到生产者设置一个值。
实现这一点的最简单方法是使用 BlockingQueue
之类的并发集合来存储共享整数。请参阅文档中 ProducerConsumer 的示例。
你可以自己实现这个队列机制来试验低级同步,但这不仅仅是在SharedInt
...[=21=周围放一个synchronized
关键字的问题]
在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 );