问一个关于 Java 线程的疑惑?

2022-03-22 09:55:03 +08:00
 bjhc

最近在重新学习有关 java 多线程方面的知识。然后使用内部锁的机制,想简单模拟一下生产者和消费者。 代码逻辑大概是这样的:每个生产者线程产生 10 个数据,然后供一个消费者消费。由于在 main 线程中设置了每个生产者线程的名字,所以想在生产者生成数据的逻辑中打印当前线程名。 我的疑惑:

  1. 为什么打印日志中,线程的名字有重复?比如在日志中没有看到 producer-2 或 producer-5 ,但是 producer-17 和 producer-11 分别重复出现了 3 次?
  2. 线程总数可以对上,不知道是不是代码哪里出了问题还是本身没有理解到位?

代码如下与日志输出如下:

package org.example;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class WaitAndNotify {

	private final Object lock = new Object();

	private final List<Integer> list = new ArrayList<>();

	private final Random random = new Random();

	public void producer() throws InterruptedException {
		synchronized (lock) {
			while (list.size() == 10) {
				lock.wait();
			}
			list.add(random.nextInt(100));
			if (list.size() == 10) {
				log.info("thread-{},{}", Thread.currentThread().getName(),
						Arrays.toString(list.toArray(new Integer[0])));
			}
			lock.notifyAll();
		}
	}

	public void consumer() throws InterruptedException {
		synchronized (lock) {
			while (list.size() < 10) {
				lock.wait();
			}
			list.clear();
			lock.notifyAll();
		}
	}

	static class MyProducer implements Runnable {

		private final WaitAndNotify waitAndNotify;

		public MyProducer(WaitAndNotify waitAndNotify) {
			this.waitAndNotify = waitAndNotify;
		}

		@SneakyThrows
		@Override
		public void run() {
			int i = 10;
			while (i > 0) {
				waitAndNotify.producer();
				i--;
			}
		}
	}

	static class MyConsumer implements Runnable {

		private final WaitAndNotify waitAndNotify;

		public MyConsumer(WaitAndNotify waitAndNotify) {
			this.waitAndNotify = waitAndNotify;
		}

		@SneakyThrows
		@Override
		public void run() {
			while (true) {
				waitAndNotify.consumer();
			}
		}
	}

	public static void main(String[] args) {
		WaitAndNotify waitAndNotify = new WaitAndNotify();
		Runnable runnable = new MyProducer(waitAndNotify);
		for (int i = 0; i < 20; i++) {
			Thread producer = new Thread(runnable, "producer-" + i);
			producer.start();
		}
		Thread consumer = new Thread(new MyConsumer(waitAndNotify), "consumer");
		consumer.start();
	}
}

