并发学习笔记(2)

避免代码块受到并发访问的干扰

java语言提供了两种机制实现这种功能java

  • Synchonized 关键字(调用对象内部的锁)
    synchronized关键字自动提供一个锁以及相关的条件
  • 引入了ReentrantLock类。(显示锁)
  • 更好: JUC框架为这些基础机制提供了独立的类: 线程池,或者高级一点专门作并发的工具的支持

ReentrantLock类 - 锁

Lock 与synchronized 区别

Lock 不是Java语言内置(compared to synchronized),Lock是一个类,经过这个类能够实现同步访问;安全

Lock 和 synchronized有一点很是大的不一样,采用synchronized不须要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完以后,系统会自动让现场释放对锁的占用,而Lock必需要用户手动释放,若是没有主动释放锁,将会产生死锁。 + Lock优缺点多线程

Lock优缺点(compared to Synchronized)

Lock 能完成synchronized所实现的全部功能,并且比synchronized更好的性能。并且没有synchronized简洁。
可是:
1. 若是但愿当获取锁时,有一个等待时间,不会无限期等待下去。
2. 但愿当获取不到锁时,可以响应中断
3. 当读多,写少的应用时,但愿提升性能
4. 获取不到锁时,当即返回 false。获取到锁时返回 true。并发

用ReentrantLock 保护代码块的基本结构以下:框架

myLock.lock();
try{
   critical section
}
finally{
    myLock.unlock(); // 把解锁语句放在finally子句内是相当重要的。若是在临界区的代码抛异常,锁必须被释放。不然,其余线程将永远阻塞。
}

用锁来保护Bank类的transfer方法函数

public class Bank
{
    private Lock bankLock = new ReentrantLock();
    public void transfer(int from, int to, int amount){
        bankLock.lock();
        try{
            accounts[from] -= amount;
            account[to] += amount; 
        }
        finally{
            bankLock.unlock();
        }
    }
}

这个结构确保任什么时候刻只有一个线程进入临界区,一旦一个线程封锁了锁对象,其余任何线程都没法经过lock语句。当其余线程调用lock时,它们被阻塞,直到第一个线程释放锁对象工具

JUC包关于Lock
java.util.concurrent.locks.Lock
- void lock()
获取这个锁;若是锁同时被另外一个线程拥有则发生阻塞。
- void unlock()
释放这个锁
java.util.concurrent.locks.ReentrantLock
- ReentrantLock()
构建一个能够被用来保护临界区的可重入锁
- ReentrantLock(boolean fair)
构建一个带有公平策略的锁。一个公平锁偏心等待时间最长的锁,可是公平的保证会致使大大下降性能。性能

条件对象

一般线程进入临界区,却发如今某一条件知足以后它才能执行。要使用一个条件对象来管理那些已经得到了一个锁可是不能作拥有工做的线程。
好比银行的模拟程序。咱们避免没有足够资金的帐户做为转出帐户. 以下的代码是不能够的,代码有可能在transfer方法以前被中断,在线程在此运行前,帐户余额可能已经低于提款金额了。this

javaif(bank.getBalance(from) >= amount)
    // thread might be deactivated at this point
    bank.transfer(from,to,amount);

因此必须确保没有其余线程再检查余额和转帐活动之间修改金额。经过锁来保护检查与转帐动做的原子性,来作到这一点:线程

javapublic void transfer(int from, int to, int amount){
    backLock.lock();
    try{
        while(accounts[from] < amount){
            // wait
        }
        // transfer funds;
    }
    finally{
        bankLock.unlock();
    }
}

当帐户没有足够的余额的时候,应该作什么?当前线程陷入wait until 另外一个线程向帐户注入了资金。可是锁的排他性致使其余线程没有进行存款操做的机会。这就是为何须要调节对象的缘由。

一个锁对象能够有一个或者多个相关的条件对象。能够用newCondition方法得到一个条件对象。

javaclass Bank{
    private Condition sufficientFunds;
    public Bank(){
        sufficientFunds = bankLock.newCondition();
    }
 }

若是transfer方法发现余额不足,就能够调用sufficientFunds.await() 当前线程被阻塞,并放弃了锁;一旦一个线程调用了await(),它进入了该条件的等待集(进入等待状态)。当锁可用时,该线程不能立刻解除阻塞,相反,它仍然处于阻塞状态,也就是本身不能激活本身,须要另外一个线程调用同一条件的signalAll方法为止。而signalAll方法仅仅是通知正在等待的线程:此时有可能已经知足条件,值得再次去检查该条件。
因此正确的代码是:

javapublic void transfer(int from, int to, int amount){
    backLock.lock();
    try{
        while(accounts[from] < amount)
           sufficientFunds.await();
        // transfer funds;
        ...
       sufficientFunds.signalAll();
    }
    finally{
        bankLock.unlock();
    }
}
  • Condition newCondition()
    返回一个与该锁相关的条件对象。
    java.util.concurrent.locks.Condition
  • void await()
    将该线程放到条件的等待集中
  • void signalAll()
    解除该条件的等待集中的全部线程的阻塞状态
  • void signal()
    从该条件的等待集中随机地选择一个线程,解除其阻塞状态

