Java 并发编程 同步控制 三

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();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值