6.JUC之 Lock锁
- 6.1锁的可重入性
- 6.2 ReentrantLock 可重入锁
- 6.3 lockInterruptibly()方法
- 6.4 tryLock
- 6.5 newCondition()方法
- 6.6 公平锁与非公平锁
- 6.7 ReentrantLock中API
- 6.8 ReentrantReadWriteLock 读写锁- 6.8.1 读读共享
- 6.8.2 写写互斥
- 6.8.3 读写互斥
 
- 6.9 Lock与Synchronized区别
JUC代表java并发包的简写
在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock 接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。
6.1锁的可重入性
锁的可重入是指,当一个线程获得一个对象锁后,再次请求该对象锁时仍然可以获得该对象的锁。
public class LockTest01 {
    private synchronized void print1() {
        System.out.println("同步方法1...");
        print2();
    }
    private synchronized void print2() {
        System.out.println("同步方法2...");
        print3();
    }
    private synchronized void print3() {
        System.out.println("同步方法3...");
    }
    public static void main(String[] args) {
        LockTest01 lockTest01 = new LockTest01();
        new Thread(new Runnable() {
            @Override
            public void run() {
                lockTest01.print1();
            }
        }).start();
    }
}
// 线程执行 print1()方法,默认 this 作为锁对象,在 print1()方法中调用了 print2()方法,注意当前线程还是持有 this 锁对象的
// print2()同步方法默认的锁对象也是 this 对象, 要执行 print2()必须先获得 this 锁对象,当前 this 对象被当前线程持有,可以 再次获得 this 锁对象, 这就是锁的可重入性. 假设锁不可重入的话,可能会造成死锁
6.2 ReentrantLock 可重入锁
调用 lock()方法获得锁,调用 unlock()释放锁,同样可以重入锁。
  Lock lock = new ReentrantLock();
  lock.lock();
  try{
	// 可能会出现线程安全的操作
  }finally{
	// 一定在finally中释放锁
	// 也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
    lock.unlock();
  }
public class C1Lock {
    // 总票数
    static int ticket = 10;
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Runnable runnable = () -> {
            // 循环买票
            while (true) {
                // 获得锁
                lock.lock();
                try {
                    Thread.sleep(100);
                    if (ticket > 0) {
                        ticket--;
                        System.out.println(Thread.currentThread().getName() 
                                           + "卖了一张票,剩余:" + ticket);
                    } else {
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        };
        // 创建3个线程
        Thread t1 = new Thread(runnable, "窗口1");
        Thread t2 = new Thread(runnable, "窗口2");
        Thread t3 = new Thread(runnable, "窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
6.3 lockInterruptibly()方法
lockInterruptibly() 方法的作用:如果当前线程未被中断则获得锁,如果当前线程被中断则抛出异常。
对于 synchronized 内部锁来说,如果一个线程在等待锁,只有两个结果:要么该线程获得锁继续执行;要么就保持等待。
对于 ReentrantLock 可重入锁来说,提供另外一种可能,在等待锁的过程中,程序可以根据需要取消对锁的请求。
/**
 * 通过 ReentrantLock 锁的 lockInterruptibly()方法避免死锁的产生
 */
public class Test06 {
    static class IntLock implements Runnable {
        //创建两个 ReentrantLock 锁对象
        public static ReentrantLock lock1 = new ReentrantLock();
        public static ReentrantLock lock2 = new ReentrantLock();
        int lockNum; //定义整数变量,决定使用哪个锁
        public IntLock(int lockNum) {
            this.lockNum = lockNum;
        }
        @Override
        public void run() {
            try {
                if (lockNum % 2 == 1) {
                    //奇数,先锁 1,再锁 2
                    lock1.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName() + "获得锁 1,还需要获得锁 2");
                    Thread.sleep(new Random().nextInt(500));
                    lock2.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName() + "同时获得了锁 1 与锁 2....");
                } else {
                    //偶数,先锁 2,再锁 1
                    lock2.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName() + "获得锁 2,还需要获得锁 1");
                    Thread.sleep(new Random().nextInt(500));
                    lock1.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName() + "同时获得了锁1 与锁 2....");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock1.isHeldByCurrentThread()) { //判断当前线程是否持有该锁
                    lock1.unlock();
                }
                if (lock2.isHeldByCurrentThread()) {
                    lock2.unlock();
                }
                System.out.println(Thread.currentThread().getName() + "线程退出");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        IntLock intLock1 = new IntLock(11);
        IntLock intLock2 = new IntLock(22);
        Thread t1 = new Thread(intLock1);
        Thread t2 = new Thread(intLock2);
        t1.start();
        t2.start();
        //在 main 线程,等待 3000 秒,如果还有线程没有结束就中断该线程
        Thread.sleep(3000);
        //可以中断任何一个线程来解决死锁, t2 线程会放弃对锁 1 的申请,同时释放锁 2, t1 线程会完成它的任务
        if (t2.isAlive()) {
            t2.interrupt();
        }
    }
}
6.4 tryLock
tryLock    public boolean tryLock()
仅在调用时锁未被另一个线程保持的情况下,才获取该锁。 
1)如果该锁没有被另一个线程保持,并且立即返回 true 值,则将锁的保持计数设置为 1。
即使已将此锁设置为使用公平排序策略,但是调用 tryLock() 仍将 立即获取锁(如果有可用的),而不管其他线程当前是否正在等待该锁。
在某些情况下,此“闯入”行为可能很有用,即使它会打破公平性也如此。如果希望遵守此锁的公平设置,则使用 tryLock(0, TimeUnit.SECONDS) ,它几乎是等效的(也检测中断)。 
2)如果当前线程已经保持此锁,则将保持计数加 1,该方法将返回 true。 
3)如果锁被另一个线程保持,则此方法将立即返回 false 值。 
指定者:
   接口 Lock 中的  tryLock
