web——6.线程安全(面试重点)

目录

线程安全

1.现象:

1.线程不安全示例:

2.线程不安全的原因 

2.保护线程安全的措施——锁lock

1.synchronized锁

2.判断是否互斥:

3.synchronied的解引用操作

4.synchronied加锁的作用:

5.synchronized 锁   VS   juc下的锁

线程状态——阻塞状态(blocked、waiting、timed_waiting)


线程安全

1.现象:

开发者角度:有多个线程&&只有一个线程在修改数据

系统角度:原子性、内存可见性、代码重排序  ——线程调度

1.线程不安全示例:

代码每次运行的结果都不相同,并且与预期结果不符合

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

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

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);
    }
}

2.线程不安全的原因 

1.开发者角度 

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

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

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

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

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

 2.系统角度解释

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

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

2.保护线程安全的措施——锁lock

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

static boolean lock = false;

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

false:unlocked    true:locked

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

临界区代码会互斥着进行

1.synchronized锁

   sync(引用 ->指向的是被当成锁的对象)这里请求锁{...这是临界区(被锁保护起来的代码)..}这里释放锁

sync修饰方法:

a.sync修饰普通方法

视为对“当前对象”加锁  sync(this){...}

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

b.修饰静态方法

视为对静态方法所在的类加锁 sync(类.class){...}

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

请求的结果:

      a.请求成功(该锁,没有线程持有)

          继续花括号内代码的执行

      b.请求失败(该锁,已经被线程持有)

        请求锁的线程会被释放,直到锁被释放,重新去请求锁

//JVM针对每个对象都实现了“锁”的功能

一旦正确加锁:多个县城都尝试加锁&&请求的是同一个把锁,会出现互斥现象

互斥:某一时刻,只有一个线程(请求到锁的线程)在临界区运行指令,其他线程(请求锁失败的线程)在等待

满足互斥的条件:线程都有加锁操作&&同一把锁 锁的是同一个对象

2.判断是否互斥:

1.理论基础

前提条件:

t1线程 s1 = new SomeClass();

t2线程 s2 = new SomeClass();

s3 =s1;

 结果:

 需要注意的是:

同步静态方法就视为对SomeClass.class加锁

SomeClass.class与SomeClass.class.o2不是同一对象

s1与s1.o1也不是同一对象

2.代码实践

 门门 、丢丢互斥,

门门、聪聪不互斥, 毛毛、丢丢不互斥

门门、毛毛在同一线程,丢丢、聪聪在统一线程,不存在互斥问题

3.synchronied的解引用操作

synchronized(ref){...},当ref == null 的时候,结果如何?

隐含着一个解引用(dereference)的操作(通过引用操作引用指向的对象)

如果null,一定会出现空指针异常

 很多时候,异常看起来一堆,只是调用栈比较长

 这两段代码的区别是啥?

加锁“粒度”不同,左边的加锁粒度更细,右边的加锁粒度更粗

粒度不是越粗越好,也不是越细越好,最好是一个需要工程测量的取值

加锁粒度越细,并发性越高


新学了一个copy class

4.synchronied加锁的作用:

1.原子性(90%):通过将需要保证原子性的操作互斥起来

2.可见性(5%)

3.重排序(5%)

5.synchronized 锁   VS   juc下的锁

sync   代码的书写就保证一定有锁的释放

           只有一种类型的锁

           一直请求锁

juc      是可能忘记写lock.unlock(),导致锁一直不释放

           书写更灵活,可以在一个方法加锁,另一个方法中解锁

           锁的类型更灵活

           公平锁/非公平锁      读写锁/独占锁        可重入锁/不可重入锁

           锁的加锁策略更加灵活:1.一直请求锁 2.带中断 3.尝试请求4.带超时的请求

线程状态——阻塞状态(blocked、waiting、timed_waiting)

blocked:专指请求synchrnoized锁失败的状态

waiting            VS                                                       timed_waiting

不带时间                                                                     带时间

 lock.lock()、lock.lockInterruptly()、t.join()          Thread.sleep(...)、t.join(50000)、                                                                                                      lock.tryLock(5s)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值