不变性
- 如果对象在被创建后,状态不能被修改,那么它就是不可变
- 具有不变性的对象一定是线程安全的
对象不可变的判断条件
- 对象创建后,其状态不能改变
- 所有属性都是final修饰的
- 对象创建过程中没有发生逸出
final的三种用法
final修饰变量
-
含义:被final修饰的变量,意味着值不能被修改。如果变量是对象,那么对象的引用不能变,但是对象自身的内容依然可以改变
public class Person { final int age = 18; final String name = "alice"; //String bag = "computer" } public class TestFinal{ public static void main(String[] args) { final Person person = new Person(); //给引用赋值会出错,不能改变,但是Person类中的内容可以改变 //person = new Class1(); } }
-
类中的final属性有三种赋值时机:
-
第一种是在声明变量的等号右边直接赋值
private final int a = 6;
-
第二种是在构造方法中赋值
public class Test{ private final int a; public Test(int a){ this.a = a; } }
-
第三种就是在类的初始化代码块中赋值
private final int a; { a = 3; }
-
-
类中的static final属性则有两种赋值时机,除了在声明变量的等号右边直接赋值,还可以用static初始代码块赋值,而不能用普通的初始化代码块赋值
-
在方法中的final变量,由于方法中并没有构造函数,也没有初始化代码块,所以不规定赋值时机,只要求在变量使用前必须赋值
final修饰方法
- 构造方法不允许final修饰
- final修饰的方法不能被重写,这里与static方法不能被重写是一致的
final修饰类
- final修饰的类不能被继承
栈封闭
- 在方法里新建的局部变量,实际上是存储在每个线程私有的栈空间,而每个栈的栈空间是不能被其他线程访问到的,所以不会有线程安全问题
/**
* 描述: 演示栈封闭的两种情况,基本变量和对象 先演示线程争抢带来错误结果,然后把变量放到方法内,情况就变了
*/
public class StackConfinement implements Runnable {
int index = 0;
public void inThread() {
int neverGoOut = 0;
synchronized (this) {
for (int i = 0; i < 10000; i++) {
neverGoOut++;
}
}
System.out.println("栈内保护的数字是线程安全的:" + neverGoOut);
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
index++;
}
inThread();
}
public static void main(String[] args) throws InterruptedException {
StackConfinement r1 = new StackConfinement();
Thread thread1 = new Thread(r1);
Thread thread2 = new Thread(r1);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(r1.index);
}
}
-------------------------
栈内保护的数字是线程安全的:10000
栈内保护的数字是线程安全的:10000
13763
是否相同?
public static void main(String[] args) {
String a = "wukong2";
final String b = "wukong";
String d = "wukong";
String c = b + 2;
String e = d + 2;
System.out.println((a == c));
System.out.println((a == e));
}
----------------
true
false
原因:final修饰的 b 在编译期间就被确定,放入常量池中,因此 c 也就被确定,编译期间发现常量池中a与c是一样的,因此 c 直接复用了常量池中的 a。
而 d 在编译期间不能确定,到运行期间赋值操作会在堆中新建对象,因此a与e地址并不相同