您的位置:首页 > 编程语言 > Java开发

我是菜鸟:java并发编程

2015-08-26 17:18 477 查看
最近找工作,发现并发这块相当重要,准备系统的学习下,对,是学习。一些简单的线程知识,就再记录笔记,知识记录java线程同步中的一些内容。

线程的中断

一开始,我始终没有明白interrupt,中断异常这些概念的区分以及函数的用法,下面记录下:

可以通过调用interrupt方法来中断线程,这时线程的中断位将被置位,检查线程是否被中断用Thread.isInterrupted()方法。

interrupt( )

如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。也就是当一个线程被阻塞了,再调用interupt()方法,会产生InterruptedException异常。

interrupted()方法也可以检查是否被中断,但是该方法会将中断状态置位为false。

线程同步

在 JAVA SE5.0 提供了可重入锁和Sysnchronized关键字来保证线程同步。

可重入锁

基本结构

myLock.lock() ; //加锁

try{

cirtical section;

} finally{

myLock.unlock(); //解锁

}

可重入锁顾名思义就是可以重复的获取已经持有的锁,锁保持一个持有计数来跟踪对lock方法的嵌套调用,因此可以调用另外一个使用相同锁的方法。(sysnchronized隐式的支持可重入)

ReentrantLock()提供了2个构造函数,通过传入不同的参数可以保证锁的公平性问题(即满足锁请求的FIFO特点,可以减少饥饿的发生,但是效率有所降低。)

下面是具体内部实现—-通过组合自定义同步器来实现锁的获取与释放。:

通过以上的分析,我们知道可重入锁需要解决:

1. 线程再次获取锁;

2. 锁的最终释放, 当锁的持有计数为0的时候,代表成功释放。

获取锁的逻辑:

step1. 获取当前锁的持有计数,如果为0,代表当前对象可以获得锁,返回true;如果不为0, 执行step2;(使用CAS方式设置同步状态,即持有计数的值)

step2. 首先判断请求锁是都为之前对象锁的持有对象,如果是,更改计数(+1),返回true,否则返回false。

(成功获取锁的线程再次请求锁时候,只是更新了同步状态值。)

对于可重入锁释放来说,只有当同步状态为0时候,将占有线程设为null,并返回true,释放成功。

对于公平锁而言,与上述非公平锁来说,唯一不同在于step1中,不仅使用了CAS设置同步状态,还需要判断当前节点是否有前驱节点,如果有,则表示有线程比当前线程更早的请求了锁,因此需要等待。

非公平性锁容易出现一个先吃个连续获取锁的情况。但是非公平锁可以减少了线程的切换,故效率上有所提高。

条件对象

condition概述

当线程获取到了锁以后,不一定能够执行,因为可能一些条件不满足,在java中有条件对象。

在Java中,任意一个对象都有一组监视器方法(object.wait, notify(all)), 这些方法与Synchronized关键字配合可以实现等待/通知模式, 而Condition对象也有类似的监视器方法,与Lock配合实现等待/通知模式。区别是后者可以更加灵活的支持更多的需求。

Condition定义了等待/通知2中类型的方法,当线程调用这些方法的时候,需要获的Condition对象关联的锁,Condition对象是有Lock对象创建出来的。

Condition对象上的一些方法

void await();

void awaitUniterruptibly() // 对中断不敏感,不会响应中断,在Synchronized中没有该功能;

boolean awaitUntil(Data deadline) // 当前线程进入到等待状态直到被通知,中断或则到达某个时间,同样Synchronized模式中不支持。

一个锁上可以定义多个Condition对象。

代码样式:

Lock lock = new ReentrantLock();

Condition condition = lock.newCondition();

public void conditionWait() throws InterruptedException{

lock.lock();

try{

while(某个条件)

conditon.await( );

其他操作;

}finally{

lock.unlock();

}

}

condition 内部实现

Condition对象是同步器AbstractQueuedSynchronizer的内部类,每个condition对象包含一个等待队列。

1. 等待队列

一个FIFO队列,队列中的每个节点包含了一个线程的引用,该线程就是Condition对象上等待的线程,如果一个线程调用了Condition.await( )方法,那么该线程就会释放锁,构造成节点加入等待队列并进入等待状态。

注意该过程中更新节点的尾节点,没有使用CAS保证,因为调用await()方法的线程必定是先获取到了锁。

2. await()方法

调用await() 方法后,当前线程进入同步队列并释放锁,变为等待状态,当冲await()方法返回时候,当前线程一定重新获取了与condition相关联的锁。

3. signal()方法

先检查当前 线程是否获取了锁,接着获取等待队列的首节点将其移动到同步队列并唤醒节点中的线程。(容易造成死锁)

Synchronized关键字

java的每个对象都内置一个锁,synchronized获取的是对象的内部锁,并且该锁有个内置的条件对象。对象的wait方法相当于调用了内部锁的await()方法。

Synchronized关键字有3种方式:

对于一般的普通方法,获取的锁是当前实例;

对于类方法,获取的锁是该类的锁(.class);

对于代码区,获取的锁是Synchronized关键字括号中配的对象。

局限性:

1. 每个锁仅仅为一个单一条件,可能不够;

2. 试图获得锁时候不能设置超时;

3. 不能中断一个正在试图获得锁的线程;

读写锁

读写锁的使用

ReentrantLock是排它锁,同一个时候只是允许一个线程访问,而读写锁是同一个时刻允许多个线程读,但是写时,必须是仅仅一个线程。当一个应用,大多数时候在读的时候,利用读写锁可以提高并发和吞吐量。

使用步骤:

1. 获取读写锁

ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

2. 抽取读锁和写锁

Lock readLock = rwl.readLock();

Lock writeLock = rwl.writeLock();

3. 对读、写加锁

// 和使用锁一样,readLock.lock()…readLock.unlock();

读写锁的内部实现

主要包括了读写状态的设计,写锁的获取和释放, 读锁的获取与释放和锁降级

读写状态的设计

读写锁同样是通过自定义同步器来实现的同步功能,与可重入锁不一样的是,可重写锁同步状态表示锁被一个线程重复获取的次数,而读写锁的同步状态上(一个整数)维护多个读线程和一个写线程的状态。那么一个整数如何来维护多种状态呢?那就是 “按位切割使用“这个变量。在设计的时候,读写锁将这个变量分为高16位和低16位(表示写)。

如何获取状态?加入现在的同步状态为S,那么,

获取写状态:S&0X0000FFFF

读状态:S>>>16

如何设置呢?

设置写状态:S+1;

设置读状态:S+(1<<<16);

写锁的获取与释放

类似于可重入锁的实现。判断条件为:如果当前线程在获取锁时候,读锁已经被获取(读状态不为0)或者改线程不是已经获取写锁的线程,那么当前线程进入等待状态。

读锁的获取与释放

读锁是一个支持重入的共享锁,能够被多个线程同时获取,在没有被其它写线程访问时候,读锁总能够被成果获取,仅仅需要安全的增加读的状态。

锁降级

把持住当前的写锁,再获取到读锁,随后释放写锁的过程。

此处有个疑问:当一个线程先获取到读锁的时候,有另外的线程需要获取写锁,显然这个线程不能获取写锁,等待读状态为0才能获取到,那么如果同时有其他线程在永远不停的获取读锁,这个读状态永远都不为0,那么写线程是否会饿死呢?为了防止这种写锁可能会永远等待,饿死的情况出现是如何避免的?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: