【并发技术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月前
|
设计模式 缓存 安全
【JUC】(6)带你了解共享模型之 享元和不可变 模型并初步带你了解并发工具 线程池Pool,文章内还有饥饿问题、设计模式之工作线程的解决于实现
JUC专栏第六篇,本文带你了解两个共享模型:享元和不可变 模型,并初步带你了解并发工具 线程池Pool,文章中还有解决饥饿问题、设计模式之工作线程的实现
145 2
|
1月前
|
设计模式 消息中间件 安全
【JUC】(3)常见的设计模式概念分析与多把锁使用场景!!理解线程状态转换条件!带你深入JUC!!文章全程笔记干货!!
JUC专栏第三篇,带你继续深入JUC! 本篇文章涵盖内容:保护性暂停、生产者与消费者、Park&unPark、线程转换条件、多把锁情况分析、可重入锁、顺序控制 笔记共享!!文章全程干货!
177 1
|
4月前
|
Java API 调度
从阻塞到畅通:Java虚拟线程开启并发新纪元
从阻塞到畅通:Java虚拟线程开启并发新纪元
353 83
|
4月前
|
存储 Java 调度
Java虚拟线程:轻量级并发的革命性突破
Java虚拟线程:轻量级并发的革命性突破
329 83
|
6月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
249 0
|
1月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
144 6
|
4月前
|
Java API 微服务
为什么虚拟线程将改变Java并发编程?
为什么虚拟线程将改变Java并发编程?
301 83
|
1月前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
226 0
|
2月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
220 16

热门文章

最新文章

下一篇
oss云网关配置