线程对局部变量以及对只能通过当前线程的局部变量才能访问到的对象进行的操作总是线程安全的
基本定义
局部变量 是指在函数或代码块内部定义的变量,其作用范围仅限于定义它的函数或代码块内部
线程安全 是指多个线程访问共享资源时,不会破坏资源的完整性
示例1
public void example() {
Object obj = new Object();
}
上述创建对象的代码中,obj就是变量,如果在方法中创建这个对象那么obj就属于局部变量,obj和右边的Object()是两个东西,obj是变量,由于创建的是引用类型,所以obj存储在栈中的是地址值,这个内存地址指向堆中的Object()实例,又由于创建的地方在方法内,所以其他线程无法共享obj变量,每个进入该方法的线程都会重复创建只属于自己的Object()对象。
结论:
- obj变量属于局部变量,无法被其他线程修改
- obj引用的new Object()对象属于只能通过当前线程的局部变量才能访问到的对象
所以上述代码总是线程安全的
示例2
@Slf4j
public class Test {
public List<Integer> list = null;
public void k1(List<Integer> argList) {
log.info("k1接收到的list哈希{}",argList.hashCode());
argList.forEach(System.out::println);
}
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
test.list = new Vector<>(); // 集合1
test.list.add(998);
test.list.add(999);
log.info("调用k1前的list哈希{}",test.list.hashCode());
new Thread(() -> {
test.k1(test.list);
}).start();
test.list = new Vector<>(); // 集合2
test.list.add(1);
test.list.add(2);
log.info("调用k1后的list哈希{}",test.list.hashCode());
}
}
如上代码所示,k1方法中的参数argList也是局部变量,但是在main方法中调用k1的动作是由其他线程完成的,所以这里k1实际接收到的参数就是不确定的,不能确定新启动的线程进入k1方法时传入的集合是1还是2,不过可以确定的是,一旦进入k1方法,不管实际传入的集合是1还是2,这个集合就已经确定了(也就是argList变量持有的引用地址),即使后续main方法中再怎么修改test.list的引用也没关系,因为k1方法中的局部变量argList已经持有了进入该方法时传入的集合的引用地址。
重点提示
虽然argList变量是线程安全的(argList变量会一直指向传入该方法的实例的内存地址,对于argList变量的修改也只能在k1方法中实现),但是对于argList变量指向的实例内容则不做保证,因为在k1方法执行过程中argList引用的集合可能被其他线程添加或删除元素,虽然本例中使用的是线程安全的Vector,但是大部分业务代码可能都是使用ArrayList集合(非线程安全集合),所以还是需要注意。
结论:
- k1方法的argList变量属于局部变量
- argList变量引用的List不属于【只能通过当前线程的局部变量才能访问到的对象】,所以对于引用的List来说,我们需要自己保证线程安全