欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java Lock的使用

程序员文章站 2024-01-09 18:47:16
...

首先扯点别的:记得以前在大学校里和同学一起打勾级,我怂恿我的队友杜仁建出牌,硬说我队友的对门周通要不了杜仁建的牌,原话是这样的“他(周通)要是能要了,我把牌吃了吐出来再吃”。结果他(周通)还是把我队友(杜仁建)给闷了。现在想想也是有意思。

今天记录一下Java中同步锁的使用。以后再慢慢理解。

首先看一下Lock这个接口,在java.util.concurrent.locks.Lock包下面,ReentrantLock:可重入锁,实现了Lock接口。

public interface Lock {

    void lock();

    void lockInterruptibly() throws InterruptedException; 

    boolean tryLock(); 

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 

    //释放锁
    void unlock();
    //返回一个和当前锁关联的Condition对象
   Condition newCondition();

解释一下上面几个方法的作用

void lock();

获取锁,如果锁不可用,当前线程则不能被线程调度,并处于休眠状态,直到获得锁。

void lockInterruptibly() throws InterruptedException; 

获取锁,如果锁可用则立即返回。如果锁不可用,当前线程则不能被线程调度,并处于休眠状态,直到下面两种情况之一发生。
1. 当前线程获取到锁。
2. 当前线程被中断,抛出InterruptedException。

 boolean tryLock(); 

如果能够获取锁,立即返回true,否则立即返回false。使用tryLock( )惯用语法。

     Lock lock = ...;
     if (lock.tryLock()) {
       try {
         // 获取成功操作
        } finally {
          lock.unlock();
        }
      } else {
        //获取失败操作
      }}
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  • 如果能够获取锁,方法立即返回true。
  • 如果锁不可用,当前线程会一直休眠等待直到下面三种情况中的一种情况发生。
    1. 当前线程成功获取了锁,返回true。
    2. 当前线程被其他线程中断,抛出InterruptedException异常。
    3. 等待时间超时,返回false。

ReentrantLock类中其他方法

//判断锁是否被线程持有
 public boolean isLocked() {
        return sync.isLocked();
    }
//判断当前锁是否是公平锁
public final boolean isFair() {
    return sync instanceof FairSync;
}

具体的使用方法

lock()方法

public class Test {

    private ArrayList<Integer> list = new ArrayList<>();
    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        Test test = new Test();
        new Thread(new Runnable() {
            @Override
            public void run() {
                test.insert(Thread.currentThread());
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                test.insert(Thread.currentThread());
            }
        }).start();
    }

    public void insert(Thread t) {
        lock.lock();
        try {
            System.out.println(t.getName() + "得到了锁");
            for (int i = 0; i < 5; i++) {
                list.add(i);
            }
        } finally {
            System.out.println(t.getName() + "释放了锁");
            lock.unlock();
        }
    }
}

输出结果

Thread-0得到了锁
Thread-0释放了锁
Thread-1得到了锁
Thread-1释放了锁

tryLock()方法

public class Test {

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        Test test = new Test();
        new Thread(new Runnable() {
            @Override
            public void run() {
                test.insert(Thread.currentThread());
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                test.insert(Thread.currentThread());
            }
        }).start();
    }

    public void insert(Thread t) {
        if (lock.tryLock()) {
            try {
                System.out.println(t.getName() + "得到了锁");
                try {
                    //睡眠一会,但是不释放锁
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                System.out.println(t.getName() + "释放了锁");
                lock.unlock();
            }
        } else {
            System.out.println(t.getName() + "获取锁失败");
        }
    }
}

输出结果

Thread-0得到了锁
Thread-1获取锁失败
Thread-0释放了锁

lockInterruptibly()方法

public class Test {

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        Test test = new Test();
        MyThread thread1 = new MyThread(test);
        MyThread thread2 = new MyThread(test);
        thread1.start();
        //延迟一会再启动线程2,否则可能出现线程2先被调度执行,那么线程2就不能被中断了
        try {
            Thread.sleep(2000);
            System.out.println("启动线程" + thread2.getName());
            thread2.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.interrupt();
    }

    public void insert(Thread thread) throws InterruptedException {
        lock.lockInterruptibly();
        try {
            System.out.println(thread.getName() + "获得了锁");
            long startTime = System.currentTimeMillis();
            for (; ; ) {
                if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
                    break;
                //插入数据
            }
        } finally {
            System.out.println(Thread.currentThread().getName() + "执行finally");
            lock.unlock();
            System.out.println(thread.getName() + "释放了锁");
        }
    }
}