返回: 
   如果锁是自由的并且被当前线程获取,或者当前线程已经保持该锁,则返回 true ;否则返回 false
作者:wuxinliulei
链接:https://www.zhihu.com/question/36771163/answer/68974735
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
// 仅在调用时锁是空闲的情况下才获取锁
boolean tryLock()
// 如果锁在给定的等待时间内是空闲的,并且当前线程没有被中断,则获取锁
boolean tryLock(long time, TimeUnit unit)
public class C2TryLock {
    private ArrayList arrayList = new ArrayList<>();
    //注意这个地方
    private Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        final C2TryLock test = new C2TryLock();
        Runnable run1 = new Runnable() {
            @Override
            public void run() {
                test.insert(Thread.currentThread());
            }
        };
        Runnable run2 = new Runnable() {
            @Override
            public void run() {
                try {
                    test.insert2(Thread.currentThread());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Runnable run3 = new Runnable() {
            @Override
            public void run() {
                test.threadSleep();
            }
        };
        
        // 情况1:开启线程3
        new Thread(run3, "线程3").start();
        
        // 情况2:不开启线程3,线程1执行中睡眠3秒
//        new Thread(run3, "线程3").start();
        
        // 情况3:线程1执行中睡眠2秒
        new Thread(run1, "线程1").start();
        new Thread(run2, "线程2").start();
    }
    public void insert(Thread thread) {
        // 如果锁被占用直接返回false
        if (lock.tryLock()) {
            try {
                System.out.println(thread.getName() + "得到了锁");
                Thread.sleep(2000);
                for (int i = 0; i < 5; i++) {
                    arrayList.add(i);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(thread.getName() + "释放了锁");
                lock.unlock();
            }
        } else {
            System.out.println(thread.getName() + "获取锁失败");
        }
    }
    public void insert2(Thread thread) throws InterruptedException {
        // 如果锁被占用,在等待3秒内获得锁返回true,超出等待时间后返回false
        if (lock.tryLock(3, TimeUnit.SECONDS)) {
            try {
                System.out.println(thread.getName() + "得到了锁");
                for (int i = 0; i < 5; i++) {
                    arrayList.add(i);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(thread.getName() + "释放了锁");
                lock.unlock();
            }
        } else {
            System.out.println(thread.getName() + "获取锁失败");
        }
    }
    public void threadSleep() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"睡眠中...");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
 6.5 newCondition()方法
// 返回一个绑定到这个Lock实例的新的Condition实例
Condition newCondition()
??:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重新获得锁并继续执行。
注意:
在调用 Condition 的 await()/signal()方法前,也需要线程持有相关的 Lock 锁,调用 await()后线程会释放这个锁,在 singal()调用后会从当前 Condition 对象的等待队列中唤醒 一个线程,唤醒的线程尝试获得锁,一旦获得锁成功就继续执行。若调用await()/signal()方法前没有持有相关Lock锁,会抛出java.lang.IllegalMonitorStateException异常。
Interface Condition
| Modifier and Type | Method | Description | 
|---|---|---|
| void | await() | 使当前线程等待,直到它被通知或中断。 | 
| boolean | await(long time, TimeUnit unit) | 使当前线程等待,直到它被通知或中断,或指定的等待时间过去。 | 
| long | awaitNanos(long nanosTimeout) | 使当前线程等待,直到它被通知或中断,或指定的等待时间(纳秒)过去。 | 
| void | awaitUninterruptibly() | 使当前线程等待,直到它得到信号 | 
| boolean | awaitUntil(Date deadline) | 使当前线程等待,直到它被通知或被中断,或超过指定的截止日期。 | 
| void | signal() | 唤醒一个等待的线程。 | 
| void | signalAll() | 唤醒所有等待的线程。 | 
举个??:
使用Condition实现两个线程交替打印字母和数字
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 使用Condition实现两个线程交替打印字母和数字
 *
 */
public class AlternatePrint {
    static Lock lock = new ReentrantLock();
    static Condition figureCondition = lock.newCondition();
    static Condition letterCondition = lock.newCondition();
    private int num = 0;
    public static void main(String[] args) {
        AlternatePrint alternatePrint = new AlternatePrint();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 26; i++) {
                    alternatePrint.printLetter();
                }
                System.out.println("thread-0结束");
                /*
                * 需要释放两个Condition等待的线程,
                *
                * 问题:Thread-0线程打印完'Z'后,唤醒了Thread-1线程自身进入等待并释放锁
                * Thread-1线程打印完26后唤醒Thread-0,自身进入等待并释放锁,自此Thread-1一致处于等待及获取锁的状态TIMED_WAITING
                *
                * 解决方式:
                * Thread-0线程结束后唤醒另一个Condition的线程等待状态,使程序正常结束
                * */
                alternatePrint.releaseWail();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 26; i++) {
                    alternatePrint.printFigure();
                }
                System.out.println("thread-1结束");
                alternatePrint.releaseWail();
            }
        }).start();
    }
    /**
     * 打印字母
     */
    private void printLetter() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"--->" + (char)(num + 65));
            // 这里要先把对方释放,自身再等待,否则当前持有锁的线程会一直等待,其他线程也不能获取锁
            figureCondition.signal();
            letterCondition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    /**
     * 打印数字
     */
    private void printFigure() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"--->" + (++num));
            // 这里要先把对方释放,自身再等待,否则当前持有锁的线程会一直等待,其他线程也不能获取锁
            letterCondition.signal();
            figureCondition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    private void releaseWail() {
        lock.lock();
        try {
            letterCondition.signalAll();
            figureCondition.signalAll();
        }  finally {
            lock.unlock();
        }
    }
}
运行结果:程序没有完全结束,其中一个线程处于等待状态
修改后:
6.6 公平锁与非公平锁
大多数情况下,锁的申请都是非公平的,如果线程 1与线程 2都在请求锁 A,当锁 A 可用时,系统只是会从阻塞队列中随机的选择一个线程,不能保证其公平性。
公平的锁会按照时间先后顺序,保证先到先得,公平锁的这一特点不会出现线程饥饿现象。
synchronized 内部锁就是非公平的,ReentrantLock 重入锁提供了一个构造方法:ReentrantLock(boolean fair) ,当在创建锁对象时实参传递 true 可以把该锁设置为公平锁。
// 创建一个ReentrantLock实例,非公平锁
ReentrantLock()
// 使用给定的公平策略创建一个ReentrantLock实例。
ReentrantLock(boolean fair)
公平锁看起来很公平,但是要实现公平锁必须要求系统维护一个有序队列,公平锁的实现成本较高,性能也低。因此默认情况下锁是非公平的,不是特别的需求,一般不使用公平锁。
/**
 * 公平锁与非公平锁
 */
