java:为什么两个线程不能并行执行
我试图理解java中的内在锁。我有一个程序,我在其中启动两个线程,它们将循环通过并调用同一对象上的同步方法。我期望两个线程并行执行,但看起来它是按顺序执行的 如果我在循环中引入睡眠,那么它们会以随机顺序执行[正如我所期望的那样]java:为什么两个线程不能并行执行,java,multithreading,loops,for-loop,context-switching,Java,Multithreading,Loops,For Loop,Context Switching,我试图理解java中的内在锁。我有一个程序,我在其中启动两个线程,它们将循环通过并调用同一对象上的同步方法。我期望两个线程并行执行,但看起来它是按顺序执行的 如果我在循环中引入睡眠,那么它们会以随机顺序执行[正如我所期望的那样] public class Synchronized { private int valueM; public Synchronized( int value) { valueM = value; } synchron
public class Synchronized {
private int valueM;
public Synchronized( int value) {
valueM = value;
}
synchronized
public void one() throws InterruptedException
{
System.out.println("Object[" + valueM + "] executing one");
Thread.sleep(100); //For case 2: comment it out
System.out.println("Object[" + valueM + "] completed one");
}
synchronized
public void two() throws InterruptedException
{
System.out.println("Object[" + valueM + "] executing two");
Thread.sleep(100); //For case 2: comment it out
System.out.println("Object[" + valueM + "] completed two");
}
}
测试代码:
@org.junit.jupiter.api.Test
void test_sync() throws InterruptedException
{
Synchronized obj = new Synchronized(1);
Runnable task_one = new Runnable() {
public void run() {
for (int i=0 ; i<10; i++)
{
try {
obj.one();
//Thread.sleep(100); //For case 2: uncomment it out
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
Runnable task_two = new Runnable() {
public void run() {
for (int i=0 ; i<10; i++)
{
try {
obj.two();
//Thread.sleep(100); //For case 2: uncomment it out
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
Thread t1 = new Thread(task_one);
Thread t2 = new Thread(task_two);
t1.start();
t2.start();
t1.join();
t2.join();
}
更新:
原来的问题已经解决了。。看起来即使在案例1中也是随机的,但我只有在加载更多迭代(30K)时才能看到它
所以线程切换在没有休眠的for循环中发生得更少?Java JVM试图让for循环作为“一种”原子(不是完全的,而是尽可能多的)来执行它,这对Java JVM来说有什么特殊之处吗 您已将方法
one
和two
标记为synchronized
。这意味着在线程可以进入其中任何一个之前,它必须在obj
上获得锁。如果另一个线程持有锁,则该线程无法获取锁。当线程退出one
/two
时,锁被释放,两个线程再次争夺锁。有时第一个线程会成功,有时第二个线程会成功——这就是为什么您会看到调用的随机顺序,但从不混合
这是设计的。实际上,您已经告诉JVM您不希望两个线程同时运行。内部的锁(
synchronized
关键字)被认为是“不公平的”,这意味着不能保证在竞争线程中锁的获取率是相同的
一个已知的事实是,释放锁的线程通常更有可能再次获取锁,从而导致您遇到的问题
如果希望线程具有类似的获取可能性(公平性),则可以使用显式锁,如确保使用可选的布尔值
参数将其设置为true
ReentrantLock(boolean fair)
那么你可以这样使用它
class X {
private final ReentrantLock lock = new ReentrantLock(true);
public void m() {
lock.lock();
try {
// method body
} finally {
lock.unlock()
}
}
}
让我们试着理解你的问题,然后试着看到预期的结果
sleep
方法,情况2有在当前执行的线程上调用的sleep
方法wait
方法,该方法为其他线程释放锁。所以在你的情况下,睡眠只会让执行变慢,其他什么都没有
线程调度程序决定线程的执行顺序以及处理器周期共享。它不保证任何顺序,也不保证任何随机性,它可能是随机的,也可能不是随机的
现在,正如我所说的,当睡眠不能释放锁时,我们有时如何获得随机的执行顺序呢?答案是:只要两个线程中的一个线程完成了其中一个同步方法的执行,锁就会被释放,线程调度程序就会决定给哪个线程下一次执行的机会 相对而言,Thread#start是一种非常慢的方法。数到10(或数到1000)并不需要计算机很长时间。在操作系统完成第二个线程实际执行的工作之前,第一个线程已经完成计数。如果您想“同时”启动两个线程,则需要使用闩锁
根据您的执行环境,系统控制台编写器本身可能是一个同步的有争议的资源(或者相反,它可能无法保证以与线程访问顺序一致的时间点方式刷新和写入),这一事实也让您的测试感到困惑多年来,试图使用System.out.println调试并发问题给许多人带来了很多麻烦,因为暂停获取控制台编写器通常会隐藏他们的内存一致性错误
public static CountDownLatch latch = new CountDownLatch(1);
public static class Thing implements Runnable {
@Override
public void run() {
try {
latch.await();
//doStuff
} catch (InterruptedException e) {
}
}
}
public static void main(String[] args) throws Exception {
Thing thing1 = new Thing();
Thing thing2 = new Thing();
new Thread(thing1).start();
new Thread(thing2).start();
latch.countDown();
}
如果你把环变大怎么办?e、 g.30k或更多迭代?@MargaretBloom我怀疑是这样,尝试了1000次,让我用30K@SamDaniel我复制了您的代码并按原样运行:它不正常。它显示了9次1,10次2,1次1。我认为,如果没有睡眠,这些方法非常快,以至于在调用to
run()
之间的时间足以让第一个线程在第二个线程开始之前完成,或者至少完成大部分工作。案例1的输出是随机的,您应该多次重新运行以检查,我认为工作中存在一些优化,它们试图通过过于频繁地切换线程来避免过多的开销。随着工作时间的延长(在run
方法中),您可能会看到更多的开关。我发现很有趣。我理解线程的概念。问题是为什么在第一种情况下它不是随机的。排序是不确定的。当线程竞争时,任何一个都可能在下次获得锁。没有任何形式的保证。实际结果可能取决于JVM实现、CPU调度算法,或者由于缺少更好的示例,取决于天气。所以,即使你看到一个序列,实际上仍然是一个随机的(更准确地说:不可预测的)顺序。
public static CountDownLatch latch = new CountDownLatch(1);
public static class Thing implements Runnable {
@Override
public void run() {
try {
latch.await();
//doStuff
} catch (InterruptedException e) {
}
}
}
public static void main(String[] args) throws Exception {
Thing thing1 = new Thing();
Thing thing2 = new Thing();
new Thread(thing1).start();
new Thread(thing2).start();
latch.countDown();
}