从零开始学多线程之共享对象(二)

想要使用多线程编程,有一个很重要的前提,那就是必须保证操纵的是线程安全的类.

那么如何构建线程安全的类呢? 1. 使用同步来避免多个线程在同一时间访问同一数据. 2. 正确的共享和安全的发布对象,使多个线程能够安全的访问它们.

那么如何正确的共享和安全的发布对象呢? 这正是这篇博客要告诉你的.

  1. 多线程之间的可见性问题.
    为什么在多线程条件下需要正确的共享和安全的发布对象呢?

这要说到可见性的问题:

在多线程环境下,不能保证一个线程修改完共享对象的数据,对另一个线程是可见的.

一个线程读到的数据也许是一个过期数据,这会导致严重且混乱的问题,比如意外的异常,脏的数据结构,错误的计算和无限的循环.

举个例子:

public class NoVisibility {
private static int num;
private static boolean ready;

private static class RenderThread extends Thread{
    @Override
    public void run(){
        while(!ready){
            Thread.yield();
        }
        System.out.println("num = " + num);
    }

}

public static void main(String [] args) throws InterruptedException {
        new RenderThread().start();
        num = 42;
        ready = true;

}  

}

new RenderThread().start()表示创建一个新线程,并执行线程内的run()方法 ,如果ready的值是false,执行Thread.yield()方法(当前线程休息一会让其他线程执行),这时候再交给main方法的主线程执行,给num赋值42,ready赋值true,然后在任务线程中输出num的值.因为可见性的问题,任务线程可能没有看到主线程对num赋值,而输出0.

我们接下来来看看发布对象也会引发的可见性问题.

  1. 什么是发布一个对象

发布: 让对象内被当前范围之外的代码所使用.

public class Publish {
public int num1;

private int num2;

public int getNum2(){
    return this.num2;
}

}

无论是 publish.num1 还是 publish.getNum2()哪种方法,只要能在类以外的地方获取到对象,我们就称对象被发布了.

如果一个对象在没有完成构造的情况下就发布了,这种情况叫逸出.逸出会导致其他线程看到过期值,危害线程安全.

常见的逸出的情况:

1.最常见的逸出就是将对象的引用放到公共静态域(public static Object obj),发布对象的引用,而在局部方法中实例化这个对象.

public class Test {
public static Set set;

public void initialize(){
    set = new HashSet<>();
}

}

2.发布对象的状态,而且状态是可变的(没用final修饰),或状态里包含其他的可变数据.

public class UnsafeStates {
private String [] states = new String[]{“a”,“b”,“c”};

public String[] getStates(){
    return states;
}

}

3.在构造方法中使用内部类. 内部类的实例包含了对封装实隐含的引用.

public class UnsafeStates {

private Runnable r;

public UnsafeStates() {
    r = new Runnable() {
        @Override
        public void run() {
             // 内部类在对象没有构造好的情况下,已经可以this引用,逸出了
            // do something;
        }
    };
}

}

逸出主要会导致两个方面的问题:

  1. 发布线程以外的任何线程都能看到对象的域的过期值,因而看到的是一个null引用或者旧值,即使此刻对象已经被赋予了新值.
  2. 线程看到对象的引用是最新的,但是对象的状态却是过期的.

我们已经了解了逸出的问题,那么如何安全的发布一个对象呢?
为了安全地发布对象,对象的引用以及对象的状态必须同时对其他线程可见(也就是说安全发布就是保证对象的可见性),一个正确创建的对象可以通过下列条件安全发布:

  1. 通过静态初始化器初始化对象的引用.

public class NoVisibility {

public static Object obj = new Object();

}

  1. 将它的引用存储到volatile域或AtomicReference;
    public class NoVisibility {

    public volatile Object obj = new Object();

}

Volatile可以保证可见性.性能消耗也只比非volatile多一点,但是不要过度依赖volatile变量,它比使用锁的代码更脆弱,更难以理解,
使用volatile的最佳方式就是用它来做退出循环的条件.
使用volatile的例子:
public class Cycle {
private boolean condition;

public  void loop(){
    while (condition){
        //do something..
    }
}

public void changeCondition(){
    if(condition == true){
        condition = false;
    }else{
        condition = true;
    }
}

}

  1. 将它的引用储存到正确创建的对象的final域中.

