Java中synchronized和Lock实现并发锁
2017-03-06 21:07
597 查看
前言
参考文章:1. Java 多线程:synchronized 关键字用法(修饰类,方法,静态方法,代码块)
2. Java 多线程:Lock 接口(接口方法分析,ReentrantLock,ReadWriteLock)
3. synchronized 与 Lock 的那点事
4. Java并发编程:Lock
5. ReentrantLock(重入锁)以及公平性
参考书籍:《疯狂Java讲义(第二版)》。
synchronized
创建一个账户类Account,有姓名和账户余额属性以及
get、
set方法:
public class Account { private String name; private double money; public Account(String name, double money) { super(); this.name = name; this.money = money; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } }
线程类是实现
Runnable接口的
Bank类,代码如下:
public class Bank implements Runnable { private Account account; private double getMoney; // 本次取钱数量 public Bank(Account account, double getMoney) { super(); this.account = account; this.getMoney = getMoney; } @Override public void run() { getMoney(); } private void getMoney() { if (account.getMoney() >= getMoney) { System.out.println("取钱成功!取出:" + getMoney + " 元!"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } account.setMoney(account.getMoney() - getMoney); System.out.println(account.getName() + "账户余额为:" + account.getMoney() + " 元!"); } else { System.out.println("取钱失败,余额不足!"); } } }
main函数如下:
public class Test { public static void main(String[] args) { Account account = new Account("Rojer", 2000); Bank bank = new Bank(account, 1200); new Thread(bank).start(); new Thread(bank).start(); } }
运行结果如下:
取钱成功!取出:1200.0 元! 取钱成功!取出:1200.0 元! Rojer账户余额为:800.0 元! Rojer账户余额为:800.0 元!
从结果中看出,两个线程都取钱成功了,这样银行是会亏死的,现在要实现一个时间段内只能有一个线程执行取钱操作,可以用
synchronized关键字来进行加锁。
修饰方法
注意,synchronized不能修饰接口中的方法,而且父类中的
synchronized方法被子类继承以后,是没有继承
synchronized的,如果要使用,还需要加上
synchronized关键字。
用
synchronized关键字来修饰
Bank类的
getMoney方法:
private synchronized void getMoney() { if (account.getMoney() >= getMoney) { System.out.println("取钱成功!取出:" + getMoney + " 元!"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } account.setMoney(account.getMoney() - getMoney); System.out.println(account.getName() + "账户余额为:" + account.getMoney() + " 元!"); } else { System.out.println("取钱失败,余额不足!"); } }
运行结果如下:
取钱成功!取出:1200.0 元! Rojer账户余额为:800.0 元! 取钱失败,余额不足!
可以看出程序运行结果和我们期待的一样,只有一个线程取钱成功,第二个线程取钱的时候失败了,因为余额不足。
用
synchronized修饰方法时,所有的
synchronized修饰的方法都被锁住了,只能供当前拥有锁的线程使用,但是其他的非
synchronized修饰的方法没有被锁住,是可以正常调用的。
HashTable中就是这样实现线程安全的,可以参考我的另一篇文章-深入JDK源码之Hashtable。所以说,用synchronized来修饰方法是重量级的。
修饰代码块
修饰代码块就是以下面的方式,用synchronized关键字“包裹”住要同步的代码块:
synchronized (...) { ... }
括号中可以是一个对象,也可以是一个类的class。如果是一个对象的话,那么只对该对象的线程进行加锁,如果是修饰了一个类的class,如上文的
Bank.class,那么无论有多少个
Bank对象,并发时只能有一个对象的一个线程访问同步代码块,其他线程都要等待,但是访问没有
synchronized锁住的代码块不受影响。
锁住class
修改getMoney代码如下所示:
private void getMoney() { synchronized (Bank.class) { if (account.getMoney() >= getMoney) { System.out.println("取钱成功!取出:" + getMoney + " 元!"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } account.setMoney(account.getMoney() - getMoney); System.out.println(account.getName() + "账户余额为:" + account.getMoney() + " 元!"); } else { System.out.println("取钱失败,余额不足!"); } } }
执行程序结果如下:
取钱成功!取出:1200.0 元! Rojer账户余额为:800.0 元! 取钱失败,余额不足!
锁住对象
修改getMoney代码,锁住账户
account对象,如下所示:
private void getMoney() { synchronized (account) { if (account.getMoney() >= getMoney) { System.out.println("取钱成功!取出:" + getMoney + " 元!"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } account.setMoney(account.getMoney() - getMoney); System.out.println(account.getName() + "账户余额为:" + account.getMoney() + " 元!"); } else { System.out.println("取钱失败,余额不足!"); } } }
运行结果如下:
取钱成功!取出:1200.0 元! Rojer账户余额为:800.0 元! 取钱失败,余额不足!
synchronized修饰的是私有的
account对象,因此当前对象的其他线程如果要使用
account对象,就必须先拿到对象的锁才可以。
相比于
synchronized修饰方法,修饰代码段的同步粒度更小,更加轻量。
修饰静态方法
静态方法是所有类的对象共享的,因此用synchronized修饰静态方法,其实就是修饰类,即一个线程拥有该静态方法的监视器的时候,其他所有该类的对象,或者直接调用该静态方法的线程,都需要等待当前线程释放监视器。
Lock
从 Java 5 开始,Java提供了一种更为强大的线程同步机制——通过显示定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当。Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock允许实现更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象。
[align=right]—疯狂Java讲义 [/align]
Lock接口
Lock是
java.util.concurrent.locks包中的一个接口,源码如下:
public interface Lock { // 用来获取锁,如果锁已被其他线程获取,则进行等待。 void lock(); // 如果获取锁成功返回true,否则返回false boolean tryLock(); // 拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。 // 如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态 void lockInterruptibly() throws InterruptedException; // 释放锁 void unlock(); Condition newCondition(); }
ReentrantLock(可重入锁)是
Lock接口的实现类,可重入,也就是可以在当前线程上对资源再加一个锁,相应的有一个值在记录加了几重锁,如果释放锁时没有释放完,则会报错。同时,
ReentrantLock有个特性就是公平性,这个后面再说。
ReadWriteLock接口
还有一个接口ReadWriteLock,顾名思义是读写锁:public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing */ Lock writeLock(); }
ReentrantReadWriteLock实现了
ReadWriteLock接口,如果调用
readLock().lock()的话,被当前线程锁住的资源,其他所有线程都可以进行读,但是不能进行写,也不允许其他线程进行
writeLock().lock()来对当前资源进行加写锁;如果一个线程调用写锁
writeLock().lock(),那么之后当前线程可以进行写操作,其他线程不能同时写。
上面说到的公平性,如果是非公平的,就是JVM决定唤醒哪个等待线程获得锁,而公平的锁,即唤醒当前在等待的线程中,等待时间最长的那个线程获得锁。默认情况下,
ReentrantLock和
ReentrantReadWriteLock都是非公平锁,但是可以设置为公平锁,相对于公平锁,非公平锁有更好的效率,因为公平的获取锁没有考虑到操作系统对线程的调度因素,这样造成JVM对于等待中的线程调度次序和操作系统对线程的调度之间的不匹配,而且对于锁的快速且重复的获取过程中,连续获取的概率是非常高的,而公平锁会压制这种情况,虽然公平性得以保障,但是响应比却下降了。
总结
相比于synchronized的笨重,
Lock更加灵活,如果获取
synchronized锁的线程阻塞了,那么其他等待锁的线程只能等待,十分影响程序效率,而
Lock可以让线程只等待一段时间或者中断线程的等待,还可以加读锁,同时多个线程都可以进行读,效率十分高。
但是
Lock实现锁需要程序员多操点心了,获取
synchronized锁的线程执行完毕之后就会释放锁,不需要手动释放,而且如果线程发生异常,JVM会让线程自动释放锁,但是
Lock锁需要程序员手动释放锁,并且要处理异常。
相关文章推荐
- java并发包中的Condition和Lock 取代Synchronized、wait、notify/notifyAll实现线程的同步与互斥
- java并发之BlockingQueue和Lock以及synchronized
- java中多线程模拟(多生产,多消费,Lock实现同步锁,替代synchronized同步代码块)
- java并发之Lock与synchronized的区别
- java并发编程---synchronized和lock两种锁的比较
- java并发编程入门:synchronized 与 Lock 的那点事
- java并发之BlockingQueue和Lock以及synchronized
- Java并发之Lock的实现原理
- 【Java并发编程】之synchronized和Lock
- Java 并发编程之ReentrantLock和synchronized锁
- java并发锁ReentrantLock源码分析一 可重入支持中断锁的实现原理
- 深入浅出Java并发—锁(Lock)VS同步(synchronized)
- 深入java并发--Lock与synchronized
- JAVA多线程(五)用lock、synchronized、阻塞队列三种方法实现生产者消费者模式
- 线程高级应用-心得5-java5线程并发库中Lock和Condition实现线程同步通讯
- 深入浅出Java并发包—锁(Lock)VS同步(synchronized)
- 【Java之并发】ReentrantLock和synchronized区别
- Java多线程与并发应用-(11)-用Lock+Condition实现1,2,3 三个模块按顺序执行。
- Java三线程按序打印10次ABC (Lock实现与synchronized,wait,notify实现)
- 深入学习java并发编程:Lock与AbstractQueuedSynchronizer(AQS)实现