java多线程(二)锁对象

转载请注明出处:http://blog.csdn.net/xingjiarong/article/details/47679007 
在上一篇博客中,我们讨论了Race Condition现象以及它产生的原因,现在我们知道它是不好的一种现象了,那么我们有什么方法避免它呢。最直接有效的方式就是放弃多线程,直接改为使用单线程但操作数据,但是这是不优雅的,因为我们知道有时候,多线程有它自己的优势。在这里我们讨论两种其他的方法——锁对象和条件对象。

锁对象

java SE5.0之后为实现多线程的互斥引入了ReentrantLock类。ReentrantLock类一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

ReentrantLock类有两种构造方法:

构造方法

一、不带公平参数的构造方法

private ReentrantLock lock = new ReentrantLock();
  • 1

默认的是非公平锁,这种锁不会根据线程等待时间的长短来优先调度线程。

这样就构造了一个锁对象lock。

二、带公平参数的锁对象

private ReentrantLock lock = new ReentrantLock(true);
  • 1

此类的构造方法接受一个可选的公平 参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。

公平锁和非公平锁的区别:

与采用默认设置(使用不公平锁)相比,使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁和保证锁分配的均衡性时差异较小。不过要注意的是,公平锁不能保证线程调度的公平性。因此,使用公平锁的众多线程中的一员可能获得多倍的成功机会,这种情况发生在其他活动线程没有被处理并且目前并未持有锁时。

使用方法

