Java 多线程

内置锁

Java提供了内置的锁机制来支持原子性:同步代码块(Synchronized Block)。同步代码块包括两部分:一个是作为锁的对象引用,另一个是作为由这个锁保护的代码块。以synchronized修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或者方法。Java的内置锁是一种互斥锁,这意味着最多只有一个线程能持有这个锁。
内置锁是可重入的,“重入”意味着获取锁的操作的粒度是“线程”,而非“调用”。重入的一种实现方法是,为每个锁关联一个获取计数值和一个所有者线程。
用锁来保护状态
对于可能被多个线程同时访问的可变状态变量,在访问它的时候都需要持有同一个锁,在这种情况下,我们称状态变量是由这个锁保护的。
对象的内置锁与其状态之间没有内在
的关联。虽然大多数类都将内置锁作为一种有效的加锁机制。但对象的域并不一定需要通过内置锁来保护。当获取与对象相关联的锁时,并不能阻止其他线程访问该对象,某个线程在获得对象的锁之后,只能阻止其他线程获得同
一个锁。之所以每个对象都有一个内置锁,只是为了免去显式的创建锁对象。
对于每个包含多个变量的不变性条件
,其中涉及到的所有变量都必须由同一个锁来保护。
虽然synchronized方法可以确保单个操作的原子性,但如果要把多个操作合并成一个复合操作,还是需要添加额外的加锁机制。
通过缩小同步代码块的作用范围,我们既确保servlet的并发行,又能维护线程安全性,要确保同步代码块不要过小,并且不要将本应是原子的操作拆分到多个同步代码块中。应该尽量将不影响共享状态且执行时间较长的操作从同步代码块中分离出去,从而保证这些操作的执行过程中,其他线程可以访问共享状态。

对象的共享

可见性

为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。
失效数据
在访问某个共享且可变的变量时,要求所有线程在同一个锁上同步,确保某个线程写入该变量的值对其他线程来说是可见的,否则读取到的可能是一个失效值。
仅当volatile变量能够简化代码的的实现以及对同步策略的验证时,才应该使用它。volatile变量通常用作某个操作完成、发生中断或者状态的标志,加锁机制既可以保证可见性又可以保证原子性,但是volatile变量只能保证可见性。

发表和逸出

“发布”一个对象的意思是指,使对象能够在当前作用域之外的代码中使用。当某个不应该发布的对象被发布时,这种情况被称为逸出。发布对象的最简单的方法是将对象的引用保存到一个公有的静态变量中,以便任何类和线程都能看见该对象。

public static Set knownSecrets;
public void initialize(){
    knownSecrets = new HashSet();
}

当发布某个对象时,可能会间接地发布其他对象,如果将一个Secret对象添加到secrets集合中,那么同样会发布这个对象。当发布一个对象时,在该对象的非私有域中引用的所有对象同样会被发布。一般来说,如果一个已经发布的对象能够通过非私有的变量引用和方法调用到达其他的对象,那么这些对象也都会被发布。封装能够使得对程序的正确性进行分析变得可能,并使得无意中破坏约束条件变得更难。

安全的对象构造过程

当对象在其构造函数中创建一个线程时,无论是显示创建(通过将它传给构造函数)还是隐式创建(由于Thread或者Runnable是该对象的一个内部类),this引用都会被新创建的线程共享,在对象尚未完全构造之前,新的线程就可以看见它。在构造函数中创建线程并没有错误,但是最好不要立即启动它,而是通过start或者initialize方法来启动。

线程封闭

仅在单线程中访问数据,就不需要同步,这种技术被称为线程封闭。
栈封闭是线程封闭的一种特例,在栈封闭中,只有通过局部变量才能访问对象。局部变量的固有属性之一就是封闭在执行线程中,它们位于执行线程的栈中,其他线程无法访问这个栈。对于基本类型的局部变量,无论如何都不会破坏栈封闭性。由于任何方法都无法获得对基本类型的引用,因此,将Java语言的这种语义就确保了基本类型的局部变量始终封闭在线程内。

不变性

满足同步需求的另一种方法是使用不可变对象,如果某个对象在被创建之后状态就不能改变,那么这个对象就称为不可变对象。线程安全是不可变对象的固有属性之一,他们的不变性条件是由构造函数创建的,只要他们的的状态不改变,那么这些不变性条件就得以维持。

final类型的域是不能修改的(但如果final域所引用的对象是可变的,那么这些被引用的对象是可以修改的),final域能确保初始化过程的安全性,从而可以不受限的访问不可变对象

安全发布的常用模式

可变对象必须通过安全的方式来发布,这通常意味着在发布和使用该对象的线程时,都必须使用同步。
要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。安全发布的方式包括:
-在静态初始化函数中初始化一个对象引用。
-将对象的引用保存到voltile类型的域或者AtomicReferance对象中。
-将对象的引用保存到某个正确构造对象的final类型域中
-将对象的引用保存到一个由锁保护的域中

对象的组合

设计线程安全的类

设计线程安全的三个基本要素
-找出构成对象状态的的所有变量
-找出约束状态变量的不变性条件
-建立对象状态的并发访问管理策略
同步策略定义了如何在不违背对象不变条件或后验条件的情况下对其状态的访问操作进行协同。同步策略规定了如何将不可变性、线程封闭与枷锁机制等结合起来以维护线程的安全性,并且还规定了哪些变量由哪些锁来保护

收集同步需求

你可能感兴趣的:(Java 多线程)