总结一下有关锁(外部锁)和条件的关键之处:
- 锁用来保护代码片断,任什么时候刻只能有一个线程执行被保护的代码
- 锁能够管理试图进入被保护代码段的线程
- 锁能够拥有一个或多个相关的条件对象
- 每一个条件对象管理那些已经进入被保护代码段但还不能运行的先。


Java Synchronized keywords

synchronized 是java的关键字,也就是说是java语言内置的特性, 是托管给JVM执行的。
java的每个对象都有一个内部锁。若是一个方法用synchronized声明,那么对象的锁将保护整个方法。namely,要调用该方法线程必须得到内部的对象锁。经过使用synchonized 块能够避免竞争条件;synchonized 修饰的同步代码块确保了一次只能一个线程执行同步的代码块。全部其它试图进入同步块的线程都会阻塞,直到同步块里面的线程退出这个块。

用Synchronized保护代码块的基本结构以下:

public synchronized void method(){
...
}

synchronized锁定的是调用这个同步方法的对象。 namely 当一个对象P1在不一样的线程中执行这个同步方法时,不一样的线程会造成互斥,达到同步的效果。可是这个对象所属的类的另外一个对象P2却能调用这个被加了synchonized的方法。
上述代码等同于

public void method(){
synchronized(this){...}
}

this 指的是调用这个方法的对象,可见同步方法实质上是将synchronized做用于object reference -- 拿到了P1对象锁的线程,才能调用调用P1的同步方法。

javaclass Bank{
    private double[] accounts;
    public synchronized void tranfer(int from, int to, int amount) throws InterruptedException{
        while(accounts[from] < amount)
            wait();// wait on intrinsic object lock's single condition
        accounts[from] -= amount;
        accounts[to] += amount;
        notifyAll(); // notify all threads waiting on the condition
    }
}

能够看到synchronized关键字来编写代码要简洁的多。要理解这一代码,再一次重申:每个对象有一个内部锁,而且该锁有一个内部条件。 由内部锁来管理那些试图进入synchronized方法的线程,由条件来管理那些调用wait的线程
java的 synchronized 关键字可以做为函数的修饰符,也可做为函数内的语句,也就是平时说的同步方法和同步语句块.

而不管 synchronized 关键字是加在了方法上仍是对象上,他取得的锁都是对象,而不是把一段代码或者函数当作锁; 每一个对象只有一个锁(lock)与之关联。 实现同步是要很大的系统开销做为代价的,甚至可能形成死锁,因此要尽可能难免无谓的同步控制。

相关条件

内部对象锁只有一个相关条件。wait方法添加一个线程到等待集中,notifyAll/notify方法解除等待线程的阻塞状态。调用wait or notifyall等价于

intrinsicCondition.await()
intrinsicCondition.signalAll()
public synchronized void transfer(int from, int to, double amount) throws InterruptedException{
        while(accounts[from] < amount){
            wait();
        }
        System.out.print(Thread.currentThread());
        accounts[from] -= amount;
        accounts[to] += amount;
        notifyAll();
    }

java.lang.Object
- void notifyAll()
解除那些在该对象上调用wait方法的线程的阻塞状态
java.util.concurrent.locks.Condition
- void await()
将该线程放到条件的等待集中
- void signalAll()
解除该条件的等待集中的全部线程的阻塞状态
- void signal()
从该条件的等待集中随机地选择一个线程,解除其阻塞状态

synchorinzed(缺陷)

- 不能中断一个正在试图得到锁的线程;

若是一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其余线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种状况:

  1)获取锁的线程执行完了该代码块,而后线程释放对锁的占有;
  2)线程执行发生异常,此时 JVM 会让线程自动释放锁。

  那么若是这个获取锁的线程因为要等待 IO 或者其余缘由(好比调用sleep方法)被阻塞了,可是又没有释放锁,其余线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。

- 试图得到锁时不能设定超时;

- 每一个锁仅有单一的条件,可能不够的;

总结

在代码中应该使用哪种呢? Lock 和 Condition对象仍是同步方法?
下面是Core java的一些建议:

  • 最好既不是用Lock/Condition 也不是用synchonized关键字。 在许多状况下可使用JUC包中的一种机制,它会为你处理全部加锁.
  • 若是synchronized关键字适合你的程序,那么尽可能使用它,这样能够减小编写的代码数量,减小出错概率。
  • 若是特别须要Lock/Condition结果提供的独有特性,才使用Lock/Condition

5.18

阻塞队列

对于许多线程问题,能够经过使用一个或多个队列以优雅且安全的方式将其形式化。好比生产者线程向队列插入元素,消费者线程则取出它们。使用队列,能够安全地从一个线程向另外一个线程传递数据。

阻塞队列是一种比lock, synchonized更高级的管理同步的方法。

具体实现:
先定义一个BlockingQueue,队列放共享的资源,而后多个线程取或者存而后直接调用他的函数放入和取出元素就好了.

相关文章
相关标签/搜索