2022-03-21 17:32:42,014 INFO  [learning-concurrency][producer-0] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-0,[49, 96, 0, 43, 23, 6, 79, 44, 28, 21]
2022-03-21 17:32:42,019 INFO  [learning-concurrency][producer-17] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-17,[92, 56, 9, 43, 77, 24, 53, 74, 44, 85]
2022-03-21 17:32:42,019 INFO  [learning-concurrency][producer-1] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-1,[86, 11, 10, 58, 5, 86, 3, 68, 73, 24]
2022-03-21 17:32:42,020 INFO  [learning-concurrency][producer-17] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-17,[10, 9, 97, 41, 81, 35, 84, 0, 86, 12]
2022-03-21 17:32:42,020 INFO  [learning-concurrency][producer-3] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-3,[13, 90, 30, 45, 1, 8, 43, 68, 49, 26]
2022-03-21 17:32:42,020 INFO  [learning-concurrency][producer-17] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-17,[81, 19, 34, 58, 89, 66, 74, 76, 20, 5]
2022-03-21 17:32:42,021 INFO  [learning-concurrency][producer-4] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-4,[48, 60, 0, 36, 97, 9, 42, 44, 67, 81]
2022-03-21 17:32:42,021 INFO  [learning-concurrency][producer-16] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-16,[77, 38, 17, 63, 71, 7, 80, 64, 61, 19]
2022-03-21 17:32:42,021 INFO  [learning-concurrency][producer-6] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-6,[47, 56, 50, 7, 89, 45, 32, 91, 97, 66]
2022-03-21 17:32:42,022 INFO  [learning-concurrency][producer-16] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-16,[27, 96, 24, 85, 3, 28, 23, 21, 93, 72]
2022-03-21 17:32:42,022 INFO  [learning-concurrency][producer-7] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-7,[4, 95, 19, 95, 42, 9, 49, 5, 54, 8]
2022-03-21 17:32:42,022 INFO  [learning-concurrency][producer-13] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-13,[43, 81, 97, 19, 40, 65, 48, 32, 61, 77]
2022-03-21 17:32:42,023 INFO  [learning-concurrency][producer-14] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-14,[67, 81, 84, 11, 73, 71, 65, 26, 78, 45]
2022-03-21 17:32:42,023 INFO  [learning-concurrency][producer-12] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-12,[54, 48, 67, 88, 0, 78, 99, 12, 49, 84]
2022-03-21 17:32:42,023 INFO  [learning-concurrency][producer-7] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-7,[58, 66, 53, 39, 14, 64, 2, 57, 27, 91]
2022-03-21 17:32:42,023 INFO  [learning-concurrency][producer-11] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-11,[91, 79, 70, 36, 34, 28, 63, 67, 17, 77]
2022-03-21 17:32:42,024 INFO  [learning-concurrency][producer-10] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-10,[2, 76, 42, 92, 91, 87, 64, 20, 25, 3]
2022-03-21 17:32:42,024 INFO  [learning-concurrency][producer-9] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-9,[46, 57, 98, 0, 28, 68, 63, 71, 97, 54]
2022-03-21 17:32:42,024 INFO  [learning-concurrency][producer-11] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-11,[26, 66, 10, 77, 69, 41, 77, 80, 21, 55]
2022-03-21 17:32:42,024 INFO  [learning-concurrency][producer-11] org.example.ch5._1_1.WaitAndNotify producer(33): thread-producer-11,[94, 97, 84, 87, 85, 53, 77, 88, 79, 84]
3218 次点击
所在节点    Java
32 条回复
312ybj
2022-03-22 10:28:17 +08:00
线程复用了, 你把生产者的数量调整大点,多跑几次
0o0o0o0
2022-03-22 10:30:45 +08:00
非公平锁?
pennai
2022-03-22 10:33:23 +08:00
这应该是 monitor lock 是非公平锁的问题,你的哪个生产者可以获得锁是完全随机的,并非你想象的“每个生产者正好可以获得一次锁”。实际情况是有些线程拿了多次锁,有些线程没拿到锁。
bjhc
2022-03-22 10:42:07 +08:00
@pennai 虽然是随机的,但是每个线程不应该只执行一次吗?
bjhc
2022-03-22 10:42:39 +08:00
@312ybj 调大了也不行,还是有重复的
lancelee01
2022-03-22 10:45:18 +08:00
“日志中没有看到 producer-2 或 producer-5 ,但是 producer-17 和 producer-11 分别重复出现了 3 次”。这个是因为元素满 10 个打印,所以打印多次的是正好这个线程每次执行时生成的元素正好是第 10 个元素,没有打印是该线程每次执行生成数对应列表内是 0-8 下标。想看到所有线程应该在 if 判断外。
bjhc
2022-03-22 10:49:31 +08:00
@lancelee01 重复出现的线程,打印出的数组元素也不同,这是为什么?
lancelee01
2022-03-22 10:51:16 +08:00
@bmwyhc 因为你的消费者在消费呀,打印说明满 10 个元素,生产者阻塞,唤醒消费者
lancelee01
2022-03-22 10:51:44 +08:00
哈哈哈,感觉这个代码不是你写的 doge
bjhc
2022-03-22 10:52:04 +08:00
@lancelee01 我也发现这个问题了,如果在 if 语句外,可以打印出这 20 个线程,而且这 20 个线程的数组元素最后都符合满 10 的条件。为啥在 if 里就不一样。
bjhc
2022-03-22 10:53:08 +08:00
@lancelee01 那不会,当然是自己写的。我就是好奇为什么在条件里和条件外会有不同的输出。
lancelee01
2022-03-22 10:54:16 +08:00
@bmwyhc 原因就是我首次回复内容
bjhc
2022-03-22 10:55:53 +08:00
@lancelee01 可是最后所有线程都满 10 了呀。
ffkjjj
2022-03-22 10:56:26 +08:00
@bmwyhc #7 并不是同一个线程一个集合, 为啥同线程会打印出一样的元素
你代码的执行过程是: 所有线程通过竞争锁向同一个集合中添加元素, 当集合元素满 10 个的时候, 此时, 如果 producer 竞争到锁便进入 lock.wait() 等待, 如果是 consumer 竞争到锁, 则清空 list. 然后以此重复.
bjhc
2022-03-22 10:58:39 +08:00
@ffkjjj 您的意思我也想到过,但是将 if 语句移除,居然可以打印出所有正确的线程。
lancelee01
2022-03-22 10:59:05 +08:00
@bmwyhc 多线程又不是串行执行,waitAndNotify.producer(); 这个是多个线程交替执行的,每个线程只是生产 10 个元素,但不是连续生产,所以完全有可能第一个线程生产了 8 个,然后第二个线程生产了 2 个。然后第二个线程生产 8 个,第一个线程生产 2 个。这样第二个线程有很大几率打印 2 次,比如第一轮的下标 8-9 ,第二轮的 2-9
ffkjjj
2022-03-22 11:23:45 +08:00
@bmwyhc #15 和 if 没关系, 锁机制的问题, synchronized 是非公平锁, 可以了解下 synchronized 对应的 偏向锁, 轻量锁 和重量锁的特点
bjhc
2022-03-22 11:33:46 +08:00
@ffkjjj 好的多谢
ffkjjj
2022-03-22 11:35:04 +08:00
@ffkjjj #17
~~synchronized 是非公平锁~~(这句话放在这里可能不合适)
BiChengfei
2022-03-22 11:35:20 +08:00
@bmwyhc 这个老铁(@lancelee01 )说的是对的
---------------------------------------------------------------------------------------
举个例子:
你找了 20 个人来搬砖头,每个人总共有 10 块。他们需要把砖头放到同一辆小推车里,当小推车里有 10 块砖头的时候,他们就喊你把小推车拉走,你清空后再把小推车拉回来,他们接着装。
** 上述就是你代码的解读 **
---------------------------------------------------------------------------------------
下面是我的理解:
你的构想,工人应该排队搬砖,0 号开始搬,0 号搬完 1 号搬,一直到 19
事实上,工人会争先恐后的搬砖,可能有个机灵鬼,每次都等车里有 9 块的时候,就去放最后 1 块,这就导致了每次你都看到他
---------------------------------------------------------------------------------------
如果想做到你想的那样,你应该给工人加锁,而不是给小推车加锁
---------------------------------------------------------------------------------------
验证:你把 producer() 方法中的 if 给删除,然后打印,就像下面这样
```
list.add(random.nextInt(100));
System.out.println("thread-" + Thread.currentThread().getName() + Arrays.toString(list.toArray()));
lock.notifyAll();
```
你会看到线程争先恐后的向 list 里面放砖,但每个线程都只有 10 块砖

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/842015

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX