资讯专栏INFORMATION COLUMN

java并发编程学习8--同步--ReentrantLock

bergwhite / 2007人阅读

摘要:而问题往往就是有多个线程同时在执行步骤。另一个线程有机会执行转账操作,为当前账户打钱。相反的,它处于阻塞状态,直到另一个线程调用同一条件的。唤醒所有处于该条件中的等待线程,这些线程将重新竞争锁。

【条件竞争

在多线程的开发中,两个及其以上的线程需要共享统一数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用一个修改该对象状态的方法,根据线程访问数据的顺序,可能会出现错误的数据结果,这种现象成为条件竞争。因为修改对象状态的方法并不是一个原子操作,通常步骤是:

1. 读取当前状态值到线程工作内存。
2. 在线程工作内存中修改状态值。
3. 将修改后的状态值重新写入主内存。

而问题往往就是有多个线程同时在执行步骤2。

【有两种机制代码受并发访问的干扰

synchronized关键字。

Reentrantlock类。

【Reentrantlock类

可重入的互斥锁,又被称为“独占锁”。Lock和synchronized机制的主要区别:

    1. synchronized机制提供了对与每个对象相关的隐式监视器锁的访问, 并强制所有锁获取和释放均要出现在一个块结构中, 当获取了多个锁时,它们必须以相反的顺序释放。
    2. synchronized机制对锁的释放是隐式的, 只要线程运行的代码超出了synchronized语句块范围, 锁就会被释放;而Lock机制必须显式的调用Lock对象的unlock()方法才能释放锁, 这为获取锁和释放锁不出现在同一个块结构中, 以及以更自由的顺序释放锁提供了可能。
    

public class LockObject {

    private ReentrantLock lock = new ReentrantLock();

    public void lockSet(int value){
        lock.lock();
        try{
            System.out.println("lock writing : " + value);
//            while (1==1){}
        }finally {
            lock.unlock();
        }
    }
    public void unLock(){
        System.out.println("unlock");
    }
}

如果我们把while(1==1){}放开的话,可以看见只有一个线程能进入该方法中,说明锁有效。

【读写锁

不过有一个问题出现了,如果两个线程有写的操作,那么上锁是没有问题的。 但是如果都是读的操作那么还用不用上锁呢?应该不用了,因为锁是很消耗资源的,能不用就不用。基于这样的考虑,jdk提供了读写锁:

private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock writeLock = readWriteLock.writeLock();
private Lock readLock = readWriteLock.readLock();

总结一些读写锁最重要的特点:

多个线程包含至少一个写操作:锁生效。

多个线程全部是读操作:锁不生效。

这样一来,我们就把读写分离开来,在系统查询是大部分操作,如果将读写分离出来,性能就会大大受益。 有人会问:读操作本来就不需要加锁啊,只在写操作上用锁就可以了。如果是这样的话,试想:共享对象正在执行写操作,这时一个读操作执行了, 但是读操作读出来的数据与执行完写操作的数据不一致,这就会影响使用读操作返回值的逻辑,这种现象就是:不可重复读! 是的,数据库中就使用了读写锁的思想,并且划分了事务的隔离级别。

public class LockObject2 {

    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock writeLock = readWriteLock.writeLock();
    private Lock readLock = readWriteLock.readLock();
    //共享数据
    private int shareValue = 100;

    public void setLock(int value){
        writeLock.lock();
        try{
            System.out.println("writing : " + value);
            shareValue = value;
            while (1==1){

            }
        }finally {
            writeLock.unlock();
        }
    }

    public void getLock(){
        readLock.lock();
        try{
            System.out.println("getting : " + shareValue);
            while (1==1){

            }
        }finally {
            readLock.unlock();
        }
    }
}
【常用方法

void lock():获得锁,如果锁同时被另一个线程持有则发生阻塞。

void unlock():释放锁,必须在finally{}中。

【构造方法

ReentrantLock():构建一个可以用来保护临界区的可重入锁对象。

ReentrantLock(boolean fair):构建一个带有公平策略的锁。公平锁偏爱等待时间最长的线程,但是会大大降低性能,所以默认情况下,锁不公平。 貌似公平锁更加的合理,但是公平锁会比常规锁慢很多,只有当你确定自己要做什么并且对于你要解决的问题有一个特定的理由时,才使用公平锁。并且就算是公平锁也无法绝对的公平因为这和线程的调度器有关。

【条件对象

些时候,线程进入临界区之后,发现需要满足一定的条件才可以执行。要使用一个条件对象来管理那些已经获得锁但是不能工作的线程。我们使用银行转账的例子:

 private ReentrantLock bankLock = new ReentrantLock();        
             public void lockTrans(int to ,int amount){            
                   bankLock.lock();           
                   try{               
                         while (this.amount < amount){                    
                              //wait               
                         }            
                    }finally {               
                         bankLock.unlock();           
                    }
             }   

现在,账户没有足够金额的时候,只有等待另一个线程向账户中转账。但是这个线程已经获得了锁,其他线程无法执行转账操作啊。这就是为什么我们要使用条件对象的原因。 一个对象锁,可以有多个相关的条件对象。

private ReentrantLock bankLock = new ReentrantLock();        
             private Condition sufficientFunds = bankLock.newCondition();        
             public void lockTrans(int to ,int amount) throws InterruptedException {           
                  bankLock.lock();           
                  try{               
                        while (this.amount < amount){                   
                             //wait                    
                             sufficientFunds.await();                
                         }            
                   }finally {               
                         bankLock.unlock();           
                   }       
              } 

现在当前线程被阻塞了,并放弃了锁。另一个线程有机会执行转账操作,为当前账户打钱。等待获得锁的线程与调用了await()的线程有本质区别,一旦一个线程调用了await(),它就进入该条件的等待集,当锁可以使用时,它不能马上解锁。相反的,它处于阻塞状态,直到另一个线程调用同一条件的signalAll()。

signalAll():唤醒所有处于该条件中的等待线程,这些线程将重新竞争锁。

 private ReentrantLock bankLock = new ReentrantLock();            
                private Condition sufficientFunds = bankLock.newCondition();            
                public void lockTrans(int to ,int amount) throws InterruptedException {                
                       bankLock.lock();                
                       try{                   
                              while (this.amount < amount){                       
                              //wait                       
                              sufficientFunds.await();                        
                              //处理转账逻辑                        
                              sufficientFunds.signalAll();                    
                              }                
                       }finally {                   
                              bankLock.unlock();                
                       }           
                 }
【完整的转账代码如下
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Bank {

    public static void main(String[] args) {
        Bank b = new Bank(5,100);
        Random random = new Random();
        List cfs = new ArrayList<>();
        for (int i = 0; i < b.size(); i++) {
            int from = i;
            int to = (int) (b.size() * Math.random());
            int max = 100;
            int min = 10;
            int amount = random.nextInt(max) % (max - min + 1) + min;
            cfs.add(CompletableFuture.runAsync(() -> {
                    try {
                        b.transfer(from, to, amount);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                })
            );
        }
        CompletableFuture.allOf(cfs.toArray(new CompletableFuture[cfs.size()])).join();
    }

    private final int[] accounts;
    private Lock bankLock;
    private Condition sufficientFunds;

    public Bank(int n,int initialBalance){
        accounts = new int[n];
        Arrays.fill(accounts,initialBalance);
        bankLock = new ReentrantLock();
        sufficientFunds = bankLock.newCondition();
    }

    //转账逻辑
    public void transfer(int from,int to,int amount) throws InterruptedException {
        bankLock.lock();
        try {
            while (accounts[from] < amount){
                sufficientFunds.await();
            }
            System.out.println(Thread.currentThread());
            accounts[from] -= amount;
            System.out.println("---------------------------------------");
            System.out.println(String.format("%d from %d to %d",amount,from,to));
            accounts[to] += amount;
            System.out.println(String.format("Total balance:%d",getTotalBalance()));
            System.out.println("Detail:");
            for (int i = 0; i < accounts.length; i++) {
                System.out.println(String.format("account[%d]"s amount is : %d",i,accounts[i]));
            }
            System.out.println("---------------------------------------");
            sufficientFunds.signalAll();
        }finally {
            bankLock.unlock();
        }
    }
    public int getTotalBalance(){
        bankLock.lock();
        try{
            int sum = 0;
            for(int a : accounts){
                sum += a;
            }
            return sum;
        }finally {
            bankLock.unlock();
        }
    }

    public int size(){
        return accounts.length;
    }
}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68092.html

相关文章

  • java并发编程学习13--Atomic数据结构简介

    摘要:介绍中无锁的线程安全整数,一个提供原子操作的的类。在语言中,和操作并不是线程安全的,在使用的时候,不可避免的会用到关键字。而则通过一种线程安全的加减操作接口。就是的意思,比较并操作。有个操作数,内存值,旧的预期值,要修改的新值。 【介绍 JAVA 中无锁的线程安全整数 AtomicInteger,一个提供原子操作的Integer的类。在Java语言中,++i和i++操作并不是线程安全的...

    李增田 评论0 收藏0
  • Java并发编程,深入理解ReentrantLock

    摘要:公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。ReentrantLock简介ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁, 支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻...

    番茄西红柿 评论0 收藏0
  • Java并发编程,深入理解ReentrantLock

    摘要:公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。ReentrantLock简介ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁, 支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻...

    番茄西红柿 评论0 收藏0
  • Java并发编程,深入理解ReentrantLock

    摘要:公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,而非公平锁会降低一定的上下文切换,降低性能开销。因此,默认选择的是非公平锁,则是为了减少一部分上下文切换,保证了系统更大的吞吐量。ReentrantLock简介ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁, 支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻...

    fredshare 评论0 收藏0
  • JAVA并发编程之-ReentrantLock锁原理解读

    摘要:作者毕来生微信锁状态转换分类以后帮助我们提供了线程同步机制,通过显示定义同步锁来实现对象之间的同步。等待重新尝试因为在中是用关键字声明的,故可以在线程间可见再次判断一下能否持有锁可能线程同步代码执行得比较快,已经释放了锁,不可以就返回。 作者 : 毕来生微信: 878799579 锁状态转换 showImg(https://segmentfault.com/img/remote/...

    荆兆峰 评论0 收藏0

发表评论

0条评论

bergwhite

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<