Synchronized关键字可以用于实现:
1.原子性
2.确定临界区
3.内存可见性,一个线程修改了状态,其他等待线程可以立即感知。
可见性:
1.失效数据 线程在check之后act前,数据被另外的线程修改。
2.非原子64位操作 java虚拟机模型,允许将long double等64位的数据,分成高32位和低32位进行操作。可能当计算完高32位后,数据变化,低32位随之变化,之前计算的高32位失效。
3.加锁与可见性 加锁的意义不仅在于互斥,还能确保所有线程在得到锁后,都能看见最新的值。
4.volatile 只保证可见性,访问volatile变量时不会加锁,编译时不会将该变量上的操作,与其他内存操作一起进行重排序。通常用作线程间通信的条件标志。
一般变量:优先读取工作内存,若不存在,则总从主内存中copy一份到工作内存,读写操作都只修改工作内存;
volatile修饰的变量:当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。
使用volatile的条件:
1.对变量的写入操作不依赖当前值(例如自增操作,不保证原子性)
2.该变量不会与其他状态变量一起纳入不变性条件(volatile变量肯定会变化,不具备不变性)
3.在访问变量时,无需加锁
不变性:某个对象在被创建后其状态就不能被修改,那么这个对象就称为不可变对象,不可变对象一定是线程安全的。不可变对象很简单。他们只有一种状态,并且该状态由构造函数来控制。
当满足以下条件时,对象才是不可变的:(1)、对象创建以后其状态就不能改变;(2)、对象的所有域都是final类型;(3)、对象是正确创造的(在对象创建期间,this引用没有溢出)。
发布:通过外部方法(其他类定义的方法,或者非final、private方法)一个对象被传到作用域之外。
逸出:不正确的发布。
this引用逸出是怎样产生的?
它需要满足两个条件:一个是在构造函数中创建内部类(EventListener),另一个是在构造函数中就把这个内部类给发布了出去(source.registerListener)。内部类、匿名内部类都可以访问外部类的对象的域,为什么会这样,实际上是因为内部类构造的时候,会把外部类的对象this隐式的作为一个参数传递给内部类的构造方法,这个工作是编译器做的,它会给内部类所有的构造方法添加这个参数,所以例子里的匿名内部类在构造ThisEscape时就把ThisEscape创建的对象隐式的传给匿名内部类了。
线程封闭:仅在单个线程中访问数据,将可变对象封闭在一个线程里面,自动实现线程安全。
java提供了局部变量和thread local来维持线程封闭,但在开发时仍要注意不要让封闭的对象逸出。
ad-hoc线程封闭:维护线程封闭的职责完全由程序来承担,过于脆弱,不建议使用。
栈封闭:局部变量被封闭在线程栈中,其他线程无法访问,通过局部变量才能访问数据对象,即为栈封闭。 缺点:需要明确哪些局部变量的引用不能逸出,后期维护可能导致逸出。
TreadLocal类:用于防止对可变的单实例变量或全局变量进行共享,提供get set方法,为每次使用该变量的线程返回一个独立的副本。每个线程的变量副本存放在thread中,线程消亡后进行垃圾回收。
4.不变性(final-不变性,volatile-可见性)
不可变对象:对象创建后,状态不能被修改;所有域都是final的;对象被正确创建(没有this逸出)。
性质:一定是线程安全的。
4.1 final:当指向primitive类型或者string时,可以保证不可变性;当指向对象时,仅保证指向的地址空间不变,但是对象的属性是否可变不受该final限制。
4.2 volatile:可变对象需要锁来确保线程安全;不可变对象无法被修改,线程需要更新状态,可以对其副本进行修改,然后将volatile的引用指向新对象,这样可以在不加锁的情况下保证线程安全。
5.3 安全发布的常用模式
安全发布:当对象的引用对所有访问该对象的线程可见时,对象发布时的状态对所有线程也是可见的。
1)在静态初始化函数中初始化一个对象引用(静态初始化由JVM在类初始化阶段完成,JVM内部存在同步机制,可以保障对象被安全的发布)
2)将对象的引用保存在volatile域或AtomicReferance对象中。
3)将对象的引用保存到某个正确构造对象的final类型域中。
4)将对象的引用保存到一个由锁保护的域中(将对象放入线程安全的容器中,如Hashtable, synchronizedMap, ConcurrentMap; vector, CopyOnWriteArrayList, CopyOnWriteArraySet, SynchronizedList, SynchronizedSet; BlockingQueue, ConcurrenLinkedQueue)。
5.4 事实不可变对象:技术上,对于发布的对象能够进行修改,但发布后却不会进行修改的对象。将其看作不可变对象,减少同步,提高效率。
5.5
不可变对象可以通过任意机制发布;
事实不可变对象必须通过安全的方式发布;
可变对象必须通过安全的方式来发布,并且是线程安全的或者由锁来保护。
5.6 安全地共享对象
线程封闭
只读共享:不可变、事实不可变对象
线程安全共享:线程安全的对象在其内部实现同步,线程通过对象的公有接口进行访问,无需额外同步。
保护对象:被保护的对象只能通过持有锁来访问。保护对象包括封装在其他线程安全对象中的对象,以及已经发布的由某个特定锁保护的对象。