1、 重入锁 ReentrantLock
synchronized 的重入锁: ReentrantLock。 重入锁需要手动进行加锁和解锁: lock 和unlock。
public class ReentrantLockTest implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static int i=0;
public void run() {
for(int j=0;j<10000;j++){
lock.lock();
try{
i++;
}finally{
lock.unlock();
}
}
}
public static void main(String args[]){
ReentrantLockTest test = new ReentrantLockTest();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
Thread t3 = new Thread(test);
t3.start();
t1.start();
t2.start();
try {
t1.join();
t2.join();
t3.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(i);
}
}
重入锁相对于 synchronized 还可以相应中断。
public class IntLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;//线程的私有的属性,每个线程都有每个线程的值, 这个值线程安全吗,
// 如果是Static,那么每个对象的值都一样了。
public IntLock(int lock) {
this.lock = lock;
}
public void run() {
try {
System.out.println(Thread.currentThread().getId()+ ">>>"+lock);
if (lock == 1) {
lock1.lockInterruptibly();
try{
Thread.sleep(500);
}catch(InterruptedException e){}
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getId()+"执行");
System.out.println(Thread.currentThread().getId()+ "执行>>>"+lock);
}else{
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getId()+ ">>>"+lock);
//lock2.lock();
try{
Thread.sleep(500);
}catch(InterruptedException e){}
//lock1.lock();
lock1.lockInterruptibly();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(lock1.isHeldByCurrentThread()){
lock1.unlock();
}
if(lock2.isHeldByCurrentThread()){
lock2.unlock();
}
System.out.println(Thread.currentThread().getId()+":线程退出");
}
}
public static void main(String args[]) throws InterruptedException{
IntLock r1 = new IntLock(1);
IntLock r2 = new IntLock(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(1000);
t2.interrupt();
}
线程T1 和T2 启动后,t1先占用locl1,再占用lock2; T2正好相反。 因此很容易形成t1和t2 之间的互相等待。 在这里,对锁的请求,统一使用lockInterruptibly() 方法,这是一个可以对中断进行响应的锁申请动作,即在等待锁的过程中,可以响应中断。
锁申请等待限时
lock.tryLock(5,TimeUnit.SECONDS) 可以在锁请求中,设置等待的时间。
tryLock 方法也可以不带参数直接运行。在这种情况下,当前线程会尝试获得锁,如果锁并未并其他线程占用,则申请锁会成功,并立即放回true。 如果锁别其他线程占用,则当前线程不会进行等待,而是立即返回false。这种模式不会引起线程等待,因此也不会产生死锁。
重入锁还有一个锁类型就是可以设置公平锁,默认情况是非公平锁。
构造函数为
public ReentrantLock(boolean fair)
设置为true就是公平锁。 实现公平锁要求系统维护一个有序队列,以保证每个线程按照顺序进行获得锁。
ReetrantLock几个重要方法如下:
lock :获得重入锁,如果锁已经被占用,则等待。
lockInterruptibly 获得锁,但优先响应中断。
trylock 尝试获得锁,如果成功返回true,失败返回flase。该方法不等待,立即返回。
tryLock(long time,TimeUnit unit) 在给定时间内尝试获得锁
unlock 释放锁
在重入锁的实现中,主要包含三个要素:
第一个 :原子状态。 原子状态使用CAS来存储当前锁的状态。判断锁是否已经被别的线程持有。
第二个: 等待队列,所有没有请求到锁的线程,会进入等待队列进行等待。待有线程释放锁后,系统就能从等待队列中唤醒一个线程,继续工作。
第三: 阻塞原语;park 和unpark ,用来挂起和恢复线程。没有得到锁的线程将会被挂起。
2、Condition条件
Condition 和wait和notify的方法的作用大致相同。 Condition.newCondition 方法可以生成一个与当前重入锁绑定的Condition实例。
Condition接口方法如下:
await() throws InterruptedException;// 会使当前线程等待,同时释放当前锁,当其他线程中使用signal 或者 signalAll方法时,线程会重新获得锁并继续执行。或者当前线程被中断时,也能跳出等待。
awaitUninterruptibly() // 此方法 不会在等待过程中相应中断
awaitNanos(long nanosTimeout) throws InterruptedExcetion;
boolean await (long time,TimeUnit unit)throws InterruptedException;
boolean awaitUntil(Date deadline) throws IntertupredException;
void signal()// 唤醒一个在等待中的线程。
void signalAll();
jdk中的阻塞队列就是通过 Condition实现的。
BlockingQueue:阻塞队列
ArrayBlockingQueue 是基于数组实现的,适合做有界队列。
LIinkedBlockingQueue 基于链表 适合做无界队列
BlockingQueue之所以适合作为数据共享的通道,关键在于Blocking上。 当服务线程,处理完成队列中所有消息后,它如何知道下一条信息何时到来呢?
BlockingQueue很好的解决了这个问题,它让服务线程在队列为空时,进行等待,当有新的消息进入队列后,自动将线程唤醒。
那么它是如何实现呢? 以ArrayBlockingQueue为例,来一探究竟。
ArrayBlockingQueue的内部元素都放置在一个对象数组中:
final Object [] items;
向队列中压入元素可以使用offer 和put方法。对于offer方法,如果当前队列已经满了,它就立即返回false。 如果没有满,则执行正常的入队操作。所以我们不讨论这个方法。我们需要关注的是put方法,put方法也是将元素压入队列队尾。但如果队列满了,他会一直等待,直到队列中有空闲的位置。
从队列中弹出元素可以使用poll方法和take方法。 他们都可以从队列的头部获得一个元素,不同之处在于:如果队列为空,poll方法直接返回null。而take方法会等待,直到队列内有可用元素。
因此,put方法和take 方法才是体现blocking的关键。为了做好等待和通知两件事,在ArrayBlockingQueue内部定义了以下一些字段:
final ReentrantLock lock;
pirvate final Condition notEmpty;
private final Condition notFull;
当执行take操作时,如果队列为空,则当前线程等待在notEmpty 上,新元素入队时,则进行一次notEmpty上的通知。
public E take() throws InterruptedException{
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try{
while(count==0)
notEmpty.await();
return extract();
}finally{
lock.unlock();
}
}
private void insert(E e){
items[putIndex]=x;
putIndex=inc(putIndex);
++count;
notEmpty.signal();//发送一个队列不空的信号,此时take方法被唤醒。
}
同理,对于put操作,也是一样,当队列满时,需要让压入线程等待。
public void put(E e) throws InterruptedException{
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try{
while(count==items.length)
notFull.await();
insert(e)
}finally{
lock.unlock();
}
}
当有元素从队列中被挪走,队列中出现空位时, 自然也需要通知等待入队的线程:
private E extract() {
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal();//发送一个不队列未满的信号
return x;
}
3、允许多个线程同时访问:信号量 Semaphore
信号量为多线程协作提供了更为强大的控制方法。 广义上说,信号量是对锁的扩展。无论是内部锁synchronized还是重入锁ReentrantLock,一次都只能允许一个线程访问一个资源,而信号量却可以指定多个线程,同时访问某一个资源。
构造函数如下
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
在构造信号量对象时,必须指定信号量的准入数,即同时能申请多少个许可。当每个线程只申请一个许可时,这就相当于指定了同时有多少个线程可以访问某一个资源。信号量的主要逻辑方法有:
public void acquire() //尝试获得一个准入的许可。 若无法获得,则线程会等待,直到有线程释放一个许可或者当前线程被中断。
acquireUninterruptibly // 方法和acquire方法类似,但是不响应中断。
tryAcquire//尝试获得一个许可,如果成功返回true,如果失败返回false,它不会进行等待。
release //用于在线程访问资源结束后,释放一个许可,以使其它等待许可的线程可以进入资源的访问。
public class SemaphoreTest implements Runnable{
final Semaphore semp = new Semaphore(5);
public void run() {
try {
semp.acquire();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId()+": done!");
semp.release();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String args[]){
ExecutorService exec = Executors.newFixedThreadPool(20);
final SemaphoreTest demo = new SemaphoreTest();
for(int i=0;i<20;i++){
exec.submit(demo);
}
exec.shutdown();
}
}