【转】Java多线程-Lock

转载出处: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();
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/hundan_520520/article/details/82021914