线程安全性

什么是线程安全性

  • 当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么久称这个类是线程安全的。

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

  • 无状态对象一定是线程安全的。

原子性

假定有两个操作A 和 B,如果从执行A的线程来看,当另一个线程执行B时,要么将B全部执行完,要么完全不执行B,那么A 和 B对彼此来说都是原子的,原子操作是指:对于访问同一个状态的所以操作(包括该操作本身来说),这个操作是一个以原子方式执行的操作

  • 在并发编程中,这种由于不恰当的执行时序而出现不正确的结果是一种非常严重的情况,它有一个正式的名字:竞态条件(RaceCondition)

  • 竞态条件的本质: 基于一种可能失效的观察结果来做出判断或者执行某个计算

内置锁(synchronized)

synchronized锁可重入

  • 如果某个线程视图获取一个已经由它自己持有的锁,那么这个请求就会成功
  • 重入锁的一种实现方法是,为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时,这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取的计数值置为1。如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数值会相应地递减。当计数值为0时,这个锁将被释放。
  • 关键字 synchronized 拥有锁重入的功能,也就是在使用 synchronized 时,当一个线程得到一个对象锁后, 再次请求该对象时是可以再次得到该对象的锁。

    //======继承上的锁重入, 如果内置锁不是可重入的,那么这段断码将发生死锁====//
    public class Widget{
        public synchronized void doSomething(){
            ...
        }
    }
    
    public class LoggingWidget extends Widget{
        public synchronized void doSomething(){
            Systeme.out.println(toString() + ": calling doSomething")
            super.doSomething();
        }
    }
    
    //===========  synchronized的重入:  同步方法之间的锁重入, 调用方法链 ========//
    public synchronized void method1(){
        System.out.println("method1..");
        method2();
    }
    public synchronized void method2(){
        System.out.println("method2..");
        method3();
    }
    public synchronized void method3(){
        System.out.println("method3..");
    }
    
    
  • 出现异常,锁自动释放
    • 说明:对于web应用程序,异常释放锁的情况,如果不及时处理,可能对你的应用程序业务逻辑产生严重的错误。 例如: 你现在执行一个队列任务,很多对象都去在等待第一个对象正确执行完毕再去释放锁。当时第一个对象由于异常的出现,导致业务逻辑没有正常的执行完毕,就释放了锁,那么可想而知后续的对象执行的都是错误的逻辑。所以这一点一定要引起注意,在编写代码的时候,一定要考虑周全。

synchronized 代码块

  • 使用 synchronized 声明方法在某些情况下是有弊端的, 比如 A线程调用同步方法执行一个很长时间的任务,那么 B线程就必须等到比较长的时间才能执行,这样的情况下可以使用 synchronized 代码块去优化代码执行时间, 也就是通常所说的减小锁的粒度
  • synchronized 可以使用**任意的Object ** 进行加锁,用法比较灵活
  • 另外需要特别注意的一个问题,就是不要使用String 常量加锁,会出现死循环问题。当t1线程进入代码块之后,一直占用当前的资源。 因为常量池的概念,t2现在到这一步并不会获取到一个新的锁。所以t1一直处于循环状态。
// 使用String 常量出现死循环问题
//分别使用new String("abc")和"abc"
synchronized ("abc") {  //这里是一个String类型的常量锁
  try {
    while(true){
      System.out.println("当前线程 : "  + Thread.currentThread().getName() + "开始");
      Thread.sleep(1000);
      System.out.println("当前线程 : "  + Thread.currentThread().getName() + "结束");
    }
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
}

锁对象改变问题

  • 当使用一个对象进行加锁的时候,要注意对象本身发生改变的时候,那么持有的锁就不同。
  • 如果对象本身不发生改变,那么依然是同步的,即使是对象的属性发生了改变。

死锁问题

  • 当t1 持有锁 lock-1, t2 持有锁 lock-2,然后t1 在临界资源中需要lock-2, t2也需要lock-1就会出现死锁问题
//-------------- t1线程 -----------
synchronized (lock1) {
  try {
    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock1执行");
    Thread.sleep(2000);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  synchronized (lock2) { //需要lock2的资源
    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock2执行");
  }
}
//-------------- t2线程 ---------
synchronized (lock2) {
  try {
    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock2执行");
    Thread.sleep(2000);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  synchronized (lock1) {//需要lock1的资源
    System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock1执行");
  }
}

脏读问题

  • 对于同一个变量的读和写操作,为了保证业务的原子性,需要使用完整的 synchronized, 就是在读和写上都加上synchronized, 避免脏读现象.

    public class SynchronizedInteger{
        
        private int value;
        
        public synchronized int get(){
            return value;
        }
        
        public synchronized void set(int value){
            this.value = value;
        }
    }
    
  • 非原子的64位操作

  • 非 volatile 类型的64位数值变量(double 和 long)。Java 内存模型要求,变量的读取操作和写入操作都必须是原子操作,但对于非volatile类型的double 和 long 变量, JVM 允许将64位的读操作和写操作分解成两个32位操作。 当读取一个非 volatile 类型的long变量时, 如果对该变量的读操作和写操作在不同的线程中执行,那么很可能会读取到某个值高32位和另一个值得低32位。因此,即使不考虑失效数据的问题,在多线程程序中使用共享且可变的long和double等类型的变量也是不安全的,除非用关键字volatile来声明他们,或者用锁保护起来。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容