《Java多线程编程核心技术》学习笔记(三)——Lock、Timer、单例模式

Lock的使用

使用ReentrantLock类

在Java多线程中,可以使用synchronized关键字来实现线程之间同步互斥,但在JDK1.5中新增加了ReentrantLock类也能达到同样的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定、多路分支通知等功能,而且在使用上也比synchronized更加灵活

调用ReentrantLock对象的lock()方法获取锁,调用unlock()方法释放锁

使用Condition实现等待/通知

类ReentrantLock通过Condition对象可以实现等待/通知模式

Condition类是JDK5中出现的技术,有更好的灵活性,比如可以实现多路通知功能,就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活

    public class MyService{

        private Lock lock = new ReentrantLock();
        //创建多个Condition实例
        public Condition conditionA = lock.newCondition();
        public Condition conditionB = lock.newCondition();
        public void awaitA(){
            try{
                lock.lock();//持有锁
                System.out.println("begin awaitB时间为"+System.currentTimeMillis()+" ThreadName="+Thread.currentThread().getName());
                conditionA.await();//相当于wait()
                System.out.println("end awaitA时间为"+System.currentTimeMillis()+" ThreadName="+Thread.currentThread().getName());
            }catch(InterruptedException e){
                e.printStackTrace();
            }finally{
                lock.unlock();//释放锁
            }
        }

        public void awaitB(){
            try{
                lock.lock();//持有锁
                System.out.println("begin awaitB时间为"+System.currentTimeMillis()+" ThreadName="+Thread.currentThread().getName());
                conditionA.await();//相当于wait()
                System.out.println("end awaitB时间为"+System.currentTimeMillis()+" ThreadName="+Thread.currentThread().getName());
            }catch(InterruptedException e){
                e.printStackTrace();
            }finally{
                lock.unlock();//释放锁
            }
        }

        public void signalAll_A(){
            try{
                lock.lock();
                System.out.println("  signalAll_A时间为"+System.currentTimeMillis()+" ThreadName="+Thread.currentThread().getName());
                conditionA.signalAll();//相当于notifyAll()
            }finally{
                lock.unlock();
            }
        }

        public void signalAll_B(){
            try{
                lock.lock();
                System.out.println("  signalAll_B时间为"+System.currentTimeMillis()+" ThreadName="+Thread.currentThread().getName());
                conditionB.signalAll();//相当于notifyAll()
            }finally{
                lock.unlock();
            }
        }

    }

    //线程A
    public class ThreadA extends Thread{

        private MyService service;
        public ThreadA(MyService service){
            super();
            this.service = service;
        }

        @Override
        public void run(){
            service.awaitA();//将线程A注册到conditionA实例中
        }

    }

    //线程B
    public class ThreadB extends Thread{

        private MyService service;
        public ThreadA(MyService service){
            super();
            this.service = service;
        }

        @Override
        public void run(){
            service.awaitB();//将线程B注册到conditionB实例中
        }

    }

    //Run类
    public class Run{

        public static void main(String[] args) throws InterruptedException{
            MyService service = new MyService();
            ThreadA a = new ThreadA(service);
            a.setName("A");
            a.start();
            ThreadB b = new ThreadB(service);
            b.setName("B");
            b.start();
            Thread.sleep(3000);
            service.signalAll_A();//仅唤醒绑定在conditionA实例上的线程(线程A)
        }

    }

公平锁与非公平锁

  • 公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序
  • 非公平锁是一种获取锁的抢占机制,是随机获取锁的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁

ReentrantLock lock = new ReentrantLock(isFair);//true为公平锁,false为非公平锁

