线程汇总(3)

1. Lock锁和Condition条件

Lock接口的3个实现类:
ReentrantLock,ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock。lock必须被显示创建,锁定和释放,加锁和释放锁的方式:

//默认使用非公平锁,如果要使用公平锁,需要传入参数true  
Lock lock = new ReentrantLock();
//........  
lock.lock();  
try {  
     //更新对象的状态  
    //捕获异常,必要时恢复到原来的不变约束  
   //如果有return语句,放在这里  
} finally {  
       lock.unlock();        //锁必须在finally块中释放  
}
  • ReentrantLock和synchronized的比较:

    在JDK1.5中,synchronized是性能低效的。因为这是一个重量级操作,它对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来了很大的压力。于是到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronized的性能并不比Lock差。官方也表示,他们也更支持synchronized,在未来的版本中还有优化余地,所以还是提倡在synchronized能实现需求的情况下,优先考虑使用synchronized来进行同步。

    互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因而这种同步又称为阻塞同步,它属于一种悲观的并发策略,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。synchronized采用的便是这种并发策略。

    基于冲突检测的乐观并发策略,如果没有其他线程争用共享数据,那操作就成功了,如果共享数据被争用,产生了冲突,那就再进行其他的补偿措施(最常见的补偿措施就是不断地重拾,直到试成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步被称为非阻塞同步。ReetrantLock采用的便是这种并发策略。

    Java 5中引入了注入AutomicInteger、AutomicLong、AutomicReference等特殊的原子性变量类,它们提供的如:compareAndSet()、incrementAndSet()和getAndIncrement()等方法都使用了CAS操作。因此,它们都是由硬件指令来保证的原子方法。

  • ReentrantLock相对于synchronized的新特性:

1、等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情,它对处理执行时间非常长的同步块很有帮助。而在等待由synchronized产生的互斥锁时,会一直阻塞,是不能被中断的。

2、可实现公平锁:多个线程在等待同一个锁时,必须按照申请锁的时间顺序排队等待,而非公平锁则不保证这点,在锁释放时,任何一个等待锁的线程都有机会获得锁。synchronized中的锁时非公平锁,ReentrantLock默认情况下也是非公平锁,但可以通过构造方法ReentrantLock(ture)来要求使用公平锁。

3、锁可以绑定多个条件:ReentrantLock对象可以同时绑定多个Condition对象(条件变量),而在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含条件,而ReentrantLock只需要多次调用newCondition()方法即可。而且我们还可以通过绑定Condition对象来判断当前线程通知的是哪些线程(即与Condition对象绑定在一起的其他线程)。

  • 可中断锁

ReetrantLock有两种锁:忽略中断锁和响应中断锁。忽略中断锁与synchronized实现的互斥锁一样,不能响应中断,而响应中断锁可以响应中断。
如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,如果此时ReetrantLock提供的是忽略中断锁,则它不会去理会该中断,而是让线程B继续等待,而如果此时ReetrantLock提供的是响应中断锁,那么它便会处理中断,让线程B放弃等待,转而去处理其他事情。

当用synchronized中断对互斥锁的等待时,并不起作用,该线程依然会一直等待

ReentrantLock lock = new ReentrantLock();  
//...........  
lock.lockInterruptibly();//获取响应中断锁  
try {  
      //更新对象的状态  
      //捕获异常,必要时恢复到原来的不变约束  
      //如果有return语句,放在这里  
} finally {  
    lock.unlock();        //锁必须在finally块中释放  
}  
  • 条件变量实现线程间协作
    Java 5之后,我们可以用Reentrantlock锁配合Condition对象上的await()和signal()或signalAll()方法来实现线程间协作。在ReentrantLock对象上newCondition()可以得到一个Condition对象,可以通过在Condition上调用await()方法来挂起一个任务(线程),通过在Condition上调用signal()来通知任务,从而唤醒一个任务,或者调用signalAll()来唤醒所有在这个Condition上被其自身挂起的任务。另外,如果使用了公平锁,signalAll()的与Condition关联的所有任务将以FIFO队列的形式获取锁,如果没有使用公平锁,则获取锁的任务是随机的,这样我们便可以更好地控制处在await状态的任务获取锁的顺序。与notifyAll()相比,signalAll()是更安全的方式。另外,它可以指定唤醒与自身Condition对象绑定在一起的任务。
class Info{ // 定义信息类  
    private String name = "name";//定义name属性,为了与下面set的name属性区别开  
    private String content = "content" ;// 定义content属性,为了与下面set的content属性区别开  
    private boolean flag = true ;   // 设置标志位,初始时先生产  
    private Lock lock = new ReentrantLock();    
    private Condition condition = lock.newCondition(); //产生一个Condition对象  
    public  void set(String name,String content){  
        lock.lock();  
        try{  
            while(!flag){  
                condition.await() ;  
            }  
            this.setName(name) ;    // 设置名称  
            Thread.sleep(300) ;  
            this.setContent(content) ;  // 设置内容  
            flag  = false ; // 改变标志位,表示可以取走  
            condition.signalAll();  
        }catch(InterruptedException e){  
            e.printStackTrace() ;  
        }finally{  
            lock.unlock();  
        }  
    }  

    public void get(){  
        lock.lock();  
        try{  
            while(flag){  
                condition.await() ;  
            }     
            Thread.sleep(300) ;  
            System.out.println(this.getName() +   
                " --> " + this.getContent()) ;  
            flag  = true ;  // 改变标志位,表示可以生产  
            condition.signalAll();  
        }catch(InterruptedException e){  
            e.printStackTrace() ;  
        }finally{  
            lock.unlock();  
        }  
    }  

    public void setName(String name){  
        this.name = name ;  
    }  
    public void setContent(String content){  
        this.content = content ;  
    }  
    public String getName(){  
        return this.name ;  
    }  
    public String getContent(){  
        return this.content ;  
    }  
}  
class Producer implements Runnable{ // 通过Runnable实现多线程  
    private Info info = null ;      // 保存Info引用  
    public Producer(Info info){  
        this.info = info ;  
    }  
    public void run(){  
        boolean flag = true ;   // 定义标记位  
        for(int i=0;i<10;i++){  
            if(flag){  
                this.info.set("姓名--1","内容--1") ;    // 设置名称  
                flag = false ;  
            }else{  
                this.info.set("姓名--2","内容--2") ;    // 设置名称  
                flag = true ;  
            }  
        }  
    }  
}  
class Consumer implements Runnable{  
    private Info info = null ;  
    public Consumer(Info info){  
        this.info = info ;  
    }  
    public void run(){  
        for(int i=0;i<10;i++){  
            this.info.get() ;  
        }  
    }  
}  
public class ThreadCaseDemo{  
    public static void main(String args[]){  
        Info info = new Info(); // 实例化Info对象  
        Producer pro = new Producer(info) ; // 生产者  
        Consumer con = new Consumer(info) ; // 消费者  
        new Thread(pro).start() ;  
        //启动了生产者线程后,再启动消费者线程  
        try{  
            Thread.sleep(500) ;  
        }catch(InterruptedException e){  
            e.printStackTrace() ;  
        }  

        new Thread(con).start() ;  
    }  
}  
  • 读写锁
    synchronized获取的互斥锁不仅互斥读写操作、写写操作,还互斥读读操作,而读读操作时不会带来数据竞争的,因此对对读读操作也互斥的话,会降低性能。Java 5中提供了读写锁,它将读锁和写锁分离,使得读读操作不互斥。
ReadWriteLock rwl = new ReentrantReadWriteLock();      
rwl.writeLock().lock()  //获取写锁  
rwl.readLock().lock()  //获取读锁  

用读锁来锁定读操作,用写锁来锁定写操作,这样写操作和写操作之间会互斥,读操作和写操作之间会互斥,但读操作和读操作就不会互斥。

2. 阻塞队列

BlockingQueue有多个实现类:ArrayBlockingQueue、DelayQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等

  • ArrayBlockingQueue
    它实现了一个有界队列,当队列满时,便会阻塞等待,直到有元素出队,后续的元素才可以被加入队列。
public class BlockingQueueTest{   
        public static void main(String[] args) throws InterruptedException {   
                BlockingQueue bqueue = new ArrayBlockingQueue(20);   
                for (int i = 0; i < 30; i++) {   
                        //将指定元素添加到此队列中   
                        bqueue.put("加入元素" + i);   //会阻塞
                        System.out.println("向阻塞队列中添加了元素:" + i);   
                }   
                System.out.println("程序到此运行结束,即将退出----");   
        }   
}  

3. CyclicBarrier

CyclicBarrier同样是Java 5中加入的新特性,使用时需要导入Java.util.concurrent.CylicBarrier。

它适用于这样一种情况:你希望创建一组任务,它们并发地执行工作,另外的一个任务在这一组任务并发执行结束前一直阻塞等待,直到该组任务全部执行结束,这个任务才得以执行。

public class CyclicBarrierTest {   
        public static void main(String[] args) {   
                //创建CyclicBarrier对象,  
                //并设置执行完一组5个线程的并发任务后,再执行MainTask任务  
                CyclicBarrier cb = new CyclicBarrier(5, new MainTask());   
                new SubTask("A", cb).start();   
                new SubTask("B", cb).start();   
                new SubTask("C", cb).start();   
                new SubTask("D", cb).start();   
                new SubTask("E", cb).start();  
        }   
}   

/**  
* 最后执行的任务 
*/   
class MainTask implements Runnable {   
        public void run() {   
                System.out.println("......终于要执行最后的任务了......");   
        }   
}   

/**  
* 一组并发任务  
*/   
class SubTask extends Thread {   
        private String name;   
        private CyclicBarrier cb;   

        SubTask(String name, CyclicBarrier cb) {   
                this.name = name;   
                this.cb = cb;   
        }   

        public void run() {   
                System.out.println("[并发任务" + name + "]  开始执行");   
                for (int i = 0; i < 999999; i++) ;    //模拟耗时的任务   
                System.out.println("[并发任务" + name + "]  开始执行完毕,通知障碍器");   
                try {   
                        //每执行完一项任务就通知障碍器   
                        cb.await();   
                } catch (InterruptedException e) {   
                        e.printStackTrace();   
                } catch (BrokenBarrierException e) {   
                        e.printStackTrace();   
                }   
        }   
} 

4. 信号量Semaphore

在操作系统中,信号量是个很重要的概念,它在控制进程间的协作方面有着非常重要的作用,通过对信号量的不同操作,可以分别实现进程间的互斥与同步。当然它也可以用于多线程的控制,我们完全可以通过使用信号量来自定义实现类似Java中的synchronized、wait、notify机制。

Java并发包中的信号量Semaphore实际上是计数信号量,从概念上讲,它维护了一个许可集合,对控制一定资源的消费与回收有着很重要的意义。Semaphore可以控制某个资源被同时访问的任务数,它通过acquire()获取一个许可,release()释放一个许可。如果被同时访问的任务数已满,则其他acquire的任务进入等待状态,直到有一个任务被release掉,它才能得到许可。

public class SemaphoreTest{  
    public static void main(String[] args) {  
    //采用新特性来启动和管理线程——内部使用线程池  
    ExecutorService exec = Executors.newCachedThreadPool();  
    //只允许5个线程同时访问  
    final Semaphore semp = new Semaphore(5);  
    //模拟10个客户端访问  
    for (int index = 0; index < 10; index++){  
        final int num = index;  
        Runnable run = new Runnable() {  
            public void run() {  
                try {  
                    //获取许可  
                    semp.acquire();  
                    System.out.println("线程" +   
                        Thread.currentThread().getName() + "获得许可:"  + num);  
                    //模拟耗时的任务  
                    for (int i = 0; i < 999999; i++) ;  
                    //释放许可  
                    semp.release();  
                    System.out.println("线程" +   
                        Thread.currentThread().getName() + "释放许可:"  + num);  
                    System.out.println("当前允许进入的任务个数:" +  
                        semp.availablePermits());  
                }catch(InterruptedException e){  
                    e.printStackTrace();  
                }  
            }  
        };  
          exec.execute(run);  
    }  
    //关闭线程池  
    exec.shutdown();  
    }  
}  

Semaphore允许并发访问的任务数一直为5,当然,这里还很容易看出一点,就是Semaphore仅仅是对资源的并发访问的任务数进行监控,而不会保证线程安全,因此,在访问的时候,要自己控制线程的安全访问。

5. CountDownLatch的使用

你可能感兴趣的:(Java进阶)