转载出处:https://www.cnblogs.com/cielosun/p/6662201.html
synchronized的缺陷
synchronized修饰的代码只有获取锁的线程才能够执行,其他线程只能等待该线程释放锁。一个线程释放锁的情况有以下方式:
- 获取锁的线程完成了synchronized修饰的代码块的执行。
- 线程执行时发生异常,JVM自动释放锁。
锁会因为等待I/O,sleep()方法等原因被阻塞而不释放锁,此时如果线程还处于用synchronized修饰的代码区域里,那么其他线程只能等待,这样就影响了效率。因此Java提供了Lock来实现另一个机制,即不让线程无限期的等待下去。
思考一个情景,当多线程读写文件时,读操作和写操作会发生冲突,写操作和写操作会发生冲突,但读操作和读操作不会有冲突。如果使用synchronized来修饰的话,就很可能造成多个读操作无法同时进行的可能(如果只用synchronized修饰写方法,那么可能造成读写冲突,如果同时修饰了读写方法,则会有读读干扰)。此时就需要用到Lock,换言之Lock比synchronized提供了更多的功能。
使用Lock需要注意以下两点:
- Lock不是语言内置的,synchronized是Java关键字,为内置特性,Lock是一个类,通过这个类可以实现同步访问。
- 采用synchronized时我们不需要手动去控制加锁和释放,系统会自动控制。而使用Lock类,我们需要手动的加锁和释放,不主动释放可能会造成死锁。实际上Lock类的使用某种意义上讲要比synchronized更加直观。
Lock类接口设计
Lock类本身是一个接口,其方法如下:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
下面依次讲解一下其中各个方法。
lock()
方法使用最多,作用是用于获取锁,如果锁已经被其他线程获得,则等待。通常情况下,lock使用以下方式去获取锁:
Lock lock = new ReentrantLock();
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
lockInterruptibly()
和lock()的区别是lockInterruptibly()锁定的线程处于等待状态时,允许线程的打断操作,线程使用Thread.interrupt()打断该线程后会直接返回并抛出一个InterruptException();lock()方法锁定对象时如果在等待时检测到线程使用Thread.interrupt(),仍然会继续尝试获取锁,失败则继续休眠,只是在成功获取锁之后在把当前线程置为interrupt状态。也就使说,当两个线程同时通过lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
因此,lockInterruptibly()方法必须实现catch(InterruptException e)代码块。常见使用方式如下:
public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
//.....
}
finally {
lock.unlock();
}
}
tryLock(long time, TimeUnit unit)
则是介于二者之间,用户设定一个等待时间,如果在这个时间内获取到了锁,则返回true,否则返回false结束。unlock()
从上面的代码里我们也看到,unlock()一般放在异常处理操作的finally字符控制的代码块中。我们要记得Lock和sychronized的区别,防止产生死锁。newCondition()
线程中通信使用
公平锁
公平锁即当多个线程等待的一个资源的锁释放时,线程不是随机的获取资源而是等待时间最久的线程获取资源(FIFO)。Java中,synchronized是一个非公平锁,无法保证锁的获取顺序。ReentrantLock和ReentrantReadWriteLock默认也是非公平锁,但可以设置成公平锁。我们前面的实例中初始化ReentrantLock和ReentrantReadWriteLock时都是无参数的。实际上,它们提供一个默认的boolean变量fair,为true则为公平锁,为false则为非公平锁,默认为false。因此,当我们想将其实现为公平锁时,仅需要初始化时赋值true。即:
Lock lock = new ReentrantLock(true);
读写锁
package com.zhihua.demo01;
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 读写锁
* @author caizh
*
*/
public class ReadWriteLockTest {
public static void main(String[] args) {
final Queue3 q3 = new Queue3();
for(int i=0;i<3;i++)
{
new Thread(){
public void run(){
while(true){
q3.get();
}
}
}.start();
new Thread(){
public void run(){
while(true){
q3.put(new Random().nextInt(10000));
}
}
}.start();
}
}
}
class Queue3{
//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
private Object data = null;
ReadWriteLock rWriteLock = new ReentrantReadWriteLock();
public void get() {
rWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " be ready to read data!");
Thread.sleep((long)(Math.random()*1000));
System.out.println(Thread.currentThread().getName() + "have read data :" + data);
} catch (Exception e) {
e.printStackTrace();
}finally {
rWriteLock.readLock().unlock();
}
}
public void put(Object data) {
rWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " be ready to write data!");
Thread.sleep((long)(Math.random()*1000));
this.data = data;
System.out.println(Thread.currentThread().getName() + " have write data: " + data);
} catch (Exception e) {
e.printStackTrace();
}finally {
rWriteLock.writeLock().unlock();
}
}
}
Lock和synchronized的选择
- synchronized是内置语言实现的关键字,Lock是为了实现更高级锁功能而提供的接口。
- synchronized发生异常时自动释放占有的锁,Lock需要在finally块中手动释放锁。因此从安全性角度讲,既可以用Lock又可以用synchronized时(即不需要锁的更高级功能时)使用synchronized更保险。
- 线程激烈竞争时Lock的性能远优于synchronized,即有大量线程时推荐使用Lock。
- Lock可以通过lockInterruptibly()接口实现可中断锁。
- ReentrantReadWriteLock实现了封装好的读写锁用于大量读少量写读者优先情景解决了synchronized读写情景难以实现问题。
synchronized和lock分别实现买票案例:
package com.zhihua;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 一、用于解决多线程安全问题的方式:
* synchronized:隐式锁
* 1. 同步代码块
* 2. 同步方法
* jdk 1.5 后:
* 3. 同步锁 Lock
* 注意:是一个显示锁,需要通过 lock() 方法上锁,必须通过 unlock() 方法进行释放锁
*/
public class TestLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket, "1号窗口").start();
new Thread(ticket, "2号窗口").start();
new Thread(ticket, "3号窗口").start();
TicketSynchronized sTicketSynchronized = new TicketSynchronized();
// new Thread(sTicketSynchronized, "1号窗口").start();
// new Thread(sTicketSynchronized, "2号窗口").start();
// new Thread(sTicketSynchronized, "3号窗口").start();
}
}
class TicketSynchronized implements Runnable{
private int tick = 100;
@Override
public void run() {
sale();
}
public synchronized void sale() {
while(true) {
if(tick>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " 完成售票,余票为:" + --tick);
}else {
System.out.println("票已售完");
break;
}
}
}
}
class Ticket implements Runnable{
private int tick = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(true) {
lock.lock();
try {
if(tick>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ " 完成售票,余票为:" + --tick);
}else {
System.out.println("票已售完");
break;
}
} finally {
lock.unlock();
}
}
}
}