ReentrantLock中的方法

  • getHoldCount():查询当前线程保持此锁定的个数,也就是调用lock()方法的次数
  • getQueueLength():返回正等待获取此锁定的线程估计数,也就是同时在等待当前线程释放lock锁的线程数量
  • getWaitQueueLength(Condition condition):返回等待与此锁定相关的给定条件Condition的线程估计数
  • hasQueuedThread(Thread thread):查询指定的线程是否正在等待获取此锁定
  • hasQueuedThreads():查询是否有线程正在等待获取此锁定
  • hasWaiters(Condition condition):查询是否有线程正在等待与此锁定有关的condition条件
  • isFair():判断是不是公平锁
  • isHeldByCurrentThread():查询当前线程是否保持此锁定
  • isLocked():查询此锁定是否由任意线程保持
  • lockInterruptibly():如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常
  • tryLock():仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定,返回boolean
  • tryLock(long timeout,TimeUnit unit):如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定

Condition中的方法:

  • awaitUninterruptibly():下、线程中断时仍可以保持正常运行
  • awaitUntil():直到…才被唤醒

    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.SECOND,10);
    condition.awaitUntil(calendar.getTime());
    

使用ReentrantReadWriteLock类

类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务。这样做虽然保证了实例变量的线程安全性,但效率却非常低下。

读写锁ReentrantReadWriteLock类可以加快运行效率,在某些不需要操作实例变量的方法中,完全可以使用读写锁ReentrantReadWriteLock来提升该方法的代码运行速度

