• 在Java中,所有实例域,静态域,数组元素,都存储在堆内存中,堆内存在线程之间共享。
  • 局部变量,方法定义参数,异常处理器参数,不会在线程之间共享,它们不会有内存可见性的问题,也不受内存模型的影响,是每一个线程私有的,不会被共享,自然也就不会存在竞争问题。(Java并发编程的艺术22页,深入理解JVM 441页)
  • (我们讨论Java内存模型的时候,讨论主内存和线程本地内存的数据共享问题的时候,考虑的是所有线程的全局变量,是不考虑每一个线程独有的局部变量的。每一个线程独有的局部变量,不涉及共享问题。)
  • (Java并发编程的艺术,22页图3-1,其中的“本地内存A”,也是只考虑所有线程的全局变量,是不考虑每一个线程独有的局部变量。)
  • 此处请读者注意区分概念:如果局部变量是一个reference类型,它引用的对象在Java堆中可被各个线程共享,但是reference本身在Java栈的局部变量表中是线程私有的。
  • Java所有的对象实例以及数组几乎都在堆上分配。
  • ThreadLocal线程局部变量,针对的是类成员变量,正常应线程共享该成员变量,但是ThreadLocal使得每个线程都有一个该成员变量,且相互独立。
  • 各个线程内自建的各个对象,各个对象实例都在堆上,但各个对象实例的各个引用属各个线程的局部变量,所以从外界来看,各个线程间,自建的对象相互独立。

多线程访问全局变量和局部变量,可参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class HelloThreadTest {

public static void main(String[] args) {
HelloThread r = new HelloThread();

Thread t1 = new Thread(r);
Thread t2 = new Thread(r);

t1.start();
t2.start();
}
}

class HelloThread implements Runnable {
int i;

@Override
public void run() {
while(true) {
System.out.println("Hello number: " + i++);

try {
Thread.sleep((long) Math.random() * 1000);
} catch(InterruptedException e) {
e.printStackTrace();
}

if (50 == i) {
break;
}
}
}
}

该例子中,HelloThread类实现了Runnable接口,其中run()方法的主要工作是输出“Hello number”: “字符串加数字i”,并且同时递增i,当i达到50时,循环退出。

main()方法中生成了一个HelloThread类的对象r,并且利用这一个对象生成了两个线程。

程序的执行结果是:依次打印0~49共50个数字。(可能存在并发的问题)

这是因为i是成员变量,HelloThread对象r只包含这一个i,两个Thread对象由r构造,所以共享同一个i。

当我们改变代码如下时(原先的成员变量i被注释掉,增加了方法中的局部变量i)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class HelloThreadTest {
public static void main(String[] args) {
HelloThread r = new HelloThread();

Thread t1 = new Thread(r);
Thread t2 = new Thread(r);

t1.start();
t2.start();
}
}

class HelloThread implements Runnable {
// int i;
// 若i是成员变量,则HelloThread的对象r只包含这一个i,两个Thread对象因由r构造,所以共享了同一个i
// 打印结果是0到49的数字
@Override
public void run() {
int i = 0;
// 每一个线程都会拥有自己的一份局部变量
// 线程之间互不影响
// 所以会打印100个数字,0~49每个数字都是2遍
while(true) {
System.out.print("Hello number: " + i++);

try {
Thread.sleep((long) Math.random() * 1000);
} catch(InterruptedException e) {
e.printStrackTrace();
}

if (50 == i) {
break;
}
}
}
}

如注释中所述,由于局部变量对于每一个线程来说都是属于自己的,所以各个线程之间不再共用同一个变量,输出结果为100个数字,实际上是两组,每组都是0~49的50个数字,并且两组数字之间随意地穿插在一起。

  • 得到结论如下:
    • 如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,它们对该成员变量是彼此影响的也就是说一个线程对成员变量的改变会影响到另一个线程。
    • 如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝(即便是同一个对象中的方法的局部变量,也会对每一个线程有一个拷贝),一个线程对该局部变量的改变不会影响到其他线程。

摘自/参考链接