Java多线程6:实例变量非线程安全

如果多个线程共同访问1个对象中的实例变量,则有可能出现“非线程安全”问题。

变量的分类

Java多线程6:实例变量非线程安全

知识小扩展(变量的生命周期)

1、实例变量:随着对象的创建而存在,随着对象的消失而消失。
2、局部变量:随着方法的调用而存在,随着方法的调用完毕而消失。
3、静态变量:静态变量是随着类的加载而加载,随着类的消失而消失。

在内存中的位置

1、实例变量:在堆中。
2、局部变量:在栈中。
3、静态的内容在方法区的静态区。

初始化值

1、成员变量:有默认值。
2、局部变量:没有默认值,必须定义,赋值,然后才能使用。

成员变量的初始化和内存中的运行机制

接下来以下面代码来举例说明成员变量的初始化和内存中的运行机制

public class Person {
    public static int num;
    public String name;
    
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Person();
        p1.num = 2;
        p2.num = 3;
        p1.name = "张三";
        p2.name = "李四";
    }
}

当程序执行Person p1 = new Person();时,如果这行代码是第一次使用Person类,则系统通常会在第一次使用Person类时加载这个类,并初始化这个类,在类的准备 阶段,系统将会为该类的类变量分配内存空间,并指定默认初始值。当person类初始化完成后,系统内存中的存储示意图如下图所示。
Java多线程6:实例变量非线程安全
可以看出,当person类初始化完成后,系统将在堆内存中为Person分配一块内存空间,实际上是创建了一个类对象,在这块内存区里包含了保存num类变量的内存,并设置num的默认初始值为0。

系统接着创建了一个Person对象,并把这个Person对象赋给p1变量,Person对象包含了名为name的实例变量,实例变量是在创建实例时分配内存空间并指定初始值的。当创建了第一个person对象后,系统内存中的存储示意图如下图所示。
Java多线程6:实例变量非线程安全
从上图可以看出num不属于对象,它属于类,所以创建第一个对象时并不需要为num分配内存空间,系统只是为name分配了内存空间,并指定初始值为null。

创建第二个对象p2时,由于在创建第一个对象时已经对类进行了初始化,所以在创建p2时对类进行初始化,对象的创建过程与第一个对象的创建过程没有什么区别。
Java多线程6:实例变量非线程安全
其中num为实例对象p1和p2所共有的,因为静态变量为类变量并且属于类,其随着类的加载而加载,随着类的结束而消亡。实例变量:随着对象的创建而存在,随着对象的消失而消失。局部变量:随着方法的调用而存在,随着方法的调用完毕而消失。

运行时数据区

Java多线程6:实例变量非线程安全
由上图可以发现堆和方法区是所有线程共享的数据区,因此实例变量为非线程安全的。可能出现覆盖的情况:

public class Demo1_shilibianliang {
	/**
	 * 实例变量的随着实例对象的销毁而结束其生命周期。
	 * @param args
	 */
	public static void main(String[] args) {
		HasSelfPrivateNum numRef = new HasSelfPrivateNum();
		ThreadA athread = new ThreadA(numRef);
		athread.start();
		ThreadB bthread = new ThreadB(numRef);
		bthread.start();
	}
}

class HasSelfPrivateNum {
	private int num = 0;
	public void AddI (String username) {
		try {
			if (username.equals("a")) {
				num = 100;
				System.out.println("a set over!");
				Thread.sleep(2000);//会让出cpu线程bthread执行,bthread执行后num = 200,2秒后athread执行此时num以是200
			} else {
				num = 200;
				System.out.println("b set over!");
			}
			System.out.println(username + " num=" + num);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class ThreadA extends Thread {
	private HasSelfPrivateNum numRef;
	
	public ThreadA(HasSelfPrivateNum numRef) {
		super();
		this.numRef = numRef;
	}
	
	public void run() {
		super.run();
		numRef.AddI("a");
	}
}

class ThreadB extends Thread {
	private HasSelfPrivateNum numRef;
	
	public ThreadB(HasSelfPrivateNum numRef) {
		super();
		this.numRef = numRef;
	}
	
	public void run() {
		super.run();
		numRef.AddI("b");
	}
}

运行结果

Java多线程6:实例变量非线程安全
由上实验可知,当两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则有可能出现“非线程安全”问题。要实现线程安全只需要在方法前加synchronized即可。

public class Demo1_shilibianliang {
	/**
	 * 实例变量的随着实例对象的销毁而结束其生命周期。
	 * @param args
	 */
	public static void main(String[] args) {
		HasSelfPrivateNum numRef = new HasSelfPrivateNum();
		ThreadA athread = new ThreadA(numRef);
		athread.start();
		ThreadB bthread = new ThreadB(numRef);
		bthread.start();
	}
}

class HasSelfPrivateNum {
	private int num = 0;
	synchronized public void AddI (String username) {
		try {
			if (username.equals("a")) {
				num = 100;
				System.out.println("a set over!");
				Thread.sleep(2000);//会让出cpu线程bthread执行,bthread执行后num = 200,2秒后athread执行此时num以是200
			} else {
				num = 200;
				System.out.println("b set over!");
			}
			System.out.println(username + " num=" + num);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class ThreadA extends Thread {
	private HasSelfPrivateNum numRef;
	
	public ThreadA(HasSelfPrivateNum numRef) {
		super();
		this.numRef = numRef;
	}
	
	public void run() {
		super.run();
		numRef.AddI("a");
	}
}

class ThreadB extends Thread {
	private HasSelfPrivateNum numRef;
	
	public ThreadB(HasSelfPrivateNum numRef) {
		super();
		this.numRef = numRef;
	}
	
	public void run() {
		super.run();
		numRef.AddI("b");
	}
}
结果

Java多线程6:实例变量非线程安全