【并发技术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


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


相关文章
|
5天前
|
Linux API C++
c++多线程——互斥锁
c++多线程——互斥锁
|
2天前
|
算法 Java 程序员
Java中的线程同步与并发控制
【5月更文挑战第18天】随着计算机技术的不断发展,多核处理器的普及使得多线程编程成为提高程序性能的关键。在Java中,线程是实现并发的一种重要手段。然而,线程的并发执行可能导致数据不一致、死锁等问题。本文将深入探讨Java中线程同步的方法和技巧,以及如何避免常见的并发问题,从而提高程序的性能和稳定性。
|
3天前
|
Java 测试技术 开发工具
Android 笔记:AndroidTrain , Lint , build(1),只需一篇文章吃透Android多线程技术
Android 笔记:AndroidTrain , Lint , build(1),只需一篇文章吃透Android多线程技术
|
3天前
|
Java 测试技术 Python
Python的多线程允许在同一进程中并发执行任务
【5月更文挑战第17天】Python的多线程允许在同一进程中并发执行任务。示例1展示了创建5个线程打印&quot;Hello World&quot;,每个线程调用同一函数并使用`join()`等待所有线程完成。示例2使用`ThreadPoolExecutor`下载网页,创建线程池处理多个URL,打印出每个网页的大小。Python多线程还可用于线程间通信和同步,如使用Queue和Lock。
16 1
|
5天前
|
安全 Java
【JAVA进阶篇教学】第十篇:Java中线程安全、锁讲解
【JAVA进阶篇教学】第十篇:Java中线程安全、锁讲解
|
5天前
|
安全 Java 程序员
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
【Java多线程】面试常考——锁策略、synchronized的锁升级优化过程以及CAS(Compare and swap)
12 0
|
5天前
|
Java
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
26 1
|
5天前
|
安全 C++
C++多线程编程:并发与同步
C++多线程编程:并发与同步
10 0
|
5天前
|
算法 安全 Linux
【探索Linux】P.20(多线程 | 线程互斥 | 互斥锁 | 死锁 | 资源饥饿)
【探索Linux】P.20(多线程 | 线程互斥 | 互斥锁 | 死锁 | 资源饥饿)
12 0
|
5天前
|
安全 Java
Java中的并发编程:理解并发性与线程安全
Java作为一种广泛应用的编程语言,在并发编程方面具有显著的优势和特点。本文将探讨Java中的并发编程概念,重点关注并发性与线程安全,并提供一些实用的技巧和建议,帮助开发人员更好地理解和应用Java中的并发机制。