【并发技术12】线程锁技术的使用(一)

简介: 【并发技术12】线程锁技术的使用

线程锁好比传统线程模型中的 synchronized 技术,但是比 synchronized 方式更加面向对象,与生活中的锁类似,锁本身也应该是个对象。两个线程执行的代码片段如果要实现同步互斥的效果,它们必须用用一个锁对象。锁是上在代表要做操的资源的类的内部方法中,而不是线程代码中。这篇文章主要总结一下线程锁技术中 Lock锁、ReadWriteLock 锁的使用。


1. Lock的简单使用


有了synchronized 的基础,Lock 就比较简单了,首先看一个实例:

public class LockTest {
   public static void main(String[] args) {
       new LockTest().init();
   }
   private void init() {
       final Outputer outputer = new Outputer();
       // 线程1打印:duoxiancheng
       new Thread(new Runnable() {
           @Override
           public void run() {
               while (true) {
                   try {
                       Thread.sleep(5);
                   } catch (InterruptedException e) {
                       // TODO Auto-generated catch block
                       e.printStackTrace();
                   }
                   outputer.output("duoxiancheng");
               }
           }
       }).start();
       ;
       // 线程2打印:eson15
       new Thread(new Runnable() {
           @Override
           public void run() {
               while (true) {
                   try {
                       Thread.sleep(5);
                   } catch (InterruptedException e) {
                       // TODO Auto-generated catch block
                       e.printStackTrace();
                   }
                   outputer.output("eson15");
               }
           }
       }).start();
       ;
   }
   // 自定义一个类,保存锁和待执行的任务
   static class Outputer {
       Lock lock = new ReentrantLock(); //定义一个锁,Lock是个接口,需实例化一个具体的Lock
       //字符串打印方法,一个个字符的打印
       public void output(String name) {
           int len = name.length();
           lock.lock();
           try {
               for (int i = 0; i < len; i++) {
                   System.out.print(name.charAt(i));
               }
               System.out.println("");
           } finally {
               lock.unlock(); //try起来的原因是万一一个线程进去了然后挂了或者抛异常了,那么这个锁根本没有释放
           }
       }
}


这个例子和前面介绍 synchronized 的例子差不多,区别在于将 synchronized 改成了 lock。从程序中可以看出,使用 Lock 的时候,需要先 new 一个 Lock 对象,然后在线程任务中需要同步的地方上锁,但是一定要记得放锁,所以使用 try 块去处理了一下,将放锁的动作放在 finally 块中了。

这是一个线程任务的情况,如果两个线程任务也不麻烦,还是在这个类中新建一个任务方法,因为 Lock 是这个类的成员变量,还是可以用这个 lock,而且必须用这个 lock,因为要实现同步互斥,必须使用同一把锁。


2. 读写锁的妙用


锁又分为读锁和写锁,读锁与读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由 jvm 自己控制的。这很好理解,读嘛,大家都能读,不会对数据造成修改,只要涉及到写,那就可能出问题。我们写代码的时候只要在挣钱的位置上相应的锁即可。读写锁有个接口叫 ReadWriteLock,我们可以创建具体的读写锁实例,通过读写锁也可以拿到读锁和写锁。下面看一下读写锁的例子。


2.1 读写锁的基本用法


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.put(new Random().nextInt(10000));
                   }
               }
           }.start();
           new Thread() { // 开启三个线程读数据
               public void run() {
                   while (true) {
                       q3.get();
                   }
               }
           }.start();
       }
   }
}
class Queue3 {
   private Object data = null; // 共享的数据
   private ReadWriteLock rwl = new ReentrantReadWriteLock();// 定义读写锁
   // 读取数据的任务方法
   public void get() {
       rwl.readLock().lock(); // 上读锁
       try {
           System.out.println(Thread.currentThread().getName()
                   + ":before read: " + data); // 读之前打印数据显示
           Thread.sleep((long) (Math.random() * 1000)); // 睡一会儿~
           System.out.println(Thread.currentThread().getName()
                   + ":after read: " + data); // 读之后打印数据显示
       } catch (InterruptedException e) {
           e.printStackTrace();
       } finally {
           rwl.readLock().unlock();// 释放读锁
       }
   }
   // 写数据的任务方法
   public void put(Object data) {
       rwl.writeLock().lock(); // 上写锁
       try {
           System.out.println(Thread.currentThread().getName()
                   + ":before write: " + this.data); // 读之前打印数据显示
           Thread.sleep((long) (Math.random() * 1000)); // 睡一会儿~
           this.data = data; //写数据
           System.out.println(Thread.currentThread().getName()
                   + ":after write: " + this.data); // 读之后打印数据显示
       } catch (InterruptedException e) {
           e.printStackTrace();
       } finally {
           rwl.writeLock().unlock();// 释放写锁        
       }
   }
}


为了说明读锁和写锁的特点(读锁和读锁不互斥,读锁与写锁互斥,写锁与写锁互斥),我先把上面两个任务方法中上锁和放锁的四行代码注释掉,来看一下运行结果。

6d8cda7ba5c51d382f6333092baf8249_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


其实不管是注释调读锁还是注释调写锁,还是全注释掉,都会出问题,写的时候会有线程去读。那么将读写锁加上后,再看一下运行结果。

20ac3e069ca6317ffdd44ba72915b271_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png


可以看出,有了读写锁,各个线程运行有序,从结果来看,也印证了读锁和读锁不互斥,写锁与读锁、写锁都互斥的特点。


相关文章
|
1月前
|
安全
List并发线程安全问题
【10月更文挑战第21天】`List` 并发线程安全问题是多线程编程中一个非常重要的问题,需要我们认真对待和处理。只有通过不断地学习和实践,我们才能更好地掌握多线程编程的技巧和方法,提高程序的性能和稳定性。
198 59
|
1天前
|
Java 关系型数据库 MySQL
【JavaEE“多线程进阶”】——各种“锁”大总结
乐/悲观锁,轻/重量级锁,自旋锁,挂起等待锁,普通互斥锁,读写锁,公不公平锁,可不可重入锁,synchronized加锁三阶段过程,锁消除,锁粗化
|
29天前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
39 6
|
1月前
|
安全 Java
线程安全的艺术:确保并发程序的正确性
在多线程环境中,确保线程安全是编程中的一个核心挑战。线程安全问题可能导致数据不一致、程序崩溃甚至安全漏洞。本文将分享如何确保线程安全,探讨不同的技术策略和最佳实践。
41 6
|
1月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
58 6
|
1月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
1月前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
3天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
14 1
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
60 1
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
32 3