Java 并发编程 同步控制 三

来源:互联网 发布:中国移动 plmn 网络id 编辑:程序博客网 时间:2024/06/08 08:25

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();    }}
原创粉丝点击