为什么要线程同步
因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题。
举例:
不同步代码:
- public class Bank {
- private int count =0;//账户余额
- //存钱
- public void addMoney(int money){
- count +=money;
- System.out.println(System.currentTimeMillis()+"存进:"+money);
- }
- //取钱
- public void subMoney(int money){
- if(count-money < 0){
- System.out.println("余额不足");
- return;
- }
- count -=money;
- System.out.println(+System.currentTimeMillis()+"取出:"+money);
- }
- //查询
- public void lookMoney(){
- System.out.println("账户余额:"+count);
- }
- }
使用同步时的代码
(1)同步方法:
即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
修改后的Bank.java
- public class Bank {
- private int count =0;//账户余额
- //存钱
- public synchronized void addMoney(int money){
- count +=money;
- System.out.println(System.currentTimeMillis()+"存进:"+money);
- }
- //取钱
- public synchronized void subMoney(int money){
- if(count-money < 0){
- System.out.println("余额不足");
- return;
- }
- count -=money;
- System.out.println(+System.currentTimeMillis()+"取出:"+money);
- }
- //查询
- public void lookMoney(){
- System.out.println("账户余额:"+count);
- }
- }
(2)同步代码块
即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
Bank.java代码如下:
- public class Bank {
- private int count =0;//账户余额
- //存钱
- public void addMoney(int money){
- synchronized (this) {
- count +=money;
- }
- System.out.println(System.currentTimeMillis()+"存进:"+money);
- }
- //取钱
- public void subMoney(int money){
- synchronized (this) {
- if(count-money < 0){
- System.out.println("余额不足");
- return;
- }
- count -=money;
- }
- System.out.println(+System.currentTimeMillis()+"取出:"+money);
- }
- //查询
- public void lookMoney(){
- System.out.println("账户余额:"+count);
- }
- }
(3)使用重入锁实现线程同步
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的 锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用
- public class Bank {
- private int count = 0;// 账户余额
- //需要声明这个锁
- private Lock lock = new ReentrantLock();
- // 存钱
- public void addMoney(int money) {
- lock.lock();//上锁
- try{
- count += money;
- System.out.println(System.currentTimeMillis() + "存进:" + money);
- }finally{
- lock.unlock();//解锁
- }
- }
- // 取钱
- public void subMoney(int money) {
- lock.lock();
- try{
- if (count - money < 0) {
- System.out.println("余额不足");
- return;
- }
- count -= money;
- System.out.println(+System.currentTimeMillis() + "取出:" + money);
- }finally{
- lock.unlock();
- }
- }
- // 查询
- public void lookMoney() {
- System.out.println("账户余额:" + count);
- }
- }
(4)使用局部变量实现线程同步
- public class Bank {
- private static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
- @Override
- protected Integer initialValue() {
- // TODO Auto-generated method stub
- return 0;
- }
- };
- // 存钱
- public void addMoney(int money) {
- count.set(count.get()+money);
- System.out.println(System.currentTimeMillis() + "存进:" + money);
- }
- // 取钱
- public void subMoney(int money) {
- if (count.get() - money < 0) {
- System.out.println("余额不足");
- return;
- }
- count.set(count.get()- money);
- System.out.println(+System.currentTimeMillis() + "取出:" + money);
- }
- // 查询
- public void lookMoney() {
- System.out.println("账户余额:" + count.get());
- }
- }
ThreadLocal的原理:
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。现在明白了吧,原来每个线程运行的都是一个副本,也就是说存钱和取钱是两个账户,知识名字相同而已。所以就会发生上面的效果。
ThreadLocal与同步机制
a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题
b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式