Java同步—线程锁和条件对象

线程锁和条件对象

在大多数多线程应用中,都是两个及以上线程需要共享对同一数据的存取,所以有可能出现两个线程同时访问同一个资源的情况,这种情况叫做:竞争条件

在Java中为了解决并发的数据访问问题,一般使用这个概念来解决。

有几种机制防止代码收到并发访问的干扰:

1.synchronized关键字(自动创建一个锁及相关的条件)

2.ReentrantLock类+Java.util.concurrent包中的lock接口(在Java5.0的时候引入)

ReentrantLock的使用

 public void Method() {
        boolean flag = false;//标识条件
        ReentrantLock locker = new ReentrantLock();
        locker.lock();//开启线程锁
        try {
            //do some work...
        } catch (Exception ex) {

        } finally {
            locker.unlock();//解锁线程
        }
    }

locker.lock();
确保只有一个线程进入临界区,一旦一个线程进入之后,会获得锁对象,其他线程无法通过lock语句。当其他线程调用lock时,它们会被阻塞,知道第一个线程释放锁对象。

locker.unlock();
解锁操作,一定要放到finally里,因为如果try语句里出了问题,锁必须被释放,否则其他线程将永远被阻塞

因为系统会随机为线程分配资源,所以在线程获得锁对象之后,可能被系统剥夺运行权,这时候其他线程来访问,但是发现有锁,进不去,只能等拿到锁对象的线程把里面的代码执行完毕后,释放锁,第二个线程才能运行。

假设说做一个银行转账的功能,线程锁操作应该定义在银行类转账方法里,因为这样每个银行对象都有一个锁对象,两个线程访问一个银行对象的时候,那么锁以串行方式提供服务。但是,如果每个线程访问不同的银行对象,每个线程都会得到不同的锁对象,彼此之间不会冲突,所以就不会造成不必要的线程阻塞。

锁是可重入的,线程可以重复获得已经持有的锁,锁通过一个持有数量计数来跟踪对lock方法的嵌套使用。

假设说,一个线程获得锁之后,要执行A方法,但是A方法里面又调用了B方法,这时候这个线程获得了两个锁对象,当线程执行B方法的时候,也会被锁死,防止其他线程乱入,当B方法执行完毕后,锁对象变成了一个,当A方法也执行完毕的时候,锁对象变成了0个,线程释放锁。

synchronized关键字

前面我们讲了ReentrantLock锁对象的使用,但是在系统里面我们不一定要使用ReentrantLock锁,Java中还提供了一个内部的隐式锁,关键字是synchronized.

举个例子:

public synchronized void Method() {
    //do some work...
}

只需要在返回值前面加上synchronized锁,就会实现上面ReentrantLock锁同样的效果.

Conditional条件对象

通常,线程拿到锁对象之后,却发现需要满足某一条件才能继续向下执行。

拿银行程序来举例子,我们需要转账方账户有足够的资金才能转出到目标账户,这时候需要用到ReentrantLock对象,因为如果我们已经完成转账方账户有足够的资金的判断之后,线程被其他线程中断,等其他线程执行完之后,转账方的钱又没有了足够的资金,这时候因为系统已经完成了判断,所以会继续向下执行,然后银行系统就会出现问题。

举例:

public void Transfer(int from, int to, double amount) {
    if (Accounts[from] > amount)//系统在结束判断之后被剥夺运行权,然后账户通过网银转出所有钱,银行凉凉
        DoTransfer(from, to, amount);
}

这时候我们就需要使用ReentrantLock对象了,我们修改一下代码:

public void Transfer(int from, int to, double amount) {
    ReentrantLock locker = new ReentrantLock();
    locker.lock();
    try {
        while (Accounts[from] < amount) {
            //等待有足够的钱
        }
        DoTransfer(from, to, amount);
    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        locker.unlock();
    }
}

但是这样又有了问题,当前线程获取了锁对象之后,开始执行代码,发现钱不够,进入等待状态,然后其他线程又因为锁的原因无法给该账户转账,就会一直进入等待状态。

这个问题如何解决呢?

条件对象登场!

public void Transfer(int from, int to, double amount) {
    ReentrantLock locker = new ReentrantLock();
    Condition sufficientFunds = locker.newCondition();//条件对象,
    lock.lock();
    try {
        while (Accounts[from] < amount) {
            sufficientFunds.await();
            //等待有足够的钱
        }
        DoTransfer(from, to, amount);
        sufficientFunds.signalAll();
    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        locker.unlock();
    }
}

条件对象的关键字是:Condition,一个锁对象可以有一个或多个相关的条件对象。可以通过锁对象.newCondition方法获得一个条件对象.

