1、多线程访问成员变量与局部变量说明
1、java多线程调用 单例类中一个的方法是不会排队的
- 因为jvm 在每个线程下,都有一份 对调用方法的引用。
2、多个线程调用的同一个对象的同一个方法:
- 如果方法里无成员变量,不受任何影响;
- 如果方法里有成员变量,只有读操作,不受影响,存在赋值操作,有影响
3、多线程调用同一个方法,局部变量会共享吗?
- 局部变量不会受多线程影响;
- 成员变量会受到多线程影响
jvm每个线程都拥有一个方法调用栈,用于跟踪线程中运行的一系列方法调用过程, 栈中的每个元素成为栈帧,线程调用每个方法时会将方法栈压入一个新帧, 帧里面存放方法参数,局部变量,运算过程产生的临时数据。
2、Java中的栈
每当启用一个线程时,JVM就为他分配一个Java栈,栈是以帧为单位保存当前线程的运行状态。某个线程正在执行的方法称为当前方法,当前方法使用的栈帧称为当前帧,当前方法所属的类称为当前类,当前类的常量池称为当前常量池。当线程执行一个方法时,它会跟踪当前常量池。
每当线程调用一个Java方法时,JVM就会在该线程对应的栈中压入一个帧,这个帧自然就成了当前帧。当执行这个方法时,它使用这个帧来存储参数、局部变量、中间运算结果等等。
Java栈上的所有数据都是私有的。任何线程都不能访问另一个线程的栈数据。所以我们不用考虑多线程情况下栈数据访问同步的情况。
像方法区和堆一样,Java栈和帧在内存中也不必是连续的,帧可以分布在连续的栈里,也可以分布在堆里
Java栈的组成元素——栈帧
栈帧由三部分组成:局部变量区、操作数栈、帧数据区。局部变量区和操作数栈的大小要视对应的方法而定,他们是按字长计算的。但调用一个方法时,它从类型信息中得到此方法局部变量区和操作数栈大小,并据此分配栈内存,然后压入Java栈。
3、 多线程访问成员变量与局部变量
- 类变量(类里面static修饰的变量)保存在“方法区”
- 实例变量(类里面的普通变量)保存在“堆”
- 局部变量(方法里声明的变量)“虚拟机栈”
“方法区”和“堆”都属于线程共享数据区,“虚拟机栈”属于线程私有数据区。
因此,局部变量是不能多个线程共享的,而类变量和实例变量是可以多个线程共享的。事实上,在java中,多线程间进行通信的唯一途径就是通过类变量和实例变量。也就是说,如果一段多线程程序中如果没有类变量和实例变量,那么这段多线程程序就一定是线程安全的。
以Web开发的Servlet为例,一般我们开发的时候,自己的类继承HttpServlet之后,重写doPost()、doGet()处理请求,不管我们在这两个方法里写什么代码,只要没有操作类变量或实例变量,最后写出来的代码就是线程安全的。如果在Servlet类里面加了实例变量,就很可能出现线程安全性问题,解决方法就是把实例变量改为ThreadLocal变量,而ThreadLocal实现的含义就是让实例变量变成了“线程私有”的,即给每一个线程分配一个自己的值。
4、例子
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时,退出循环。
生成了一个HelloThread类的对象r,并且利用这个一个对象生成两个线程。
程序的执行结果是:顺次打印了0到49的数字,共50个数字。
这是因为,i是成员变量,则HelloThread的对象r只包含这一个i,两个Thread对象因为由r构造,所以共享了同一个i。
当我们改变代码如下时(原先的成员变量i被注释掉,增加了方法中的局部变量i):
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每个数字都是两遍
while (true)
{
System.out.println("Hello number: " + i++);
try
{
Thread.sleep((long) Math.random() * 1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
if (50 == i)
{
break;
}
}
}
}
由于局部变量对于每一个线程来说都有自己的拷贝,所以各个线程之间不再共享同一个变量,输出结果为100个数字,实际上是两组,每组都是0到49的50个数字,并且两组数字之间随意地穿插在一起。
得到的结论如下:
如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,它们对该成员变量是彼此影响的,也就是说一个线程对成员变量的改变会影响到另一个线程。
如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝(即便是同一个对象中的方法的局部变量,也会对每一个线程有一个拷贝),一个线程对该局部变量的改变不会影响到其他线程。
参考:
https://www.jianshu.com/p/379f79398647