java多线程(二)锁对象

Wesley13
• 阅读 431

转载请注明出处: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 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。

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

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

使用方法

class X {
    private final ReentrantLock lock = new ReentrantLock();

    // 其他变量的定义

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

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

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

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

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

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

import java.util.concurrent.locks.ReentrantLock;

class MyThread implements Runnable {
    /**
     * 计算类型,1表示减法,其他的表示加法
     */
    private int type;
    /**
     * 锁对象
     */
    private static ReentrantLock lock = new ReentrantLock();

    public MyThread(int type) {
        this.type = type;
    }

    public void run() {

        if (type == 1)
            for (int i = 0; i < 10000; i++) {

                lock.lock();

                Test.num--;

                lock.unlock();

            }
        else
            for (int i = 0; i < 10000; i++) {

                lock.lock();

                Test.num++;

                lock.unlock();
            }

    }
}

public class Test {

    public static int num = 1000000;

    public static void main(String[] args) {

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

        a.start();
        b.start();

        /*
         * 主线程等待子线程完成,然后再打印数值
         */
        try {
            a.join();
            b.join();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(num);
    }

}
  • 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()方法这时不能保证数据的正确性。

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

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

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

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

import java.util.concurrent.locks.ReentrantLock;

class MyThread implements Runnable {
    /**
     * 计算类型,1表示减法,其他的表示加法
     */
    private int type;
    /**
     * 锁对象
     */
    private ReentrantLock lock = new ReentrantLock();

    public MyThread(int type) {
        this.type = type;
    }

    public void run() {

        if (type == 1)
            for (int i = 0; i < 10000; i++) {

                lock.lock();

                Test.num--;

                lock.unlock();

            }
        else
            for (int i = 0; i < 10000; i++) {

                lock.lock();

                Test.num++;

                lock.unlock();
            }

    }
}

public class Test {

    public static int num = 1000000;

    public static void main(String[] args) {

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

        a.start();
        b.start();

        /*
         * 主线程等待子线程完成,然后再打印数值
         */
        try {
            a.join();
            b.join();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(num);
    }

}
  • 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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
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
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这