Java多线程编程六(使用Lock)

可重入锁ReentrantLock

使用ReentrantLock方法

  • lock():锁定
  • unlock():解除锁定
  • int getHoldCount():返回当前线程保持此锁定的个数,也就是调用lock()方法的次数
  • int getQueueLength():返回正在等待获取此锁定的线程估计数,例如:8个线程,3个线程调用了await()方法,那么在调用此方法后返回的值是5,说明有5个线程同时在等待lock的释放
  • int getWaitQueueLength():顾名思义,其作用是返回等待与此锁定相关的给定条件Condition的线程估计数。例如:8个线程,每个线程都调用了同一个Condition的await()方法,则调用此方法返回的值是8
  • boolean hasQueuedThread():查询指定线程是否正在等待获取此锁定ReentrantLock.hasQueuedThread(thread)
  • boolean hasWaiters():查询是否有线程正在等待与此锁定有关的Condition条件
  • boolean isFair():判断是否是公平锁
  • boolean isHeldByCurrentThread():查询当前线程是否保持此锁定
  • boolean isLocked():查询此锁定是否由任意线程保持
  • void lockInterruptibly():如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常
  • boolean tryLock():仅在调用时锁定未被另一个线程保持的情况下,才获取该锁定
  • boolean tryLock(long timeout, TimeUnit unit):如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定

使用ReentrantLock实现同步效果

public class MyServer {
    private ReentrantLock lock = new ReentrantLock();
    public void printReentrantLock() {
        try {
            lock.lock();//获取锁
            for (int i = 0; i < 5; i++) {
                System.out.println("ThreadName=" + Thread.currentThread().getName() + ",i=" + i);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//释放锁
        }
    }
    public static void main(String[] args) {
        final MyServer myServer = new MyServer();
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    myServer.printReentrantLock();
                }
            });
            thread.setName(String.valueOf(i));
            thread.start();
        }
    }
}

运行结果为:

ThreadName=0,i=0
ThreadName=0,i=1
ThreadName=0,i=2
ThreadName=0,i=3
ThreadName=0,i=4
ThreadName=1,i=0
ThreadName=1,i=1
ThreadName=1,i=2
ThreadName=1,i=3
ThreadName=1,i=4
ThreadName=2,i=0
ThreadName=2,i=1
ThreadName=2,i=2
ThreadName=2,i=3
ThreadName=2,i=4
ThreadName=3,i=0
ThreadName=3,i=1
ThreadName=3,i=2
ThreadName=3,i=3
ThreadName=3,i=4
ThreadName=4,i=0
ThreadName=4,i=1
ThreadName=4,i=2
ThreadName=4,i=3
ThreadName=4,i=4

由结果分析知:当前线程获取锁对象后其他线程呈阻塞状态,直到当前线程打印完毕释放锁,其他线程才能获取到锁进行打印。

这个主要进行单个方法同一个锁多线程调用的同步效果,下面来看一下多个线程交叉调用不同方法同一个锁的例子:

public class MyServer {

    private ReentrantLock lock = new ReentrantLock();

