1.允许多个线程同时访问:信号量(Semaphore)
从广义的来说,信号量是对锁的扩展。无论是内部锁synchronized还是重入锁ReentrantLock一次都只允许一个线程访问一个资源,而信号量可以指定多个线程,同时访问某一个资源。信号量主要提供以下两个构造器:
public Semaphore(int permits);
public Semaphore(int permits,boolean fair); //第二个参数指定是否公平
在构造信号量对象是,必须指定信号量的准入数。(同时能申请多少个许可。)当每个线程每次只申请一个许可是,这就相当于指定了同时又多少个线程可以访问某一资源,其主要方法有:
public void acquire() throws InterruptedException;
public void acquireUninterruptibly();
public boolean tryAcquire();
public boolean tryAcquire(long timeout, TimeUnit unit);
public void release();
……
下面演示下使用方式:
package thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo implements Runnable{
Semaphore semp=new Semaphore(5);
public static void main(String[] args) {
ExecutorService exec=Executors.newFixedThreadPool(20);
final SemaphoreDemo demo=new SemaphoreDemo();
for(int i=0;i<20;i++)
exec.submit(demo);
}
@Override
public void run() {
try {
semp.acquire();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName());
semp.release();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Thread.sleep(2000); System.out.println(Thread.currentThread().getName());这两行是临界区代码,在上面我申请的信号量个数为5因此只有5个线程能进入临界区,申请信号量使用acquire()在执行完后释放需要使用release()释放信号量。运行可以发现
代码是以5个一组的方式进行输出的,如果出现了信号量申请了但是没有释放的情况那么可进入临界区的线程数目会越来越少直到所有线程都无法访问。
2.ReadWriteLock读写锁
ReadWriteLock是JDK5中提供的读写分离锁。读写分离锁可以有效的帮助减少锁竞争,以提升系统性能。读写锁允许多个线程同时读,但是在写操作和读操作间依然需要互相等待和持有锁。他们的访问约束情况如下:
读-读不互斥:读读之间不阻塞。
读-写互斥:读阻塞写。写也会阻塞读。
写-写互斥:写写阻塞。
如果读操作次数远远大于写操作,那么读写锁可以发挥最大功效提升系统性能。
下面贴上读写锁的Demo
package thread;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.omg.Messaging.SyncScopeHelper;
/**
* 读读(非阻塞)、读写(阻塞)、写写(阻塞)
*
* 以下demo,不用读写锁,要花费20秒。 使用读写锁,远小于花费20秒
*/
public class ReadWriteLockDemo {
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();//实例化读写锁
private static Lock readLock = readWriteLock.readLock();//获取读锁
private static Lock writeLock = readWriteLock.writeLock();//获取写锁
private int value;
public int handleRead(Lock lock){
try{
lock.lock();//模拟读操作
Thread.sleep(1000);//模拟耗时
System.out.println("读"+value+"时间:"+new Date(System.currentTimeMillis()));
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
return this.value;
}
public void handleWrite(Lock lock,int index){
try{
lock.lock();
Thread.sleep(1000);
this.value = index;
System.out.println("写"+value+"时间:"+new Date(System.currentTimeMillis()));
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
final ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
//读线程
Runnable readRunable = new Runnable() {
@Override
public void run() {
readWriteLockDemo.handleRead(readLock);
// readWriteLockDemo.handleRead(lock);
}
};
//写线程
Runnable writeRunable = new Runnable() {
@Override
public void run() {
readWriteLockDemo.handleWrite(writeLock,new Random().nextInt(1000));
// readWriteLockDemo.handleWrite(lock,new Random().nextInt(1000));
}
};
for (int i=0;i<18;i++){
Thread t=new Thread(readRunable);
t.start();
}
for (int i=18;i<20;i++){
Thread t=new Thread(writeRunable);
t.start();
}
}
}
这是以读写锁的形式进行的操作,运行后可以看到读操作是同步进行的,而写操作则是以串行的形式进行的所以总的耗时很短。如果将锁的形式改为重入锁,你会发现它无论是读还是写都是以串行的形式进行的所以总耗时会非常慢。
OK~
3.倒计时器:CountDownLatch
CountDownLatch是一个非常实用的多线程控制工具类。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。
CountDownLatch的构造器接受一个整数做参数,即这个计数器的记数个数。
public CountDownLatch(int count);
下面演示CountDownLatch的使用
package thread;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo implements Runnable{
static final CountDownLatch end=new CountDownLatch(8);
static final CountDownLatchDemo demo = new CountDownLatchDemo();
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(10)*1000);
System.out.println("完成一个");
end.countDown();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService exec=Executors.newFixedThreadPool(8);
for(int i=0;i<8;i++)
exec.submit(demo);
//等待检查
System.out.println("等待检查");
end.await();
System.out.println("检查完成");
exec.shutdown();
System.out.println("什么鬼");
}
}
首先通过static final CountDownLatch end=new CountDownLatch(8);表示有8个线程需要完成任务,然后end.await();表示要等待CountDownLatch上的线程都完成才能继续执行。每次调用end.countDown();则表示一个线程已经完成任务,倒计时器减一。最后的输出结果如:
CountDownLatch的逻辑图如:
主线程会在CountDownLatch上等待,当所有任务完成主线程才能继续执行。
参考 《实战Java高并发程序设计》