读写锁有两个锁,一个是读操作相关的锁,称为共享锁,另一个是写操作相关的锁,也叫排他锁。多个Thread可以同时进行读取操作,但同一时刻只允许一个Thread进行写入操作

    public class Service{

        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        public void read(){
            try{
                try{
                    lock.readLock().lock();
                    System.out.println("获得读锁"+Thread.currentThread().getName()+" "+System.currentTimeMillis())
                    Thread.sleep(10000);
                }finally{
                    lock.readLock().unlock();
                }
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }

        public void write(){
            try{
                try{
                    lock.writeLock().lock();
                    System.out.println("获得写锁"+Thread.currentThread().getName()+" "+System.currentTimeMillis());
                    Thread.sleep(10000);
                }finally{
                    lock.writeLock().unlock();
                }
            }catch(InterruptedException e)
        }

    }

    //Run类
    public class Run{

        public static void main(String[] args) throws InterruptedException{
            Service service = new Service();
            ThreadA a = new ThreadA(service);
            a.setName("A");
            a.start();
            Thread.sleep(1000);
            ThreadB b = new ThreadB(service);
            b.setName("B");
            b.start();
        }

    }

只要出现写操作就是互斥的

定时器Timer

  • 如何实现指定时间执行任务
  • 如何实现按指定周期执行任务

定时器Timer的使用

在JDK库中Timer类主要负责计划任务的功能,也就是在指定时间开始执行某一个任务

Timer类的主要作用就是设置计划任务,但封装任务的类却是TimerTask类,执行计划任务的代码要放入TimerTask的子类中,因为TimerTask是一个抽象类

Timer中的主要方法:

  • schedule(TimerTask task,Date time):在指定的日期执行一次某一任务

    private static Timer timer = new Timer();
    static public class MyTask extends TimerTask{
        @Override
        public void run(){
            System.out.println("运行了!时间为:"+new Date());
        }
    }
    public static void main(String[] args){
        try{
            MyTask task = new MyTask();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String dateString = "2016-12-16 11:22:00";
            Date dateRef = sdf.parse(dateString);
            System.out.println("字符串时间:"+dateRef.toLocaleString()+" 当前时间:"+new Date().toLocaleString());
            timer.schedule(task,dateRef);
        }catch(ParseException e){
            e.printStackTrace();
        }
    }
    

    timer = new Timer()实际上就是启动一个新线程,这个新启动的线程不是守护线程,所以即使在执行完任务后,进程仍未销毁
    可以通过timer = new Timer(true)将新创建的Timer改成守护线程,程序运行后迅速结束当前的进程,并且TimerTask中的任务不再被运行,因为进程已经结束了

    如果设置的执行时间早于当前时间,则会立即执行task任务

    TimerTask是以队列的方式一个一个被顺序执行的,所以执行的时间有可能和预期的时间不一致,因为前面的任务有可能消耗的时间较长

  • schedule(TimerTask task,Date firstTime,long period):在指定的日期之后,按指定的间隔周期性地无限循环地执行某一任务

    timer.schedule(task,dateRef,4000);//每隔4秒运行一次TimerTask任务
    
  • schedule(TimerTask task,long delay):以执行schedule(TimerTask task,long delay)方法当前的时间为参考时间,在此时间基础上延迟指定的毫秒数后执行一次TimerTask任务

  • schedule(TimerTask task,long delay,long period):执行该方法的当前时间基础上延迟指定的毫秒数,再以某一间隔时间无限次地执行某一任务

  • scheduleAtFixedRate(TimerTask task,Date firstTime,long period):不管执行任务的时间有没有被延时,scheduleAtFixedRate方法下一次任务的执行时间参考的是上一次任务”结束”时的时间来计算,而schedule方法在任务没有被延时时,下一次任务的执行时间参考的是上一次任务”开始”时的时间来计算,如果被延时则跟scheduleAtFixedRate方法一样

  • cancel():将任务队列中的全部任务清空,如果没有抢到queue锁则TimerTask类中的任务继续正常执行

TimerTask类中的主要方法:

  • cancel():将自身从任务队列中清除,其他任务不受影响

单例模式与多线程

立即加载/”饿汉模式”:使用类的时候已经将对象创建完毕

public class MyObject{
    //立即加载方式==饿汉模式
    private static MyObject myObject = new MyObject();
    private MyObject(){}
    public static MyObject getInstance(){
        //此代码版本为立即加载
        //此版本代码的缺点是不能有其他实例变量
        //因为getInstance()方法没有同步
        //所以有可能出现非线程安全问题
        return myObject;
    }
}

延迟加载/”懒汉模式”:在调用方法实例时才被创建

public class MyObject{
    private static MyObject myObject;
    private MyObject(){}
    public static MyObject getInstance(){
        //延迟加载
        if(myObject != null){

        }else{
            myObject = new MyObject();
        }
        return myObject;
    }
}

延迟加载在多线程的环境下不能保持单例的状态,需使用DCL双检查锁机制

public class MyObject{
    private volatile static MyObject myObject;
    private MyObject(){}
    //使用双检测机制来解决问题,既保证了不需要同步代码的异步执行性
    //又保证了单例的效果

    public static MyObject getInstance(){
        try{
            if(myObject != null){

            }else{
                //模拟在创建对象之前做一些准备性的工作
                Thread.sleep(3000);
                synchronized(MyObject.class){
                    if(myObject == null){
                        myObject = new MyObject();
                    }
                }
            }
        }catch(InterruptedException e){
            e.printStackTrace();
        }
        return myObject;
    }
    //此版本的代码称为双重检查Double-Check Locking
}

使用静态内置类实现单例模式

public class MyObject implements Serializable{
    private static final long serialVersionUID = 888L;
    //内部类方式
    private static class MyObjectHandler{
        private static final MyObject myObject = new MyObject();
    }
    private MyObject(){}
    public static MyObject getInstance(){
        return MyObjectHandler.myObject;
    }
    //静态内置类如果遇到序列化对象时,使用默认的方式运行不能实现单例
    //可以在反序列化中使用readResolve()方法解决
    protected Object readResolve() throws ObjectStreamException{
        System.out.println("调用了readResolve方法!");
        return MyObjectHandler.myObject;
    }
}

使用static代码块实现单例模式

//静态代码块中的代码在使用类的时候就已经执行了,可以应用静态代码块的这个特性来实现单例设计模式
public class MyObject{
    private static MyObject instance = null;
    private MyObject(){}
    static{
        instance = new MyObject();
    }
    public static MyObject getInstance(){
        return instance;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值