如果数据是非原子性操作,那么必须实现线程的同步化.
在多线程的程序中,不建议使用惰性初始化(检查在运行) 比如if(XX==null){return new XX}这种操作.
PS:原子操作:指的是单独的,不可分割的操作去执行.比如A线程和B线程,从A线程的角度来看,当其他线程执行B线程的时候,要么B完全执行完,要么一点都没有执行;在java中 i++这种操作并不是原子性的,它分为3个步骤完成,读取i值 i+1 然后写入i值,所以i++不是原子操作而是复合操作,AtomicXX类执行的操作都属于原子操作.(java.util.concurrent.atomic)
非原子操作会产生不可预计的错误,并不是每个操作都是原子性的,如何使复合操作变为原子操作.每个对象都会有个内部锁,当锁被线程占有的时候其他线程是不可以访问这个对象的,这样就使线程的复合操作在完整的运行期间占有锁,以确保其行为是原子性的,如何让线程取得锁,可以使用一下两种方法:
(1)使用synchronized关键字 对内部锁加锁
a) Synchronized方法:public synchronized void methodName();
b) Synchronized代码块:synchronized(要获取锁的对象){},synchronized代码块,相比synchronized方法来说它保护的范围要小,因此,其他线程可以执行较复杂运行时间较长的操作,待到必须执行同步是才同步,这节约了反应时间.使程序运行的速度更快,但是不能将一个原子操作,分解到多个synchronized块中.因为加锁和释放锁都需要开销,所以把多个简单的需要原子行为的操作分开放入不同的synchronized块中是不可取的,虽然看上去更能体现出多个原子行为.
(2)也可以使用Lock对象.Lock是一个接口它的实现有ReentrantLock(互斥锁)
a) 先介绍ReentrantLock方法lock()是获取锁,unlock()方法释放锁,tryLock()方法会试图获取锁如果可以获取锁则返回true 如不可以获取锁则返回flase和lock()区别是lock()如果锁不可用会阻塞线程直至获取该锁,而tryLock方法会直接返回无论是否锁都立即返回,lock和tryLock都有一个重载的方法就是可以设定获取锁的等待时间如果超过等待时间则立即返回.lockInterruptibly()是可以被中断的获取锁的方法.
我们要在finally中释放锁,这是必须要做的.也是Lock的危险所在,通常你会忘记释放掉.
ReentrantLock的构造可以选择创建公平锁(true)还是非公平锁(flase,默认是非公平锁),线程获取锁是按照请求顺序形成队列获取锁的,但是非公平的锁可以跳跃队列直接获取锁,所以非公平的锁只有在锁被占用的情况才会等待.印象中公平锁是好的而非公平锁是坏的,这是大家在看到这两种锁的印象.但是,公平因为挂起和重新开始线程要带来更大的开销相比非公平锁而言,所以在竞争激烈的时候使用非公平锁性能会更好.
Lock也有内部锁的wait和notify/notifyAll机制,使用Lock.newCondition()获得Condition对象对应的方法是await,signal,signalAll.(注意别使用错了,Condition也有wait等方法因为类都是继承自Object)
(3)synchronized和ReentrantLock选择
因为在java6中的对synchronized的优化,还有ReentrantLock必须每次都在finally中释放锁.所以在内部锁不能满足使用时,ReentrantLock应该在定时获取锁,可中断获取锁的操作中使用ReentrantLock,虽然synchronized比Reentrantlock在性能上还是有差距但是这种差距在不断被缩小,所以在没有以上情况,应该尽量选择synchronized.
(4)读写锁(ReentrantReadWriteLock)
读写锁也分为公平的和非公平的(构造设置)(默认的是非公平)
它的readLock(),writeLock()获取读写锁,读写锁允许读读并发操作,不允许读写和写写的并发操作.
如果读者获得锁,而另一个线程请求获取写锁,那么线程将不允许读者获取读锁了,直到写入被处理完.
锁降级:
重入还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的.
在读取时允许其他线程读取,一旦有线程要占用写入锁,读取将不可用.
(5)通常我们对一个可以被多个线程访问的变量,所有访问它的线程在执行时都必须获取这个对象锁才可以改变这个变量.这样的变量是受锁保护的是安全的.使用锁的时候应该你应该清楚代码块中的功能是否耗时.
可见性:有的时候对于一个变量,一个线程写入该变量,一个线程读取该变量,读取的线程可能永远看不到写入的变量.(由于各种原因缓存啦.什么的.)这时就需要使用同步.这看起来是很荒唐的.所以一个变量被多个线程使用时,使用同步是必须的.记得并不是写才需要同步,读写的方法都需要进行同步.
64位数据(long,double)的同步:JVM默认会对64位数据分化为两个32位数据进行操作,可以使用锁或者volatile关键字声明两种方式进行同步保护,volatile不会加锁,不会引起现场阻塞..所以volatile只能保证可见性,不能保证原子化.
Volatile使用的情况:写入的变量不依赖变量当前值,