public class Test01 {
    // static ReentrantLock lock = new ReentrantLock(); //默认是非公平锁
    static ReentrantLock lock = new ReentrantLock(true); //定义公平锁
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    lock.lock();
                    try {
                        System.out.println(Thread.currentThread().getName() + "获得了锁对象");
                    } finally {
                        lock.unlock();
                    }
                }
            }
        };
        for (int i = 0; i < 5; i++) {
            new Thread(runnable).start();
        }
        /*
            运行程序
            1)如果是非公平锁, 系统倾向于让一个线程再次获得已经持有的锁, 这种分配策略是高效的,非公平的
            2)如果是公平锁, 多个线程不会发生同一个线程连续多次获得锁的可能,保证了公平性
        */
    }
}
6.7 ReentrantLock中API
// 返回当前线程调用 lock()方法的次数
int getHoldCount()
    
// 返回正等待获得锁的线程预估数
int getQueueLength()
    
// 返回与 Condition 条件相关的等待的线程预估数
int getWaitQueueLength(Condition condition)
    
// 查询参数指定的线程是否在等待获得锁
boolean hasQueuedThread(Thread thread) 
    
//查询是否还有线程在等待获得该锁
boolean hasQueuedThreads() 
    
// 查询是否有线程正在等待指定的 Condition 条件
boolean hasWaiters(Condition condition) 
// 判断是否为公平锁
boolean isFair() 
    