1class X { 2 private final ReentrantLock lock = new ReentrantLock(); 3 4 // 其他变量的定义 5 6 public void m() { 7 lock.lock(); // 当试图获得锁时,如果锁已经被别的线程占有,那么该线程会一直被阻塞,直到获得锁 8 try { 9 // 处理数据 10 } finally { 11 lock.unlock(); //释放锁 12 } 13 } 14}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

首先为大家介绍一下,ReentrantLock类的两个最常用的方法:

lock() 
获得锁对象,如果该锁对象没有被其他线程占有,那么可以立刻获得锁,并执行接下来的处理;如果锁对象已经被其他对象占有,那么该线程就会被阻塞在请求锁的对象的操作上,直到其他的线程释放锁,该线程得到锁,才能继续的向下执行。

unlock() 
释放锁,已经获得锁对象的线程在操作完数据后要释放锁,以便其他的线程重新获得锁来执行自己的操作,否则所有的试图获得锁的线程都不能继续向下执行。

使用方法简单明了,就是在执行各个线程都要操作相同数据的代码之前请求锁,在finally语句中释放锁,为什么要在finally中释放锁呢,这是因为如果try语句块中有语句发生异常,则会直接跳过try中所有的剩余代码包括unlock(),所以锁对象就不能得到释放,其他的线程也不能继续向下执行,导致程序不能继续执行,我们在finally中释放锁,这样就能保证一定可以将锁释放掉,不管获得锁的线程是不是正确的执行结束。

现在来使用这个方法修改一下我们上一篇博客中的代码:

1import java.util.concurrent.locks.ReentrantLock; 2 3class MyThread implements Runnable { 4 /** 5 * 计算类型,1表示减法,其他的表示加法 6 */ 7 private int type; 8 /** 9 * 锁对象 10 */ 11 private static ReentrantLock lock = new ReentrantLock(); 12 13 public MyThread(int type) { 14 this.type = type; 15 } 16 17 public void run() { 18 19 if (type == 1) 20 for (int i = 0; i < 10000; i++) { 21 22 lock.lock(); 23 24 Test.num--; 25 26 lock.unlock(); 27 28 } 29 else 30 for (int i = 0; i < 10000; i++) { 31 32 lock.lock(); 33 34 Test.num++; 35 36 lock.unlock(); 37 } 38 39 } 40} 41 42public class Test { 43 44 public static int num = 1000000; 45 46 public static void main(String[] args) { 47 48 Thread a = new Thread(new MyThread(1)); 49 Thread b = new Thread(new MyThread(2)); 50 51 a.start(); 52 b.start(); 53 54 /* 55 * 主线程等待子线程完成,然后再打印数值 56 */ 57 try { 58 a.join(); 59 b.join(); 60 } catch (Exception e) { 61 e.printStackTrace(); 62 } 63 64 System.out.println(num); 65 } 66 67}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

再多运行几次,是不是结果都是正确的呢。

现在,我们来解释一下,什么是可重入的锁。ReentrantLock类中有一个计数器,用来表示一个线程获取锁的数量,初始值为0,当一个线程获得锁时,该值被置为1,可重入的意思就是,已经获得锁的线程还可以继续调用同一个锁所保护的方法,也就是再一次获得锁,当再一次获得锁时,ReentrantLock中的计数器就加1,每释放一次锁,计数器就减1,当计数器减为0的时候,这个线程才是真正的释放了这个锁。

我们接着讨论另外一个非常重要的问题。ReentrantLock类是依赖于创建它的类的对象。什么意思呢,就是说如果两个线程同时访问同一个ReentrantLock对象的lock()方法保护的方法时,OK,这是没有问题的,锁对象会成功的保护数据操作不会出错。但是如果两个线程同时访问ReentrantLock类的不同对象的被lock()保护的方法,那么这两个线程是不会相互影响的,也就是说lock()方法这时不能保证数据的正确性。

我们来看一下上边那个代码的这一部分:

1Thread a = new Thread(new MyThread(1)); 2Thread b = new Thread(new MyThread(2));
  • 1
  • 2
  • 3

这里建立了两个对象a和b,所以他们每个类都有自己的ReentrantLock对象,这就是我们上边所说的ReentrantLock类的不同对象,这样如果两个线程分别操作a和b的数据,lock方法是不会有效的。

不信我们试试看这个代码:

1import java.util.concurrent.locks.ReentrantLock; 2 3class MyThread implements Runnable { 4 /** 5 * 计算类型,1表示减法,其他的表示加法 6 */ 7 private int type; 8 /** 9 * 锁对象 10 */ 11 private ReentrantLock lock = new ReentrantLock(); 12 13 public MyThread(int type) { 14 this.type = type; 15 } 16 17 public void run() { 18 19 if (type == 1) 20 for (int i = 0; i < 10000; i++) { 21 22 lock.lock(); 23 24 Test.num--; 25 26 lock.unlock(); 27 28 } 29 else 30 for (int i = 0; i < 10000; i++) { 31 32 lock.lock(); 33 34 Test.num++; 35 36 lock.unlock(); 37 } 38 39 } 40} 41 42public class Test { 43 44 public static int num = 1000000; 45 46 public static void main(String[] args) { 47 48 Thread a = new Thread(new MyThread(1)); 49 Thread b = new Thread(new MyThread(2)); 50 51 a.start(); 52 b.start(); 53 54 /* 55 * 主线程等待子线程完成,然后再打印数值 56 */ 57 try { 58 a.join(); 59 b.join(); 60 } catch (Exception e) { 61 e.printStackTrace(); 62 } 63 64 System.out.println(num); 65 } 66 67}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

注意这里的代码和上边的代码并不一样,唯一的区别就在于声明ReentrantLock对象时前边是否加了static,这里是没有static修饰,再运行几次,是不是结果是不正确的呢。

为什么会这样呢?因为如果被static修饰,那么两个线程就是共用的同一个lock对象,如果不被static修饰,那么每个线程就是使用的它自己的lock对象,所以不会static修饰就会出现错误。

在下一篇博客里,我会为大家介绍java条件对象,希望与大家一起学习一起进步,请大家继续关注我的博客,如果大家支持我的话,就顶我一下吧。

版权声明:本文为博主原创文章,转载请注明出处,查看原文章,请访问:http://blog.csdn.net/xingjiarong

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )