Java线程同步的几种方式
1.使用synchronized关键字
它的工作是对同步的代码加锁,使得每一次只能有一个线程进入同步块,从而保证线程间的安全性。
synchronized关键字的用法:
(1)同步方法
即有synchronized修饰的方法,由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。代码如:public synchronized void save(){}。
synchronized也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
(2)同步代码块
即有synchronized修饰的代码块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。代码如:synchronized(Object){}
通常没有必要同步整个方法,只用synchronized同步关键代码块即可。
2.使用重入锁ReentrantLock
重入锁使用java.util.concurrent.locks.ReentrantLock类来实现
ReentrantLock是可重入、互斥、实现了Lock接口的锁,这种锁可以反复进入。
常用方法有:
(1)Lock():获得锁,如果锁已经被占用,则等待
(2)LockInterruptibly():获得锁,但优先响应中断
(3)tryLock():尝试获得锁,如果成功,则返回true,失败则返回false。该方法不等待,立即返回。
(4)tryLock(long time, TimeUnit unit):在给定时间内尝试获得锁
(5)unlock():释放锁
ReentrantLock()还有一个可以构造公平锁的构造方法,但会大幅降低程序运行效率。
在重入锁的实现中,主要包含三个要素:
(1)原子状态。原子状态使用CAS操作来存储当前锁的状态,判断锁是否已经被别的线程持有了。
(2)等待队列。所有没有请求到锁的线程,会进入等待队列进行等待。待有线程释放锁后,系统能从等待队列中唤醒一个线程,继续工作。
(3)阻塞原语park()和unpark(),用来挂起和恢复线程,没有得到锁的线程将会被挂起。
3.使用局部变量ThreadLocal
如果使用ThreadLocal管理变量,则每一个使用该变量的线程得到的都只是该变量的副本,副本之间相互独立,这样每一个线程都可以修改自己的变量副本,而不会对其他线程产生影响。
ThreadLocal类的常用方法:
(1)ThreadLocal():创建一个线程本地变量
(2)get():返回此线程局部变量的当前线程副本中的值
(3)initialValue():返回此线程局部变量的当前线程的“初始值”
(4)set(T value):将此线程局部变量的当前线程副本中的值设置为value
ThreadLocal的实现原理:
利用ThreadLocalMap,它是定义在Thread内部的成员。设置到ThreadLocal中的数据,正是写入了这个Map,其中key为ThreadLocal当前对象,value是我们需要的值。ThreadLocalMap类似于WeakHashMap,它的实现使用了弱引用,Java虚拟机在垃圾回收时,如果发现弱引用,会立即回收。ThreadLocalMao内部由一系列Entry构成,每一个Entry都是WeakReference。
在set时,首先获得当前线程对象,然后通过getMap()方法拿到线程的ThreadLocalMap,并将值存入ThreadLocalMap中。
get()时,先获得当前线程的ThreadLocalMap对象,然后通过将自己作为key取得内部的实际数据。
4.使用关键字volatile
volatile关键字为域变量的访问提供了一种免锁机制
当用volatile声明一个变量时,就等于告诉了虚拟机,这个变量极有可能会被某些程序或者线程修改,为了确保这个变量被修改后,应用程序范围内的所有线程都能够“看到”这个改动,虚拟机就会采取写特殊的手段,保持这个变量的可见性等特点。
每次使用被volatile修饰的变量时,都会重新进行计算,而不是使用寄存器中的值.
volatile不能替代锁,也无法保证一些复合操作的原子性(比如无法保证i++的原子操作),也不能用来修饰final类型的变量.