1、Java同步的实现方式
在Java内存模型以及原子性、可见性与有序性-CSDN博客这篇文章中分析了线程安全的底层原理-Java内存模型,这篇文章简单分析线程安全的具体实现方式。
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),相互之间产生冲突,将会导致数据不准确,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
Java中实现同步的方式有以下几种:
1、使用synchronized关键字修饰的同步方法或同步代码块;
同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
2、使用ReentrantLock重入锁实现线程同步;
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和块具有相同的基本行为和语义,并且扩展了其能力。
ReentrantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
3、使用局部变量实现线程同步。
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。ThreadLocal实际上并不能解决线程同步的问题,ThreadLocal只是隔离了各个线程间的变量,根本谈不上同步。
synchronized实现多个线程之间同步的两种方式:
方式一:在方法上使用synchronized关键字
public synchronized void method() {
//do something
}
方式二:在代码块上使用synchronized关键字
synchronized (obj) {
// do something
}
synchronized也可以用在静态方法和静态代码块上。
Lock实现多个线程之间同步的常用方式:
Lock lock = new ReentrantLock();
lock.lcok();
try {
// do something
} finally {
lock.unlock();
}
2、synchronized与Lock的异同
1、Lock(java.util.concurrent.locks包中的类)几乎能完成synchronized实现的所有功能,而且Lock有比synchronized更精确的线程语义和更好的性能,更加灵活,主要体现在tryLock()和lockInterruptibly()方法上。
2、synchronized 是Java语言内置的关键字,Lock是JDK 1.5中出现的一个接口。
3、synchronized会自动释放锁,而Lock一定要求程序员手动释放,并且必须在finally从句中释放。(synchronized是在JVM层面上实现的,JVM会自动释放锁定(代码执行完成或者出现异常),但是使用Lock则不行,Lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。)
4、Lock是可中断锁(lockInterruptibly()方法),而synchronized不是可中断锁。
注:关于Lock对象和synchronized关键字的选择:
a.最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。
b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 。
c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 。
3、Lock中常用的方法
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
A、lock()方法:如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁。某个线程通过lock()方法去获取锁,如果锁被其他线程占用,则该线程会被阻塞,如果调用该线程的interupt()方法,并不会让该线程放弃获取锁。
B、trylock()方法:如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false。
C、tryLock(long timeout, TimeUnit timeUnit)方法:如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false。
D、lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到获取锁定,或者当前线程被自己或别的线程中断。
E、unlock()方法:释放锁,必须手动释放锁,并放在finally块中。
通过上面几个方法可以看出Lock比synchronized加锁灵活,举个例子:假设老王正在忙于工作,突然感觉内急,于是狂奔到洗手间,到门口发现挂着“清洁中暂停使用”的牌子,没办法,老王只好先放弃去洗手间回去忙工作。可能如此反复,老王终于发现洗手间可以使用了,于是......
对于上述这种场景,如果使用synchronized,就会一直尝试获取锁,直到获取成功,也就是当老王发现洗手间暂时无法使用时,就只能乖乖站在门口干等了。如果使用trylock(),老王去洗手间发现暂时无法进入(trylock返回false),于是回去继续忙工作,如此反复,直到可以进入洗手间为止(trylock返回true)。甚至,老王非常急,可以尝试在门口等20秒,不行再去忙工作(trylock(20, TimeUnit.SECONDS);)。
示例1:通过lock()获取锁
Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
示例2:通过tryLock()获取锁
Lock lock = ...;
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}
示例3:通过lockInterruptibly()获取锁
public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
//.....
} finally {
lock.unlock();
}
}