class MyThread extends Thread {
    private Test test = null;

    public MyThread(Test test) {
        this.test = test;
    }

    @Override
    public void run() {
        try {
            test.insert(Thread.currentThread());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "被中断");
        }
    }
}

输出结果

Thread-0获得了锁
Thread-1被中断

当thread1启动后,获得了锁。两秒后再启动thread2,然后thread2尝试获取锁,这时候,thread1正在持有锁,所以thread2处于等待状态。然后又过了两秒,调用thread2.interrupt()方法,中断thread2,lock.lockInterruptibly();就会抛出中断异常,在捕获的异常中输出Thread-1被中断。

ReadWriteLock:读写锁,也是一个接口,在java.util.concurrent.locks.ReadWriteLock包下,内部维持一对相关的锁。读锁,可以多个读者线程共用。写锁,互斥,同一时刻只能被一个线程使用。ReentrantReadWriteLock实现了这个接口。

public interface ReadWriteLock {

    //返回用来读的锁
    Lock readLock();
    //返回用来写的锁
    Lock writeLock();
}
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {

    //内部类,提供读锁
    private final ReentrantReadWriteLock.ReadLock readerLock;
    //内部类,提供写锁
    private final ReentrantReadWriteLock.WriteLock writerLock;
    //...

    //获取写锁
     public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }

     //获取读锁
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; } 

   }

WriteLock的获取锁的方法,以最简单的lock( )方法为例

  /**
  * 获取写锁
  *如果读锁和写锁都没有被其他线程持有,则立即返回,并设置锁的计数为1。
  *如果当前线程持有写锁,就把锁的计数加1,立即返回。
  *如果写锁被其他线程持有,当前线程就不能被线程调度,并一直休眠,直到获取写锁(这时候写锁的计数为1)
      */
   public void lock() {
        sync.acquire(1);
    }     

ReadLock获取锁的方法,以最简单的lock( )方法为例

  //获取读锁
  //如果写锁没有没其他线程持有,则立即返回。
  //如果写锁被别的线程持有,则当前线程不能被线程调度,并一直休眠直到获取到读锁。
   public void lock() {
       sync.acquireShared(1);
   }

多个线程同时使用读锁

public class ReentrantReadWriteLockTest {

    private ReadWriteLock rwl = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        ReentrantReadWriteLockTest test = new ReentrantReadWriteLockTest();
        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();

        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();
    }

    private void get(Thread thread) {
        rwl.readLock().lock();
        try {
            long start = System.currentTimeMillis();
            while (System.currentTimeMillis() - start < 1000) {
                System.out.println(thread.getName() + "正在进行读操作");
            }
            System.out.println(thread.getName() + "读操作完毕");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwl.readLock().unlock();
        }
    }
}

输出结果会发现两个线程会交替输出,意思就是多个线程可以同时共用读锁。

一个线程使用读锁,一个线程使用写锁。

如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。如果有一个线程已经占用了写锁,则此时其他线程如果要申请读锁,则申请读锁的线程会一直等待释放写锁。

public class ReentrantReadWriteLockTest {

    private ReadWriteLock rwl = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        ReentrantReadWriteLockTest test = new ReentrantReadWriteLockTest();

        new Thread() {
            public void run() {
                test.write(Thread.currentThread());
            }
        }.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread() {
            public void run() {
                test.get(Thread.currentThread());
            }
        }.start();
    }

    private void get(Thread thread) {
        rwl.readLock().lock();
        try {
            for (int i = 0; i < 100; i++) {
                System.out.println(thread.getName() + "正在进行读操作" + i);
            }
            System.out.println(thread.getName() + "读操作完毕");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwl.readLock().unlock();
        }
    }

    private void write(Thread thread) {
        rwl.writeLock().lock();
        try {
            for (int i = 0; i < 100; i++) {
                System.out.println(thread.getName() + "正在进行写操作" + i);
            }
            System.out.println(thread.getName() + "写操作完毕");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwl.writeLock().unlock();
        }
    }
}

运行结果发现读和写是顺序执行的,也就是说,读锁和写锁是互斥的,同一时刻不能有一个线程使用读锁,一个线程使用写锁。

结尾:先记录下来,以后再慢慢体会,完善。

参考

【1】http://www.cnblogs.com/dolphin0520/p/3923167.html
【2】疯狂Java讲义