    public void methodA() {
        try {
            lock.lock();//获取锁
            System.out.println("method A begin ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
            methodB();//调用methodB
            Thread.sleep(5000);
            System.out.println("method A end   ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void methodB() {
        try {
            lock.lock();//获取锁
            System.out.println("method B begin ThreadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("method B end   ThreadName="+Thread.currentThread().getName()+" time="+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public static void main(String[] args) {
    String[] names = {"A", "AA", "B", "BB"};
    final MyServer myServer = new MyServer();
    Thread[] threads = new Thread[4];
    for (int i = 0; i < 2; i++) {
        threads[i] = new Thread(new Runnable() {
            public void run() {
                myServer.methodA();
            }
        });
    }
    for (int i = 2; i < 4; i++) {
        threads[i] = new Thread(new Runnable() {
            public void run() {
                myServer.methodB();
            }
        });
    }
    for (int i = 0; i < 4; i++) {
        threads[i].setName(names[i]);
        threads[i].start();
    }
}
method A begin ThreadName=A time=1551406905681
method B begin ThreadName=A time=1551406905681
method B end   ThreadName=A time=1551406910685
method A end   ThreadName=A time=1551406915689
method A begin ThreadName=AA time=1551406915689
method B begin ThreadName=AA time=1551406915690
method B end   ThreadName=AA time=1551406920691
method A end   ThreadName=AA time=1551406925691
method B begin ThreadName=B time=1551406925691
method B end   ThreadName=B time=1551406930693
method B begin ThreadName=BB time=1551406930693
method B end   ThreadName=BB time=1551406935698

分析结果:在线程A获取锁时其他线程呈阻塞状态直至线程A执行完毕且释放锁,其他线程才可以获取到锁,同时在线程A获取锁执行过程中又成功调用了methodB,这些说明reentrantlocksynchronized关键字一样线程之间是同步顺序执行的,也都具有锁重入特性。

使用Condition实现等待/通知

关键字synchronizedwait()notify()/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock也可以试想同样的功能,但需要借助于Condition对象。相对于notifynotifyAllCondition具有跟高的灵活性,在使用notifynotifyAll时,被通知的线程是由JVM随机选择的,而ReentrantLock结合Condition类是可以“选择性通知的”。

Condition中通知/等待方法

  • await():相当于Object类中的wait()方法
  • await(long time, TimeUtil unit):相当于Object类中的wait(long timeout)方法
  • signal():相当于Object类中的notify()方法
  • signalAll():相当于Object类中的notifyAll()方法

简单使用

注意:调用condition.await()之前需要执行lock.lock()来获取同步监视器。

public class MyServer {
    private ReentrantLock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void await() {
        try {
            lock.lock();
            System.out.println("await  时间为 " + System.currentTimeMillis());
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signal() {
        try {
            lock.lock();
            System.out.println("signal 时间为 " + System.currentTimeMillis());
            condition.signal();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final MyServer myServer = new MyServer();
        new Thread(new Runnable() {
            public void run() {
                myServer.await();
            }
        }).start();
        Thread.sleep(3000);
        myServer.signal();
    }
}

运行结果为:

await  时间为 1551420692029
signal 时间为 1551420695031

使用多个Condition实现“选择性”通知线程

public class MyServer {
    private ReentrantLock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();

    @SuppressWarnings("all")
    public void awaitA() {
        try {
            lock.lock();
            System.out.println("begin await A 时间为 " + System.currentTimeMillis());
            conditionA.await();
            System.out.println("end   await A 时间为 " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    @SuppressWarnings("all")
    public void awaitB() {
        try {
            lock.lock();
            System.out.println("begin await B 时间为 " + System.currentTimeMillis());
            conditionB.await();
            System.out.println("end   await B 时间为 " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    @SuppressWarnings("all")
    public void signalAll_A() {
        try {
            lock.lock();
            System.out.println("  signalall A 时间为 " + System.currentTimeMillis());
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
    }

    @SuppressWarnings("all")
    public void signalAll_B() {
        try {
            lock.lock();
            System.out.println("  signalall B 时间为 " + System.currentTimeMillis());
            conditionB.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final MyServer myServer = new MyServer();
        Thread threadA = new Thread(new Runnable() {
            public void run() {
                myServer.awaitA();
            }
        });
        threadA.setName("A");
        threadA.start();

        Thread threadB = new Thread(new Runnable() {
            public void run() {
                myServer.awaitB();
            }
        });
        threadB.setName("B");
        threadB.start();

        Thread.sleep(3000);
        myServer.signalAll_B();
    }
}

在上述代码中我们定义了两个Condition,分别用于两个线程的等待和唤醒,而我们只唤醒线程B而线程A则继续等待。结果如下:

begin await A 时间为 1551424032113
begin await B 时间为 1551424032116
  signalall B 时间为 1551424035121
  end await B 时间为 1551424035122

公平锁与非公平锁

公平锁就是线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出的顺序。非公平锁就是随机获取锁的,属于一种枪占机制。先来不一定先得到,谁抢到属于谁,所以是不公平的。

通过构造方法ReetrantLock(boolean isFair)来创建对应公平锁或不公平锁。无参默认为非公平锁。

使用ReentrantReadWriteLock类

使用ReetrantLock具有完全互斥排他的效果,即同一时间只有一个线程能够执行Reentrantlock.lock()方法后的代码。虽然这样能保持实例变量的线程安全性,但是效率确实非常低的。然而使用读写锁ReentrantReadWriteLock类,可以提高运行效率,在不需要操作实例变量的情况下,可以使用读写锁ReentrantReadWriteLock来提高任务的执行效率。

读写锁ReentrantReadWriteLock有两个锁,一个是读操作的锁即共享锁,一个是写操作锁即排它锁。在多个读锁之间不互斥,读锁和写锁互斥,写锁和写锁互斥。在多个线程中,如果没有线程进行写操作时,可以有多个线程同时进行读操作;而其中一个线程正在写操作时,其他线程的读操作或写操作都只能等当前的写线程执行完毕。

读写或写读互斥

public class MyServer {
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        try {
            lock.readLock().lock();
            System.out.println("获得读锁:"+Thread.currentThread().getName()+" "+System.currentTimeMillis());
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

    public void write() {
        try {
            lock.writeLock().lock();
            System.out.println("获得写锁:"+Thread.currentThread().getName()+" "+System.currentTimeMillis());
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final MyServer myServer = new MyServer();
        Thread threadA = new Thread(new Runnable() {
            public void run() {
                myServer.read();
            }
        });
        threadA.setName("A");
        threadA.start();

        Thread threadB = new Thread(new Runnable() {
            public void run() {
                myServer.write();
            }
        });
        threadB.setName("B");
        threadB.start();

        Thread threadC = new Thread(new Runnable() {
            public void run() {
                myServer.read();
            }
        });
        threadC.setName("C");
        threadC.start();
    }
}

结果如下:

获得读锁:A 1551509003778
获得读锁:A 1551509013785
获得读锁:A 1551509023789

由结果可知,读写和写读是互斥的

读读共享

更改上面的main方法:

public static void main(String[] args) throws InterruptedException {
    final MyServer myServer = new MyServer();

    Thread threadA = new Thread(new Runnable() {
        public void run() {
            myServer.read();
        }
    });
    threadA.setName("A");
    threadA.start();

    Thread threadB = new Thread(new Runnable() {
        public void run() {
            myServer.read();
        }
    });
    threadB.setName("B");
    threadB.start();
}

结果如下:

获得读锁:A 1551509377678
获得读锁:B 1551509377678

由结果可知:读和读共享

写写互斥

更改上面的main方法:

public static void main(String[] args) throws InterruptedException {
    final MyServer myServer = new MyServer();

    Thread threadA = new Thread(new Runnable() {
        public void run() {
            myServer.write();
        }
    });
    threadA.setName("A");
    threadA.start();

    Thread threadB = new Thread(new Runnable() {
        public void run() {
            myServer.write();
        }
    });
    threadB.setName("B");
    threadB.start();
}

结果如下:

获得写锁:A 1551509613785
获得写锁:B 1551509623788

由结果知:写写互斥

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/02/13/java-multi-threag-programming-lock/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Java多线程编程六(使用Lock)
可重入锁ReentrantLock 使用ReentrantLock方法 lock():锁定 unlock():解除锁定 int getHoldCount():返回当前线程保持此锁定的个数,也就是调用lock()方法……
<<上一篇
下一篇>>
文章目录
关闭
目 录