Lock与synchronized 的区别
ReentrantLock 是 API 级别的,synchronized 是 JVM 级别的
集群部署、分布式部署下,synchronized 不是线程安全的,因为属于不同的JVM
1、lock是可中断锁,支持中断处理,而synchronized 不是可中断锁
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定的三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c) tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly():如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2、synchronized是在JVM层面上实现的,lock是通过代码实现的,JVM会自动释放锁定(代码执行完成或者出现异常),但是使用Lock则不行,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。
3、在资源竞争不是很激烈的情况下,synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReentrantLock的性能能维持常态;
4、与synchronized相比,ReentrantLock提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票。
5、ReentrantLock还提供了条件Condition,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock更加适合(以后会阐述Condition)。
6、ReentrantLock提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而synchronized则一旦进入锁请求要么成功要么阻塞,所以相比synchronized而言,ReentrantLock会不容易产生死锁些。
7、ReentrantLock支持更加灵活的同步代码块,但是使用synchronized时,只能在同一个synchronized块结构中获取和释放。注:ReentrantLock的锁释放一定要在finally中处理,否则可能会产生严重的后果。
synchronized用法
-
修饰一个类:其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象;
-
修饰一个方法:被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
-
修饰一个静态的方法:其作用的范围是整个方法,作用的对象是这个类的所有对象;
-
修饰一个代码块:被修饰的代码块称为同步语句块,其作用范围是大括号{}括起来的代码块,作用的对象是调用这个代码块的对象;
修饰代码块
作用于当前对象实例的代码块
public class PoolUpdater implements Observer {
/**
* Create a ScheduledExecutorService to remove unused DataSources.
*/
public void init() {
if (inited) {
return;
}
// this 为当前对象实例
synchronized (this) {
if (inited) {
return;
}
if (intervalSeconds < 10) {
LOG.warn("CAUTION: Purge interval has been set to " + intervalSeconds
+ ". This value should NOT be too small.");
}
if (intervalSeconds <= 0) {
intervalSeconds = DEFAULT_INTERVAL;
}
executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
LOG.debug("Purging the DataSource Pool every " + intervalSeconds + "s.");
try {
removeDataSources();
} catch (Exception e) {
LOG.error("Exception occurred while removing DataSources.", e);
}
}
}, intervalSeconds, intervalSeconds, TimeUnit.SECONDS);
}
}
}
@SuppressWarnings("unchecked")
static Map<Object, ObjectName> getInstances0() {
Properties properties = System.getProperties();
Map<Object, ObjectName> instances = (Map<Object, ObjectName>) properties.get(SYS_PROP_INSTANCES);
if (instances == null) {
// 共享变量 properties
synchronized (properties) {
instances = (IdentityHashMap<Object, ObjectName>) properties.get(SYS_PROP_INSTANCES);
if (instances == null) {
instances = Collections.synchronizedMap(new IdentityHashMap<Object, ObjectName>());
properties.put(SYS_PROP_INSTANCES, instances);
}
}
}
return instances;
}
修饰静态方法和类
给当前类加锁,会作用于类所有对象实例的代码块
public synchronized static ObjectName addDataSource(Object dataSource, String name) {
final Map<Object, ObjectName> instances = getInstances();
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
synchronized (instances) {
if (instances.size() == 0) {
try {
ObjectName objectName = new ObjectName(MBEAN_NAME);
if (!mbeanServer.isRegistered(objectName)) {
mbeanServer.registerMBean(instance, objectName);
}
} catch (JMException ex) {
LOG.error("register mbean error", ex);
}
DruidStatService.registerMBean();
}
}
ObjectName objectName = null;
if (name != null) {
try {
objectName = new ObjectName("com.alibaba.druid:type=DruidDataSource,id=" + name);
mbeanServer.registerMBean(dataSource, objectName);
} catch (Throwable ex) {
LOG.error("register mbean error", ex);
objectName = null;
}
}
if (objectName == null) {
try {
int id = System.identityHashCode(dataSource);
objectName = new ObjectName("com.alibaba.druid:type=DruidDataSource,id=" + id);
mbeanServer.registerMBean(dataSource, objectName);
} catch (Throwable ex) {
LOG.error("register mbean error", ex);
objectName = null;
}
}
instances.put(dataSource, objectName);
return objectName;
}
public abstract class ElasticSearchConfigBoot {
private static boolean inited = false;
public static void boot(){
boot(false);
}
/**
*
* @param forceBoot 强制启动
*/
public static void boot(boolean forceBoot){
if(inited) {
if(!forceBoot)
return;
}
// 修饰一个类
synchronized (ElasticSearchConfigBoot.class) {
if(inited){
if(!forceBoot)
return;
}
try {
final BaseApplicationContext context = DefaultApplicationContext.getApplicationContext("conf/elasticsearch-boot-config.xml",forceBoot);
String _elasticsearchServerNames = context.getExternalProperty("elasticsearch.serverNames", "default");
String[] elasticsearchServerNames = _elasticsearchServerNames.split(",");
//初始化Http连接池
GetProperties getProperties = new WrapperGetProperties(context);
ClientConfiguration.bootClientConfiguations(elasticsearchServerNames, getProperties );
ClientConfiguration.bootHealthCheckClientConfiguations(elasticsearchServerNames,getProperties);
//初始化ElasticSearchServer
ElasticSearchHelper.booter(elasticsearchServerNames, context,forceBoot,false);
}
finally {
inited = true;
}
}
}
}
修饰普通方法
@Override
public synchronized boolean next() throws SQLException {
if (closed) {
throw new SQLException();
}
if (rowIndex < rows.size() - 1) {
rowIndex++;
return true;
}
return false;
}
Lock的使用
Jdk1.5中,在java.util.concurrent.locks包下,有一组实现线程同步的接口和类,说到线程的同步,很多立马就会想到synchronized关键字,这是java内置的关键字,用来处理线程同步的,但这个关键字有很多的缺陷,使用起来也不是很方便和直观,所以就出现了Lock,下面,我们就来对比着讲解Lock。
synchronized关键字在使用的过程中会有如下几个问题:
1、不可控性,无法做到随心的加锁和释放锁
2、效率比较低下,比如我们现在并发的读两个文件,读与读之间是互不影响的,但如果给这个读的对象使用synchronized来实现同步的话,那么只要有一个线程进入了,那么其他的线程都要等待。
3、无法知道线程是否获取到了锁
以上问题,Lock都可以很好的解决,并且jdk1.5还提供了各种锁,例如读写锁,但有一点需要注意,使用synchronized关键时,无须手动释放锁,但使用Lock必须手动释放锁。下面我们就来学习一下Lock锁。
java.util.concurrent.locks.Lock是一个上层的接口,其原型如下,总共提供了6个方法:
public interface Lock {
// 用来获取锁,如果锁已经被其他线程获取,则一直等待,直到获取到锁
void lock();
// 该方法获取锁时,可以响应中断,比如现在有两个线程,一个已经获取到了锁,另一个线程调用这个方法正在等待锁,
// 但是此刻又不想让这个线程一直在这死等,可以通过调用线程的Thread.interrupted()方法,来中断线程的等待过程
void lockInterruptibly() throws InterruptedException;
// tryLock方法会返回bool值,该方法会尝试着获取锁,如果获取到锁,就返回true,如果没有获取到锁,就返回false,但是该方法会立刻返回,而不会一直等待
boolean tryLock();
// 这个方法和上面的tryLock差不多是一样的,只是会尝试指定的时间,如果在指定的时间内拿到了锁,则会返回true,如果在指定的时间内没有拿到锁,则会返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 实现线程通信,相当于wait和notify,后面会单独讲解
Condition newCondition();
}
那么这几个方法该如何使用了?前面我们说到,使用Lock是需要手动释放锁的,但是如果程序中抛出了异常,那么就无法做到释放锁,有可能引起死锁,所以我们在使用Lock的时候,有一种固定的格式,如下:
// 下面这段代码摘自官方文档
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {// 必须使用try,最后在finally里面释放锁
l.unlock();
}
下面我们来看一个简单的例子,代码如下:
/**
* 描述:Lock使用
*/
public class LockDemo {
// new一个锁对象,注意此处必须声明成类对象,保持只有一把锁,ReentrantLock是Lock的唯一实现类
Lock lock = new ReentrantLock();
public void readFile(String fileMessage) {
lock.lock();// 上锁
try {
System.out.println(Thread.currentThread().getName() + "得到了锁,正在读取文件……");
for (int i = 0; i < fileMessage.length(); i++) {
System.out.print(fileMessage.charAt(i));
}
System.out.println();
System.out.println("文件读取完毕!");
} finally {
System.out.println(Thread.currentThread().getName() + "释放了锁!");
lock.unlock();
}
}
public void demo(final String fileMessage) {
// 创建若干个线程
ExecutorService service = Executors.newCachedThreadPool();
// 提交20个任务
for (int i = 0; i < 20; i++) {
service.execute(new Runnable() {
@Override
public void run() {
readFile(fileMessage);
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
// 释放线程池中的线程
service.shutdown();
}
public static void main(String[] args) {
LockDemo demo = new LockDemo();
demo.demo("Hello World");
}
}
-
我们先把锁的那两行代码注释掉,看下效果如何:
-
然后我们把锁的代码加上,看下效果如何:
如果我们把上面的readFile方法前面加上synchronized关键字,然后把锁去掉,效果是一样的。
tryLock锁的使用和Lock锁的使用类似,下面把示例代码贴出来,就不做过多的说明了,代码如下:
/**
* 描述:TryLock使用
*/
public class TryLockDemo {
// new一个锁对象
Lock lock = new ReentrantLock();
public void readFile(String fileMessage) {
if (lock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + "得到了锁,正在读取文件……");
System.out.println("文件读取完毕!");
} finally {
System.out.println(Thread.currentThread().getName() + "释放了锁!");
lock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + "获取锁失败!");
}
}
public void demo(final String fileMessage) {
// 创建若干个线程
ExecutorService service = Executors.newCachedThreadPool();
// 提交200个任务
for (int i = 0; i < 200; i++) {
service.execute(new Runnable() {
@Override
public void run() {
readFile(fileMessage);
}
});
}
// 释放线程池中的线程
service.shutdown();
}
public static void main(String[] args) {
TryLockDemo demo = new TryLockDemo();
demo.demo("Hello World");
}
}
- 测试效果如下:
参考文档:
- https://blog.csdn.net/fighterandknight/article/details/54906507
- https://blog.csdn.net/fighterandknight/article/details/54945559