// 判断当前线程是否持有该锁
boolean isHeldByCurrentThread() 
    
// 查询当前锁是否被线程持有
boolean isLocked()
6.8 ReentrantReadWriteLock 读写锁
synchronized 内部锁与 ReentrantLock 锁都是独占锁(排它锁),同一时间只允许一个线程执行同步代码块,可以保证线程的安全性,但是执行效率低。
ReentrantReadWriteLock 读写锁是一种改进的排他锁,也可以称作共享/排他锁。允许多个线程同时读取共享数据,但是一次只允许一个线程对共享数据进行更新。
读写锁通过读锁与写锁来完成读写操作,线程在读取共享数据前必须先持有读锁,该读锁可以同时被多个线程持有,即它是共享的,线程在修改共享数据前必须先持有写锁,写锁是排他的。一个线程持有写锁时其他线程无法获得相应的锁(读/写锁)。
读锁只是在读线程之间共享,任何一个线程持有读锁时,其他线程都无法获得写锁,保证线程在读取数据期间没有其他线程对数据进行更新,使得读线程能够读到数据的最新值,保证在读数据期间共享变量不被修改。
| 获得条件 | 排他性 | 作用 | |
|---|---|---|---|
| 读锁 | 写锁未被任意线程持有 | 对读线程是共享的, 对写线程是排他的 | 允许多个读线程可以同时读取共享数据,保证在读共享数据时,没有其他线程对共享数据进行修改 | 
| 写锁 | 该写锁未被其他线程持有,并且相应的读锁也未被其他线程持有 | 对读线程或者写线程都是排他的 | 保证写线程以独占的方式修改共享数据 | 
读写锁允许读读共享,读写互斥,写写互斥。
在 java.util.concurrent.locks包中定义了 ReadWriteLock接口,该接口中定义了 readLock()返回读锁,定义 writeLock()方法返回写锁,该接口的实现类是 ReentrantReadWriteLock。
Interface ReadWriteLock
// 返回用于读取的锁。
Lock readLock() 
// 返回用于写入的锁。
Lock writeLock() 
Class ReentrantReadWriteLock Implemented ReadWriteLock
// 返回用于读取的锁。
ReentrantReadWriteLock.ReadLock  readLock() 
// 返回用于写入的锁。
ReentrantReadWriteLock.WriteLock  writeLock()  
注意:
readLock()与 writeLock()方法返回的锁对象是同一个锁的两个不同的角色,不是分别获得两个不同的锁。ReadWriteLock 接口实例可以充当两个角色,读写锁的其他使用方法
// 定义读写锁
ReadWriteLock rwLock = new ReentrantReadWriteLock();
// 获得读锁
Lock readLock = rwLock.readLock();
// 获得写锁
Lock writeLock = rwLock.writeLock();
/*
  读数据
*/
readLock.lock(); //申请读锁
try{
    // 读取共享数据
} finally {  
    // 总是在 finally 子句中释放锁
    readLock.unlock();
}
/*
  写数据
*/
writeLock.lock(); //申请写锁
try{
    // 更新修改共享数据
} finally {
    // 总是在 finally 子句中释放锁
    writeLock.unlock();
} 
6.8.1 读读共享
ReadWriteLock 读写锁可以实现多个线程同时读取共享数据,即读读共享,可以提高程序的读取数据的效率。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * ReadWriteLock 读写锁可以实现读读共享,允许多个线程同时获得读锁
 */
