我的问题是:
例如,我有一个具有2种方法的Java程序-add()和sub()。在什么情况下2个或更多线程将运行'add()'方法?
代码不是总是线程安全的,因为多个线程将访问代码的不同部分?
如果没有,请显示一个示例程序,其中要考虑线程安全性。
答案 0 :(得分:1)
不要考虑“代码段”,而要考虑数据所在的位置以及有多少线程正在访问该实际数据。
局部变量位于使用它们的线程堆栈中,并且是线程安全的,因为它们是每个线程的不同数据“容器”。
驻留在堆上的任何数据(例如实例或静态字段)本质上都不是线程安全的,因为如果有多个线程访问该数据,那么它们可能会争用。
我们可能会变得更加复杂,并讨论数据真正的位置,但是这种基本解释应该使您对正在发生的事情有个很好的了解。
下面的代码给出了一个由两个线程共享的实例的示例,在这种情况下,两个线程都访问相同的数组列表,该列表指向堆中相同的数组数据容器。运行几次,您最终会发现失败。如果您注释掉其中一个线程,则每次都会正常工作,从99开始倒数。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
new Thread(r).start();
new Thread(r).start();
}
public static class MyRunnable implements Runnable {
// imagine this list living out in the heap and both threads messing with it
// this is really just a reference, but the actual data is in the heap
private List<Integer> list = new ArrayList<>();
{ for (int i = 0; i < 100; i++) list.add(i); }
@Override public void run() {
while (list.size() > 0) System.out.println(list.remove(list.size() - 1));
}
}
}
答案 1 :(得分:0)
取决于实现。只有一个线程(“主线程”)将调用public static void main(String[])
方法,但这并不意味着没有为其他任务启动其他线程。
如果对线程进行编程,则该线程将访问“相同代码”。我不确定您对“代码段”的想法是什么,或者两个线程永远不会同时访问同一“节”的想法来自哪里,但是创建线程不安全的代码是非常琐碎的。>
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) throws InterruptedException {
List<Object> list = new ArrayList<>();
Runnable action = () -> {
while (true) {
list.add(new Object());
}
};
Thread thread1 = new Thread(action, "tread-1");
thread1.setDaemon(true); // don't keep JVM alive
Thread thread2 = new Thread(action, "thread-2");
thread2.setDaemon(true); // don't keep JVM alive
thread1.start();
thread2.start();
Thread.sleep(1_000L);
}
}
ArrayList
不是线程安全的。上面的代码有两个线程不断尝试将新的Object
添加到相同的ArrayList
大约一秒钟。它不是保证,但是如果您运行该代码,则可能会看到ArrayIndexOutOfBoundsException
或类似内容。无论抛出任何异常,ArrayList
的状态都有被破坏的危险。这是因为状态是由多个线程更新的,没有同步。
答案 2 :(得分:0)
1)默认情况下,Java程序是否仅导致创建1个线程?
真的取决于您的代码在做什么。一个简单的System.out.println()
调用可能可能仅创建一个线程。但是,例如,当您举起一个Swing GUI窗口时,将至少有一个其他线程(“事件分配器线程”对用户输入做出反应并负责UI更新)。
2)如果是,并且如果我们创建一个多线程程序,那么多线程何时访问Java对象的相同代码?
对你的误解。对象没有 code 。基本上,线程将运行特定的方法。它自己的run()
方法或其他可用的方法。然后线程仅执行该方法,以及从该初始方法触发的任何其他方法调用。
当然,在运行该代码时,该线程可能会创建其他对象,或操纵已经存在的对象的状态。当每个线程仅接触一组不同的对象时,就不会出现问题。但是,一旦有多个线程处理相同的对象状态,就需要采取适当的预防措施(以避免不确定的行为)。
答案 3 :(得分:0)
您的问题表明您可能不完全了解“线程”的含义。
当我们学习编程的时候,他们告诉我们计算机程序是一系列指令,并且他们告诉我们计算机执行这些指令是从一个很好的开始-定义的入口点(例如main()
例程)。
好的,但是当我们谈论多线程程序时,仅仅说“计算机”执行我们的代码已不再足够。现在我们说 threads 执行我们的代码。每个线程对自己在程序中的位置都有自己的想法,如果两个或多个线程恰巧同时在同一函数中执行,则每个线程都有其函数参数和局部变量的专用副本。
所以,你问:
默认情况下,Java程序是否仅导致创建1个线程?
Java程序总是以执行您的代码的一个线程开始,通常是执行JVM代码的其他多个线程。您通常不需要了解JVM线程。执行您的代码的一个线程在您的main()
例程的开头开始工作。
程序员经常将该初始线程称为“主线程”。他们可能会这样称呼它,因为它叫main()
,但是要小心!这个名称可能会误导您:JVM在多线程Java程序中对待“主线程”的方式与其他线程没有任何区别。
如果我们创建一个多线程程序,那么多线程何时访问Java对象的相同代码?
线程仅执行程序告诉他们的操作。如果您为两个不同的线程编写代码以调用同一函数,那么它们将执行此操作。但是,让我们将这个问题分解一下...
...首先,我们如何创建一个多线程程序?
当您的代码告诉程序成为多线程时,程序将变为多线程。在一个简单的情况下,它看起来像这样:
class MyRunnable implements Runnable {
public void run() {
DoSomeUsefulThing();
DoSomeOtherThing();
}
}
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
...
当程序中的某些其他线程调用t.start()
时,Java将创建一个新线程。 (注意!Thread
实例t
不是 thread 。它只是程序可以用来启动线程的 handle 并查询其线程的状态并对其进行控制。)
新线程开始执行程序指令时,将通过调用r.run()
开始。如您所见,r.run()
的主体将使新线程先DoSomeUsefulThing()
,然后再DoSomeOtherThing()
,然后r.run()
返回。
当r.run()
返回时,线程结束(又称“终止”,又称“死”)。
所以
何时多个线程访问Java对象的相同代码?
当您的代码让他们这样做时。让我们在上面的示例中添加一行:
...
Thread t = new Thread(r);
t.start();
DoSomeUsefulThing();
...
请注意,启动新线程后,主线程没有未停止。它继续执行t.start()
调用之后的所有操作。在这种情况下,下一步是调用DoSomeUsefulThing()
。但这与程序告诉新线程的操作相同!如果DoSomeUsefulThing()
需要花费大量时间才能完成,则两个线程将同时执行此操作...因为这是程序告诉他们执行的操作。
请显示一个示例程序,其中要考虑线程安全性
我照做了。
考虑DoSomeUsefulThing()
可能正在做什么。如果它正在做有用的事情,那么几乎可以肯定它正在对某处的某些 data 做某事。但是,我没有告诉它要使用什么数据,所以有可能两个线程同时对相同的数据进行处理。
那很有可能会变得不好。
一种解决方法是告诉函数要处理哪些数据。
class MyDataClass { ... }
Class MyRunnable implements Runnable {
private MyDataClass data;
public MyRunnable(MyDataClass data) {
this.data = data;
}
public void run() {
DoSomeUsefulThingWITH(data);
DoSomeOtherThingWITH(data);
}
}
MyDataClass dat_a = new MyDataClass(...);
MyDataClass dat_b = new MyDataClass(...);
MyRunnable r = new MyRunnable(dat_a);
Thread t = new Thread(r);
t.start();
DoSomeUsefulThingWITH(dat_b);
那里!现在,两个线程正在做相同的事情,但是它们正在对不同的数据进行操作。
但是,如果您想要对它们使用相同的数据怎么办?
这是另一个问题的话题。 Google开始“互斥”。