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;
}
}