多线程编程学习四(Lock 的使用)

简介: 一、前言     本文要介绍使用Java5中 Lock 对象,同样也能实现同步的效果,而且在使用上更加方便、灵活,主要包括 ReentrantLock 类的使用和ReentrantReadWriteLock 类的使用。

一、前言

    本文要介绍使用Java5中 Lock 对象,同样也能实现同步的效果,而且在使用上更加方便、灵活,主要包括 ReentrantLock 类的使用和ReentrantReadWriteLock 类的使用。

二、使用ReentrantLock 类

1、在java多线程中,可以使用synchronized关键字来实现线程之间同步互斥,但在JDK1.5中新增加的ReentrantLock也能达到同样的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定、多路分支通知等功能,而且在使用上也比synchronized更加的灵活。

2、调用lock.lock()代码的线程就持有了“对象监视器”,即lock 持有的是对象锁,依赖于该类的实例存在。

public class MyService {
    private Lock lock=new ReentrantLock();
    public void testMethod(){
        lock.lock();
        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+(i+1));
        }
        lock.unlock();
    }
}
View Code

3、关键字synchronized 与wait() 和 notify()/notifyAll() 方法相结合可以实现等待/通知模式,类ReentrantLock 也可以实现同样的功能,但需要借助于Condition对象。

Object类中的wait()方法相当于Condition类中的await()方法
Object类中的wait(long timeout)方法相当于Condition类中的await(long time,TimeUnit unit)方法
Object类中的notify()方法相当于Condition类中的signal()方法
Object类中的notifyAll()方法相当于Condition类中的signalAll()方法
public class Myservice {
    private Lock lock=new ReentrantLock();
    private Condition condition=lock.newCondition();