public class Test01 {
    static class Service {
        // 定义读写锁
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        // 定义方法读取数据
        public void read() {
            // 获得读锁
            readWriteLock.readLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获得读锁,开始读取数据的时间--" + System.currentTimeMillis());
                // 模拟读取数据用时3s
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 释放读锁
                readWriteLock.readLock().unlock();
            }
        }
    }
    public static void main(String[] args) {
        Service service = new Service();
        // 创建 5 个线程,调用 read()方法
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 在线程中调用 read()读取数据
                    service.read();
                }
            }).start();
        }
        // 运行程序后,多个线程几乎可以同时获得锁读,执行 lock()后面的代码
    }
}
6.8.2 写写互斥
通过 ReadWriteLock 读写锁中的写锁,只允许有一个线程执行 lock()后面的代码。即获取写锁得线程间使互斥得。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * 演示 ReadWriteLock 的 writeLock()写锁是互斥的,只允许有一个线程持有
 */
public class Test02 {
    static class Service {
        // 先定义读写锁
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        // 定义方法修改数据
        public void write() {
            try {
                // 申请获得写锁
                readWriteLock.writeLock().lock();
                System.out.println(Thread.currentThread().getName() + "获得写锁,开始修改数据的时间--"
                        + DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss").format(LocalDateTime.now()));
                // 模拟修改数据的用时3s
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + "修改数据完毕时的时间=="
                        + DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss").format(LocalDateTime.now()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 释放写锁
                readWriteLock.writeLock().unlock();
            }
        }
    }
    public static void main(String[] args) {
        Service service = new Service();
        // 创建 5 个线程修改数据
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 调用修改数据的方法
                    service.write();
                }
            }).start();
        }
        
    }
}
6.8.3 读写互斥
写锁是独占锁、是排他锁,读线程与写线程也是互斥的。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
 * 演示 ReadWriteLock 的读写互斥
 * 
 * 一个线程获得读锁时,写线程等待; 一个线程获得写锁时,其他线程等待
 */
public class Test03 {
    static class Service {
        //先定义读写锁
        ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        // 获得读锁
        Lock readLock = readWriteLock.readLock();
        // 获得写锁
        Lock writeLock = readWriteLock.writeLock();
        /**
         * 读取共享数据
         */
        public void read() {
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获得读锁,开始读取数据的时间--"
                        + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
                                   .format(LocalDateTime.now()));
                // 模拟读取数据的操作用时
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + "读取数据完毕时的时间=="
                        + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
                                   .format(LocalDateTime.now()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                readLock.unlock();
            }
        } //定义方法修改数据
        /**
         * 更新共享数据
         */
        public void write() {
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获得写锁,开始修改数据的时间--"
                        + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
                // 模拟修改数据的用时
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + "修改数据完毕时的时间=="
                        + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                writeLock.unlock();
            }
        }
    }
    public static void main(String[] args) {
        Service service = new Service();
        // 定义一个线程读数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.read();
            }
        }).start();
        // 定义一个线程写数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.write();
            }
        }).start();
    }
}
6.9 Lock与Synchronized区别
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
   synchronized关键字可以直接修饰方法,也可以修饰代码块,而lock只能修饰代码块
   
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。(提供tryLock)
5)Lock可以提高多个线程进行读操作的效率。(提供读写锁)
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。