线程限制
一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改
共享只读
一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它
线程安全对象
一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它
被守护对象
被守护对象只能通过获取特定的锁来访问
在Java中,有一种对象发布了就是安全的,被称之为不可变对象。
不可变对象可以在多线程中可以保证线程安全
提到不可变的对象就不得不说一下final关键字,该关键字可以修饰类、方法、变量:
final关键字可以修饰类、方法、变量
1、锁定方法不被继承类修改; 2、可提升效率(private方法被隐式修饰为final方法)
基本数据类型变量: 初始化之后不能修改 引用类型变量: 初始化之后不能再修改其引用
可知:编译报错,被final修饰后,基本类型和String的变量无法被修改
package com.artisan.example.immutable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FinalDemo {
// 基本数据类型 int 使用final修饰 验证被final修饰的基本数据类型无法改变
private final static int num = 1;
// String类型 使用final修饰 验证被final修饰的基本数据类型无法改变
private final static String name = "小工匠";
// 引用类型 初始化之后不能再修改其引用,但是可修改其中值
private final static Map<String, Object> map = Maps.newHashMap();
static {
map.put("name", "artisan");
map.put("age", 20);
map.put("sex", "男");
}
public static void main(String[] args) {
// 被final修饰的基本数据类型和String无法改变
// 编译报错: The final field FinalDemo.num cannot be assigned
// num = 2;
// 编译报错: The final field FinalDemo.name cannot be assigned
// name = "artisan";
// 引用对象,此引用无法指向别的对象,但可修改该对象的值
map.put("name", "小工匠");
log.info("name:{}", map.get("name"));
// 验证 方法参数被final修饰的情况
List<String> list = new ArrayList<>();
list.add("我是小工匠");
test2(list);
}
// final修饰传递进来的变量基本类型,不可别改变
private void test(final int a) {
// 不能修改
// 编译报错: The final local variable a cannot be assigned. It must be blank and not using a compound assignment
// a = 2;
log.info("a:{}", a);
}
// final修饰方法传递进来的变量 引用对象,无法指向别的对象,但可修改该对象的值
private static void test2(final List<String> list) {
// 添加数据
list.add("我是artisan");
list.forEach(str ->{
log.info("数据:{}",str);
});
}
}
输出:
需要我们注意的是,final修饰引用类型时,虽然不能将引用再指向别的对象,但可修改该对象的值。 线程不安全
除了final可以定义不可变对象,java提供的Collections类,也可定义不可变对象。
执行后结果如下:
由此可见,用Collections.UnmodifiableMap修饰的对象是不可修改的,如果尝试修改对象的值,在程序运行时会抛出异常。
跟下Collections.UnmodifiableMap的源码
继续看下UnmodifiableMap
主要是将一个新的集合的所有更新方法变为抛出异常
package com.artisan.example.immutable;
import com.artisan.anno.ThreadSafe;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@ThreadSafe
public class GuavaImmutableSetDemo {
// 使用Guava中提供的类来定义不可变对象的集合
// 不可变list
private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3);
// 不可变的set
private final static ImmutableSet<Integer> set = ImmutableSet.copyOf(list);
// 不可变的map,需要以k/v的形式传入数据,即奇数位参数为key,偶数位参数为value
private final static ImmutableMap<String, String> map = ImmutableMap.of("k1", "v1", "k2","v2");
// 通过builder调用链的方式构造不可变的map
private final static ImmutableMap<String, String> map2 = ImmutableMap.<String, String>builder()
.put("key1", "value1")
.put("key2", "value2")
.put("key3", "value3")
.build();
public static void main(String[] args) {
// 修改对象内的数据就会抛出UnsupportedOperationException异常
// 不能添加新的元素 ,运行将抛出 java.lang.UnsupportedOperationException
list.add(4);
// 不能添加新的元素 ,运行将抛出 java.lang.UnsupportedOperationException
set.add(4);
// 不能添加新的元素 ,运行将抛出 java.lang.UnsupportedOperationException
map.put("k3", "v3");
// 不能添加新的元素 ,运行将抛出 java.lang.UnsupportedOperationException
map2.put("key4", "value4");
}
}
上述代码是线程安全的,开发时如果我们的对象可以变为不可变对象,我们尽量将对象变为不可变对象,这样可以避免线程安全问题。
https://github.com/yangshangwei/ConcurrencyMaster