线程安全(面试重点)

1.数据共享原则

线程共享的内存区域:堆区(对象实例)、方法区、运行时常量池区(被加载的类)

线程私有的区域:PC、栈(局部变量)

局部变量是线程私有的,类(静态属性)是线程之间共享的,对象(属性)是线程之间共享的(需要线程持有引用)


线程安全Thread safe:运行结果100%符合预期

java经常说某个类、对象线程是安全的:这个类、对象中的代码已经考虑了处理多线程的问题,如果只是“简单”使用,可以不考虑线程安全问题。

     ArrayList就不是线程安全的,完全没考虑过线程安全的任何问题,无法直接使用在多线程环境(多个线程同时操作同一个ArrayList)。

如何考虑线程安全问题?

1.尽可能让几个线程之间不做数据共享,各干各的,就不需要考虑线程安全问题了。

2.如果非要有共享操作,尽可能不去修改,而是只读操作。

一定会出现线程问题

1.原子性被破坏

2.由于内存可见性问题,导致某些线程读到了脏数据(当前数据与试试不匹配)

3.由于代码重排序导致的线程之间关于数据的配合出问题了。

所以,需要学习一些机制,目标与JVM沟通,避免上述问题发生


什么是线程不安全?

public class phenomenon {
    //定义一个共享属性 ——静态属性的方式来体现
    static int r =0;
    //定义加减的次数
    //COUNT越大,出错的概率越大
    static final int COUNT = 1000000;



    //定义两个线程,分别对r进行加减法操作
    static class Add extends Thread{
        @Override
        public void run() {
            for(int i =0 ;i< COUNT; i++){
                r++;
            }
        }
    }

    static class Sub extends Thread{
        @Override
        public void run() {
            for(int i =0;i<COUNT; i++){
                r--;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Add add = new Add();
        add.start();

        Sub sub = new Sub();
        sub.start();

        add.join();
        sub.join();
        //理论上,被加减了count次,结果应该是0
        System.out.println(r);
    }
}

每次出现的结果都不相同,代码运行的结果不符合预期

线程不安全现象出现的原因:

1.开发者角度 

   (1)多个线程之间操作同一块数据了(数据共享)——不仅仅是内存数据

   (2)至少有一个线程在修改这块共享数据

   即使在多线程的代码中,那些情况不需要考虑线程安全问题?

 a.线程之间互相没有任何数据共享的情况下,天生线程是安全的

 b.线程之间即使有共享数据,但是都做读操作,没有写操作,也是天生线程安全的

 2.系统角度解释

   (1)java代码中的一条语句,很肯对应多条指令

   (2)线程调度可能发生在任意时刻,但是不会切割指令(一条指令只有执行完/完全没有执行)


为啥count越大,出错概率越大?

COUNT越大,线程执行需要跨时间片的概率越大,导致中间出错的概率越大

原子性被破坏是线程不安全的常见的原因


系统角度分析出现线程不安全的原因 ——内存可见性的问题

CPU为了提升数据获取速度,一般在CPU中设置缓存cache

指令的执行速度 >> 内存的读写速度

主存储、主内存:真实内存

工作存储/工作内容:CPU中缓存的模拟(不需要区分L几缓存)

内存可见性:一个线程对数据的操作,很可能其他线程是无法感知的,甚至,在某些情况下,会被优化成完全看不到的结果!

系统角度看线程不安全问题 —— 代码重排序导致的问题

程序:状态机

我们写的程序,往往是经过中间很多环节优化的结果,并不保证最终执行的语言和我们写的语句是一模一样的。

JVM规定了一些重排序的基本原则:happend-before规则

JVM要求,无论怎么优化,对于单线程视角,结果不应该有改变。


学习过的常见类的线程不安全:

ArrayList、LinkedLitst、PriorityQueue、TreeMap、TreeSet、HashMap、HashSet都不是安全的

Vector、Stack、Dictionary、StringBuffer

这几个都是java设计失败的产品

StringBuilder VS StringBuffer的区别:一个线程安全,一个不安全


最违反原子性的场景:

1.read - write场景

i++;

arrat[size] = e;size++;

2.check-update场景

if(a ==10){

a=...;

}

锁lock(synchronized) 也称同步锁

1.语法

修饰方法(普通、静态方法)  ——被修饰后称为同步方法

synchronized int add(){}

2.同步代码块

sychrnoized(引用){

}

加锁机制:

sync void methord(){}   等价于 void methord(){  sync(this){} }

static sync void methord(){} 等价于static void methord(){ sync(类.class){}  }

只要解释清楚同步代码块是如何工作的,就能理解同步方法是怎么工作的

2.锁

理论上是一段数据(一段被多个线程之间互相共享的数据)

static boolean lock = false;

所以一共两种状态{锁上(locked)、打开(unlocked)}

false:unlocked    true:locked

当多个线程都有加锁操作、并且申请的是同一把锁时,会造成 加锁 代码s(临界区)  解锁

临界区代码会互斥着进行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值