Java并发编程-学习笔记(二)

可见性

可见性是指在一个线程中对字段进行了修改,在其他线程中可以马上看到,即一个线程修改的状态对其他线程是可见的。在同一个线程的不同方法之间传递对象的引用,永远也不会出现内存可见性问题。

volatile关键字可以保证数据的可见性,但不保证操作的原子性。

加锁机制既可以保证可见性,又可以确保原子性。

 

加锁与可见性

加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都可以看到共享变量的最新值,所有执行读操作或写操作的线程都必须在同一个锁上同步

一个写线程释放一个锁之后,另一个读线程随后获取了同一个锁。本质上,线程释放锁时会将强制刷新工作内存中的脏数据到主内存中,获取一个锁将强制线程装载字段的值,这样保证了数据的可见性。

 

volatile变量

当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会讲该变量上的操作与其他内存操作仪器重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量是总会返回最新写入的值。

当且仅当满足以下所有条件时,才应该使用volatile变量:

  • 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值
  • 该变量不会与其他状态变量一起纳入不变条件中
  • 在访问变量时不需要加锁

推荐一篇讲Java内存模型的博客,帮助理解volatile变量。


 

线程封闭

当访问共享的可变数据时,通常需要使用同步,一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。

 

Ad-hoc线程封闭

Ad-hoc线程封闭是指,维护线程封闭性的职责完全由程序实现来承担。

 

栈封闭

在栈封闭中,只能通过局部变量才能访问对象。局部变量的固有属性之一就是封闭在执行线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。

对于基本类型的局部变量,由于任何方法都无法获得对基本类型的引用,所以无论如何都不会破坏栈封闭性。

如果在线程内部上下文中使用非线程安全的对象,那么该对象仍然是线程安全的。

 

ThreadLocal类

ThreadLocal对象只能被同一个线程读写。如果一段代码含有一个ThreadLocal变量的引用,即使多个线程同时执行这段代码,它们也是各自维护自己的ThreadLocal变量,无法访问到其他线程的ThreadLocal变量。

这里推荐篇博客,简单介绍ThreadLocal类的使用。

《Java并发性和多线程介绍》-Java TheadLocal


 

不变性

不可变的对象一定是线程安全的

当满足以下条件时,对象才是不可变的

  • 对象创建后,其状态就不可改变
  • 对象的所有域都是final类型
  • 对象是正确创建的(在对象创建期间,this引用没有逸出)

 Final域

final关键字修饰的基本类型数据,一旦初始化就不能再改变。final修饰的对象引用,则该引用在被初始化后就不能再指向其他对象,但对象的非final域仍可以改变,即final域所引用的对象是可变的,那么这些被引用的对象是可以被修改的。

这篇博客介绍了在新的Java内存模型中,final字段是如何工作的

通过一段代码来展示不可变对象线程安全的发布

  • 变量tmp是线程私有变量,是栈封闭的,对它的操作是线程安全的。
  • FinalFiled的成员变量都是final变量,保证了即使被发布也能保证状态不变。
  • 赋值操作的原子性
  • volatile变量的可见性

以上几点共同保证了该程序的线程安全性,而不需要使用同步锁机制。

发表评论

电子邮件地址不会被公开。 必填项已用*标注