    //等待
    public void waitMethod(){
        try {
            lock.lock();
            System.out.println("A");
            condition.await();//调用的Condition的await等待方法也需要在同步方法中,否则会报错
            System.out.println("B");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    //唤醒
    public void signal(){
        try {
            lock.lock();
            System.out.println("现在开始唤醒...");
            condition.signal();
        }finally {
            lock.unlock();
        }
    }
}
View Code

4、使用多个Condition对象 实现线程之间的选择性通知。

public class MyService {
    private Lock lock=new ReentrantLock();
    //通过定义多个Condition实现选择性通知,可以唤醒指定种类的线程,这是
    //控制部分线程行为的方便形式
    private Condition conditionA=lock.newCondition();
    private Condition conditionB=lock.newCondition();

    public void awaitA(){
        try {
            lock.lock();
            System.out.println("awaitA begin");
            conditionA.await();
            System.out.println("awaitA end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void awaitB(){
        try {
            lock.lock();
            System.out.println("awaitB begin");
            conditionB.await();
            System.out.println("awaitB end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void signalA(){
        try {
            lock.lock();
            System.out.println("现在开始唤醒awaitA");
            conditionA.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public void signalB(){
        try {
            lock.lock();
            System.out.println("现在开始唤醒awaitB");
            conditionB.signalAll();
        }finally {
            lock.unlock();
        }
    }
}
View Code
public class Run
{
    public static void main(String[] args) throws InterruptedException
    {
        MyService myService=new MyService();
        Thread threadA=new Thread(){
            @Override
            public void run()
            {
                super.run();
                myService.awaitA();
            }
        };

        Thread threadB=new Thread(){
            @Override
            public void run()
            {
                super.run();
                myService.awaitB();
            }
        };

        threadA.start();
        threadB.start();
        Thread.sleep(1000);
        myService.signalA();
        Thread.sleep(1000);
        myService.signalB();

    }
}
View Code

5、公平锁和非公平锁

公平锁:表示线程获得锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。
非公平锁:一种获得锁的抢占机制,是随机获取锁的,和公平锁不一样的就是先来的不一定先得到锁,这种方式可能造成某些线程一直拿不到锁,结果也就是不公平的了。
public class Service {
    private Lock lock;

    public Service(boolean isFair)
    {
        //通过这种方式创建公平锁(true)和非公平锁(false)
        lock=new ReentrantLock(isFair);
    }

    public void methodA(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"正在运行");
        }finally {
            lock.unlock();
        }
    }
}
View Code
public class Run {
    public static void main(String[] args)
    {
        final Service service=new Service(true);
        Runnable runnable=new Runnable() {
            @Override
            public void run()
            {
             service.methodA();
            }
        };

        Thread[] threads=new Thread[10];
        for (int i=0;i<10;i++){
            threads[i]=new Thread(runnable);
            threads[i].setName("线程"+(i+1));
            threads[i].start();
        }
    }
}
View Code

 6、ReentrantLock 常用方法介绍

(1) int getHoldCount() 查询当前线程保持此锁定的个数,也就是线程中调用lock方法的次数。

(2) int getQueueLength() 返回正等待此锁定的线程估计数,比如有5个线程,1个线程正占用了这个Lock锁在执行,则调用此方法返回的就是4。该值仅是估计的数字,因为在此方法遍历内部数据结构的同时,线程的数目可能动态地变化。此方法用于监视系统状态,不用于同步控制。

(3) int getWaitQueueLength(Condition condition) 返回等待与此锁定相关的给定条件Condition的线程估计数,比如有五个线程,每个线程都执行了同一个condition对象的await()方法,则调用此方法返回的值就是5。

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

    public void methodA(){
        try {
            lock.lock();
            System.out.println("A getHoldCount 调用lock的次数=>"+lock.getHoldCount());
            Thread.sleep(2000);
            System.out.println("A getQueueLength 正在等待的线程数=>"+lock.getQueueLength());
            condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //测试getWaitQueueLength方法
    public Integer methodC(){
        try {
            lock.lock();
            return lock.getWaitQueueLength(condition);
        }finally {
            lock.unlock();
        }

    }
}
View Code
public class Run{

    public static void main(String[] args) throws InterruptedException {
        Service service=new Service();
        Runnable runnable=new Runnable()
        {
            @Override
            public void run()
            {
               service.methodA();
            }
        };

        Thread[] threads=new Thread[5];
        for (int i=0;i<5;i++){
            threads[i]=new Thread(runnable);
            threads[i].start();
        }

        Thread.sleep(1000);
        System.out.println("执行了同一个Condition对象的的await()的线程有:"+service.methodC());
    }
}
View Code

(4) boolean hasQueuedThread(Thread thread) 查询指定的线程是否正在等待获取此锁定。

(5) boolean hasQueuedThreads() 查询是否有线程正在等待获取此锁定。

(6) boolean hasWaiters(Condition condition) 查询是否有线程正在等待与此锁定有关的condition条件

(7) boolean isFair() 判断是不是公平锁。

(8) boolean isHelpByCurrentThread() 查询当前线程是否保持此锁定。

(9) boolean isLocked() 查询此锁定是否由任意线程保持。

(10) void lockInterruptibly() 如果当前线程未被中断,则获取锁定,如果已经被中断,则出现异常。

(11) boolean tryLock() 仅在调用时锁定未被另一个线程锁定的情况下,才获得此锁定。

(12) boolean tryLock(long timeout,TimeUnit unit) 如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。

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

    //测试lockInterruptibly
    public void methodA(){
        try {
            lock.lockInterruptibly();
            System.out.println("methodA=》"+Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if (lock.isHeldByCurrentThread()){//如果当前线程依旧保持对此锁的锁定,则释放
                lock.unlock();
            }
        }
    }
    //测试tryLock
    public void methodB(){
          if (lock.tryLock()){
              System.out.println(Thread.currentThread().getName()+"获得锁");
          }else{
              System.out.println(Thread.currentThread().getName()+"未获得锁");
          }
    }
}
View Code
public class Run
{
    public static void main(String[] args) throws InterruptedException
    {
        Service service=new Service();
        Runnable runnable=new Runnable()
        {
            @Override
            public void run()
            {
                service.methodA();
                service.methodB();
            }
        };

        Thread threadA=new Thread(runnable);
        threadA.setName("A");
        threadA.start();

        Thread.sleep(1000);
        Thread threadB=new Thread(runnable);
        threadB.setName("B");
        threadB.start();
        threadB.interrupt();
    }
}
View Code

(13) lock.awaitUninterruptibly():这个线程将不会被中断,一直睡眠直到其他线程调用signal()或signalAll()方法。

(14) lock.awaitUntil(Date date):这个线程将会一直睡眠直到:

  • 它被中断
  • 其他线程在这个condition上调用singal()或signalAll()方法
  • 指定的日期已经到了

三、使用ReentrantReadWriteLock 类 

      类RenntrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行RenntrantLock.lock()方法后面的任务。这样做虽然保证了实例变量的线程安全性,但效率却是非常低下的,因为即使有时候锁内没有写入内容,而也要等锁释放后,才能进行读取。所以JDK提供了一种读写锁ReentrantReadWriteLock 类,使用它可以加快运行效率。              
      ReentrantReadWriteLock有两个锁,一个是读操作相关的锁,也称为 共享锁;另一个是写操作相关的锁,也叫 排他锁。也就是 多个读锁之间不互斥、读锁与写锁互斥、写锁与写锁互斥。在没有线程Thread进行写入操作时,进行读取操作的多个Thread 都可以获取读锁。而进行写入操作的Thread 只有在获取写锁后才能进行写入操作。即多个Thread可以同时进行读取操作,但是同一个时刻只允许一个Thread 进行写入操作。
 
读读不互斥:
public class Read
{
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();

    public void read(){
        try {
            lock.readLock().lock();
            System.out.println(Thread.currentThread().getName()+"正在读"+System.currentTimeMillis());
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
}
View Code
public class Run
{
    public static void main(String[] args)
    {
        Read read=new Read();
        Runnable runnable=new Runnable()
        {
            @Override
            public void run()
            {
                read.read();
            }
        };
        Thread[] threads=new Thread[10];
        for (int i=0;i<10;i++){
            threads[i]=new Thread(runnable);
            threads[i].start();
            //通过结果可以看到所有线程几乎同时进入lock()方法
            //后面的代码,读读不互斥,可以提高程序运行效率,允许
            //多个线程同时执行lock()方法后面的代码
        }
    }
}
View Code
写写互斥:
public class Write
{
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();

    public void write(){
        try {
            lock.writeLock().lock();
            System.out.println(Thread.currentThread().getName()+"正在写"+System.currentTimeMillis());
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }
}
View Code
public class Run
{
    public static void main(String[] args)
    {
        Write write=new Write();
        Runnable runnable=new Runnable()
        {
            @Override
            public void run()
            {
                write.write();
            }
        };
        Thread[] threads=new Thread[10];
        for (int i=0;i<10;i++){
            threads[i]=new Thread(runnable);
            threads[i].start();
            //通过结果可以看到所有线程每隔两秒运行一次,写写互斥,线程之间是同步运行的
        }
    }
}
View Code

 

    另外,写读、读写都是互斥的,就不举例了。总之,只要出现"写"操作,就是互斥的!

目录
相关文章
|
7天前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
38 17
|
16天前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
50 26
|
26天前
|
Java 调度 开发者
Java线程池ExecutorService学习和使用
通过学习和使用Java中的 `ExecutorService`,可以显著提升并发编程的效率和代码的可维护性。合理配置线程池参数,结合实际应用场景,可以实现高效、可靠的并发处理。希望本文提供的示例和思路能够帮助开发者深入理解并应用 `ExecutorService`,实现更高效的并发程序。
33 10
|
2月前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
228 2
|
3月前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
2月前
|
缓存 Java 调度
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
59 10
|
2月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
2月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
72 3
|
2月前
|
算法 调度 开发者
多线程编程核心:上下文切换深度解析
在多线程编程中,上下文切换是一个至关重要的概念,它直接影响到程序的性能和响应速度。本文将深入探讨上下文切换的含义、原因、影响以及如何优化,帮助你在工作和学习中更好地理解和应用多线程技术。
54 4
|
2月前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程

热门文章

最新文章