一般关于条件对象的命名需要能够反映它表达的条件的名字,所以在这里我们叫他sufficientFund,表示余额充足的意思。

在进入锁之前,我们创建一个条件,然后如果金额不足,在这里调用条件对象的await方法,通知系统当前线程进入挂起状态,让其他线程执行。这样你这次调用会被锁定,然后系统可以再次调用该方法给其他账户转账,当每一次转账完成后,执行转账操作的线程在底部调用signalAll通知所有线程可以继续运行了,因为我们有可能是转足够的钱给当前账户,这时候有可能该线程会继续执行(不一定是你,是通知所有线程,如果通知的线程还是不符合条件,会继续调用await方法,并完成转账操作,然后通知其他挂起的线程。

你说为啥不直接通知当前线程?不行,可以调用signal方法只通知一个线程,但是如果这个线程操作的账户还是没钱(不是转账给这个账户的情况),那这个线程又进入等待了,这时候已经没有线程能通知其他线程了,程序死锁,所以还是用signal比较保险。

以上是使用ReentrantLock+Condition对象,那你说我要是使用synchronized隐式锁怎么办?

也可以,而且不需要

public void Transfer(int from, int to, double amount) {
     while (Accounts[from] < amount) {
            wait();//这个wait方法是定义在Object类里面的,可以直接用,和条件对象的await一样,挂起线程
            //等待有足够的钱
        }
        DoTransfer(from, to, amount);
        notifyAll();//通知其他挂起的线程
}

Object类里面定义了wait、notifyAll、notify方法,对应await、signalAll和signal方法,用来操作隐式锁,synchronized只能有一个条件,而ReentrantLock显式声明的锁可以用绑定多个Condition条件.

同步块

除了我们上面讲的两种获取线程锁的方式,还有另外一种机制获得锁,这种方式比较特殊,叫做同步块:

Object locker = new Object();
synchronized (locker) {
    //do some work
}

//也可以直接锁当前类的对象
sychronized(this){
    //do some work
}

以上代码会获得Object类型locker对象的锁,这种锁是一个特殊的锁,在上面的代码中,创建这个Object类对象只是单纯用来使用其持有的锁.

这种机制叫做同步块,应用场景也很广:有的时候,我们并不是整个一个方法都需要同步,只是方法里的部分代码块需要同步,这种情况下,我们如果将这个方法声明为synchronized,尤其是方法很大的时候,会造成很大的资源浪费。所以在这种情况下我们可以使用synchronized关键字来声明同步块:

public void Method() {
    //do some work without synchronized
    synchronized (this) {
        //do some synchronized operation
    }
}

监视器的概念

锁和条件是同步中一个很重要的工具,但是它们并不是面向对象的。多年来,Java的研究人员努力寻找一种方法,可以在不需要考虑如何加锁的情况下,就能保证多线程的安全性。最成功的的一个解决方案叫做monitor监视器,这个对象内置于每一个Object变量中,相当于一个许可证。拿到许可证就可以进行操作,没有拿到则需要阻塞等待。

监视器具有以下特性:

1.监视器是只包含私有域的类

2.每个监视器对象都有一个相关的锁

3.使用监视器对象的锁对所有的方法进行加锁(举个例子:如果调用obj.Method方法,obj对象的锁会在方法调用的时候自动获得,当方法结束或返回之后会自动释放该锁因为所有的域都是私有的,这样可以确保一个线程在操作类对象的时候,没有其他线程可以访问里面的域

4.该锁对象可以有任意多个相关条件

你也可以自己创建一个监视器类,只要符合以上的要求即可。

其实我们使用的synchronized关键字就是使用了monitor来实现加锁解锁,所以又被称为内部锁因为Object类实现了监视器,所以对象又被内置于任何一个对象之中。这就是我们为什么可以使用synchronized(locker)的方式锁定一个代码块了,其实只是用到了locker对象中内置的monitor而已。每一个对象的monitor类又是唯一的,所以就是唯一的许可证,拿到许可证的线程才可以执行,执行完后释放对象的monitor才可以被其他线程获取。

举个例子:

synchronized (this) {
    //do some synchronized operation
}

它在字节码文件中会被编译为:

monitorenter;//get monitor,enter the synchronized block
            //do some synchronized operation
monitorexit;//leavel the synchronized block,release the monitor

原文地址:https://www.cnblogs.com/Fill/p/9379776.html

时间: 2024-08-01 20:35:31

Java同步—线程锁和条件对象的相关文章

深入理解java同步、锁机制

本片文章尝试从另一个层面来了解我们常见的同步(synchronized)和锁(lock)机制.如果读者想深入了解并发方面的知识推荐一本书<java并发编程实战>,非常经典的一本书,英语水平好的同学也可以读一读<Concurrent programming in Java - design principles and patterns>由Doug Lea亲自操刀,Doug Lea是并发方面的大神,jdk的并发包就是由他完成的. 我们都知道在java中被synchronized修饰的

java 多线程(三)条件对象

转载请注明出处:http://blog.csdn.net/xingjiarong/article/details/47417383 在上一篇博客中,我们学会了用ReentrantLock来控制线程訪问同一个数据,防止出现Race Condition.这一次呢.我们继续深入的学习,学习一下java中的条件对象.条件对象在多线程同步中用到的比較多. 首先,我们来介绍一下临界区. 临界区:在同步的程序设计中.临界区指的是一个訪问共用资源的程序片段,而这些共用资源又具有无法同一时候被多个线程訪问的特性.

Java同步—线程和进程

进程和线程 1.线程和进程的定义 进程定义:一个程序在一个数据集上的一次动态执行过程. 简单来说: 如果把进程比喻成桌子,线程比喻成人,程序比喻成吃饭. 1.单进程单线程:一个人在一个桌子上吃饭 2.单进程多线程:一堆人在一张桌子上一起吃饭 3.多进程多线程:每个人在自己的桌子上吃饭 单进程多线程的问题是:大家容易争抢,造成一些问题. 多进程多线程的问题是:各吃各的挺好,但是彼此聊天不方便. Windows:开桌子花销大,因此鼓励大家在一个桌子上吃饭,所以Windows多线程的学习重点是解决资源

java中线程锁的概念

java多线程:锁 java的多线程中的锁是干嘛的呢?在网上找了很多博客,大都是很专业的语言,让我一时间摸不着头脑.下面分三个部分来总结多线程中的锁的概念. 一,基础概念: 多线程在运行的时候可能会遇到这样的问题,多个线程要用到同一个资源,那么可能会出现错乱,比如线程要改动资源里的数据,那么多个线程同时改就乱了套了.就像公共厕所,必须要一个一个接着上,不能两个人或者多个人同时上.那么锁这个东西就是像厕所里的门,一个人在上厕所,锁上了们,那下一个人就不能进去了.同样的,如果我们想让某一个程序或者某

深入了解java同步、锁紧机构

该薄膜还具有从本文试图一个高度来认识我们共同的同步(synchronized)和锁(lock)机制. 我们假定读者想了解更多的并发知识推荐一本书<java并发编程实战>,这是一个经典的书,英语水平良好的学生也可以读<Concurrent programming in Java - design principles and patterns>由Doug Lea亲自操刀.Doug Lea是并发方面的大神,jdk的并发包就是由他完毕的. 我们都知道在java中被synchronized

传统的同步线程锁(两)

一. 线程安全 线程安全问题是指程序中公用的东西被多个线程訪问,比方:类的静态变量 线程互斥:是指两个线程之间不能够同一时候执行,他们会互斥,必须等待一个线程执行完成,还有一个才干执行 二. 同步锁 有什么办法能够解决线程安全问题呢?那就是在程序中加锁 Java有两种加锁的方法: 1. 在代码块中加锁 synchronized (this) { ... } 2. 在方法上加锁 public synchronized void xxx(){ ... } 演示样例代码: public class T

java中线程安全的集合对象

Vector与ArrayList Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销. HashTable与HashMap Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的.在多线程并发的环境下,可以直接使用Hashtable,但是要使用HashMap的话就要自己增加同步处理了. StringBuilder与StringBuffer StringBuilder和StringBuffer的方法是一模一样,就前者是多

将线程锁加在对象上与锁加在方法上的区别(模拟火车票联网售票系统:多个线程同时出票,保证每张出票的编号连续且不重复。)

第一种,从结果看来,编号并非随着线程的逐一增加而增加,也意味着不同的人,有的人先抢票,可是线程没有及时运行,抢到票或者买到剩票. 原因: 虽然方法是加锁了,但是不同的线程运行不确定的,而实际上对这个 票号的生成  并没有加锁限制,导致先买者,抢不到票.----- 票号不重复,但是没有优先概念,不连续. ticketNum: 5 Thread Name: Thread 1 线程1 先启动并调用生成票方法, 之后得到5号票 ticketNum: 8 Thread Name: Thread 8 tic

Linux互斥锁、条件变量和信号量

Linux互斥锁.条件变量和信号量  来自http://kongweile.iteye.com/blog/1155490 博客分类: Linux sem_init:初始化信号量sem_t,初始化的时候可以指定信号量的初始值,以及是否可以在多进程间共享.sem_wait:一直阻塞等待直到信号量>0.sem_timedwait:阻塞等待若干时间直到信号量>0.sem_post:使信号量加1.sem_destroy:释放信号量.和sem_init对应. 进行多线程编程,最应该注意的就是那些共享的数据