携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第30天,点击查看活动详情
线程安全
首先看下 Brian Goetz 的书 《Java Concurrency In Practice》中对线程安全的解释:
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行问题,也不需要进行额外的同步,而调用这个对象的行为都可以获得正确的结果,那这个对象便是线程安全的。
所以,对于线程安全,就是说在多线程情况下,没有加锁限制或者其他限制,然后同时访问某对象都能得到正确的结果。
线程安全问题
- 多线程同时操作一个变量导致运行结果错误
public class ThreadTest {
static Integer count =0;
public static void main(String[] args) throws InterruptedException {
Runnable r = new Runnable() {
@Override
public void run() {
for (int i =0; i<1000;i++){
count ++;
//System.out.println(count);
}
};
Thread th1 = new Thread(r);
th1.start();
Thread th2 = new Thread(r);
th2.start();
th1.join();
th2.join();
System.out.printf("获取值%d\n",count);
}
}
// 输出结果
获取值:1314
最终结果远小于2000,而且每次执行的结果都不一样。主要因为每个线程执行时得到CPU时间片不一致导致,且出现线程暂停执行时会将 CPU 资源让给其他线程,这样就可能产生线程安全问题。
count ++ 这个过程其实是执行了三个操作:
1、count =1
2、count+1
3、count = 2
这样线程如果执行到count+1 时被其他线程得到执行权,就会使得 count 还是=1 然后继续执行,最后切回线程count =2 这样就是产生线程安全问题,导致数据结果粗线问题。
- ** 发布和初始化导致线程安全问题**
对象创建后并将其发布和初始化时,另一个线程抢占,获取对象的值,就会导致取值为空。所以如果在操作时间点不对的话就可能导致线程安全的问题。
public class ThreadTest {
private Map<Integer,String> stus;
public ThreadTest(){
new Thread(new Runnale(){
public void run(){
stus = new HashMap<>();
stus.put(1,"Tom");
stus.put(1,"Terry");
stus.put(1,"Marry");
}
}).start()
}
public static void main(String[] args) throws InterruptedException {
ThreadTest initThread= new ThreadTest();
String name = initThread.getStus().get(1);
}
}
线程启动需要一定时间的,但在 main 函数中没有进行等待就直接获取值,这样就会导致获取的值为null 。
- 程序始终无法获取到最终的结果
前2种线程安全问题是带来数据错误或报错,但是第三种线程安全问题是程序始终得不到运行的最终结果。比如遇到死锁、活锁和饥饿。也成为活跃性问题。
死锁:两个线程互相等待对方的资源,但又同时不互相让。
活锁:线程之间没有被限制,都正常运行,但一直都不能得到结果。
饥饿:某线程一直都得不到资源,一直无法运行。