public class NoVisibility {

public final Object obj = new Object();

}

  1. 或者将它的引用存储到由锁正确保存的域中.

public class NoVisibility {

private Hashtable<String,Object> hashtable = new Hashtable<>();

public void  setHashtable(){
    Object obj = new Object();
    hashtable.put("obj",obj);
}

}
不限于HashTable,只要是线程安全的容器都行

现在我们了解了如何安全的发布一个对象,那么
问题来了,是否所有对象都需要安全发布?安全发布的对象是否就是线程全的了?

让我们继续往下看.

3.如何构建一个线程安全的类.

我们先来回答上面的第一个疑问,是否所有对象都需要安全发布?
答案都是否定的.

要回答这个问题,我们先简单了解一下以下的三种对象:
1.不可变对象
2.高效不可变对象
3.可变对象

1.不可变对象:创建后不能被修改的对象叫不可变对象,不可变对象天生是线程安全的.

不可变对象不仅仅是所有域都是final类型的,
只有满足如下状态才是不可变对象:

1.1
它的状态不能在创建后改变.(包括状态包含的其他值也不可做修改,比如状态是一个集合list,list里面的值也不可以修改,或者状态是一个对象,那么对象的状态也不更改)

1.2.所有域都是final类型的.

1.3.它被正确创建(创建期间没有this引用的逸出)

2.不可变对象: 技术上是可以改变的,但是实际应用程序中,不会被改变
用高效不可变对象可以简化开发,并由于减少了同步的使用,还会提高性能.

  1. 可变对象: 就是可变对象.

下面就是三种对象的发布机制,发布对象的必要条件依赖于对象的可变性:

  1. 不可变对象可以通过任意机制发布;
  2. 高效不可变对象必须要安全地发布;
  3. 可变对象必须要安全发布,同时必须要线程安全或者是被锁保护.

最后一个问题安全发布的对象是否就是线程全的了?

安全发布只能保证对象发布时的可见性,所以要保证线程的安全就要根据对象的可变性,通过同步+安全发布来保证线程安全.
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
蛋白质是生物体中普遍存在的一类重要生物大分子,由天然氨基酸通过肽键连接而成。它具有复杂的分子结构和特定的生物功能,是表达生物遗传性状的一类主要物质。 蛋白质的结构可分为四级:一级结构是组成蛋白质多肽链的线性氨基酸序列;级结构是依靠不同氨基酸之间的C=O和N-H基团间的氢键形成的稳定结构,主要为α螺旋和β折叠;三级结构是通过多个级结构元素在三维空间的排列所形成的一个蛋白质分子的三维结构;四级结构用于描述由不同多肽链(亚基)间相互作用形成具有功能的蛋白质复合物分子。 蛋白质在生物体内具有多种功能,包括提供能量、维持电解质平衡、信息交流、构成人的身体以及免疫等。例如,蛋白质分解可以为人体提供能量,每克蛋白质能产生4千卡的热能;血液里的蛋白质能帮助维持体内的酸碱平衡和血液的渗透压;蛋白质是组成人体器官组织的重要物质,可以修复受损的器官功能,以及维持细胞的生长和更新;蛋白质也是构成多种生理活性的物质,如免疫球蛋白,具有维持机体正常免疫功能的作用。 蛋白质的合成是指生物按照从脱氧核糖核酸(DNA)转录得到的信使核糖核酸(mRNA)上的遗传信息合成蛋白质的过程。这个过程包括氨基酸的活化、多肽链合成的起始、肽链的延长、肽链的终止和释放以及蛋白质合成后的加工修饰等步骤。 蛋白质降解是指食物中的蛋白质经过蛋白质降解酶的作用降解为多肽和氨基酸然后被人体吸收的过程。这个过程在细胞的生理活动中发挥着极其重要的作用,例如将蛋白质降解后成为小分子的氨基酸,并被循环利用;处理错误折叠的蛋白质以及多余组分,使之降解,以防机体产生错误应答。 总的来说,蛋白质是生物体内不可或缺的一类重要物质,对于维持生物体的正常生理功能具有至关重要的作用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值