让我们来认识一下多线程中的高级篇(我来把之前的坑填上)
同步代码块,synchronized,
同步代码块,就是定义了一组原子性代码。所谓原子性代码,就是说在这一组代码块中,只允许一个线程进入。直到这个线程运行完毕,下一个线程才可以进入。同步代码块的格式如下:
synchronized (obj) {
}
这里的obj,我们可以理解为一把锁,叫做同步监视器。
我们要将一段代码保护起来,只允许同一时间点只有一个线程调用,那么可以使用同步代码块。
我们可以完成一段代码来证明这个的
/**
* 测试多线程情况下使用同步代码块
*/
public class SafeThreadTest {
V v = new V();
public static void main(String[] args) {
SafeThreadTest test = new SafeThreadTest();
test.test();
}
/**
* 开两个线程,分别调用V对象的打印字符串的方法
*/
public void test(){
new Thread(new Runnable() {
@Override
public void run() {
while(true){
v.printString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
v.printString("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
}
}
}).start();
}
/**
* 这个类负责打印字符串
* @author zwd
*/
class V {
/**
* 为了能使方法运行速度减慢,我们一个字符一个字符的打印
* @param s
*/
public void printString(String s){
for(int i = 0;i<s.length();i++){
System.out.print(s.charAt(i));
}
System.out.println();
}
}
}
会发生了乱码现象,是因为test方法里面是存在两个线程调用while(true)一直执行,只要能够争抢得到cpu就回去执行,然后去执行方法,但是因为可能前一个程序还没有执行完就被另外一个程序争抢到了位置,所以会出现没有打印完就换行,或者是一行打印多个超过本身的字符。
我们如何去避免这些问题呢?
我们可以使用synchronized代码块,这个东西可以将我们的方法锁起来只是允许一个线程单独进入,没有执行完毕不允许其他线程进行争抢。
具体实现呢就下面的代码
package com.langsin.threadTest;
/**
* 测试多线程情况下使用同步代码块
*
*/
public class SafeThreadTest {
V v = new V();
public static void main(String[] args) {
SafeThreadTest test = new SafeThreadTest();
test.test();
}
/**
* 开两个线程,分别调用V对象的打印字符串的方法
*/
public void test(){
new Thread(new Runnable() {
@Override
public void run() {
while(true){
v.printString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
v.printString("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
}
}
}).start();
}
/**
* 这个类负责打印字符串
* @author Administrator
*/
class V {
/**
* 为了能使方法运行速度减慢,我们一个字符一个字符的打印
* @param s
*/
Object lock = new Object();
public void printString(String s){
synchronized (lock) {
for(int i = 0;i<s.length();i++){
System.out.print(s.charAt(i));
}
System.out.println();
}
}
}
}
我们是怎么去实现这一段代码的呢?
我们完成了一个Object lock = new Object();
建立了一个object的对象,这个对象的类是object是所有类的父类,可以说这个父类几乎是没有任何意义的,我们把 synchronized (lock) {}中放进了一个object的对象,有着什么意义呢?
其实我们使用synchronized是有限制的,在synchronized中锁的必须要是一个公共的部分,多线程的核心也就是在争抢公共资源,两个线程去共同争抢一个资源,所以我们将object对象变成一个,就可以将多线程争抢cpu锁住,只能有一个完成以后后面的那一个才能进行下面的东西,那么我们问题又来了,我们可不可以直接锁this?
答案是可以的,在我们这个类里面这个,只是创建一个V的对象,也就是说V的对象是唯一的,V v = new V();
,我们创建了这个唯一的对象时,又创建了两个Thread的对象,两个对象都是在调用V对象的方法,调用的都是一个对象的,所以synchronized锁住的其实也是一个对象
下面是测试用的代码
class V {
/**
* 为了能使方法运行速度减慢,我们一个字符一个字符的打印
* @param s
*/
// Object lock = new Object();
public void printString(String s){
synchronized (this) {
for(int i = 0;i<s.length();i++){
System.out.print(s.charAt(i));
}
System.out.println();
}
}
}
为了避免重复我只是修改了V这个类下面是执行的结果
我们得出了几乎和锁住一个object一样的结果,证明了我们猜想。
那么问题又来了,那我们可不可以将形参s锁住呢?
先说答案,不行,为什么不行,我们来分析一下,因为调用的时候传进printString方法中的String s不是同一个值,也就是说“AAAAAAAAAAAAAA!=BBBBBBBBBB”他们是不相等的,所以synchronized锁无法锁住,所以还是会跟之前是一样的结果,
下面是测试的结果
测试的结果证明了我的结论,
那我们继续来思考,如果是静态方法,可以用this吗?
答案是不行,因为什么呢?因为static是一个静态的,static方法是静态的方法,是先于任何实例对象存在的,static方法在加载时已经存在,但是类的对象是在创建时才存在,而this是指的是当前对象,所以不能够使用this,相应的,super也是不能够使用的,那么在synchronized()括号里面可以使用什么呢?这个答案是我自己试出来的,只能够使用引用类型而且还静态的static,不能使用基本类型,而且要是唯一的,我们可以使用。
其实我们还可以将synchronized直接加在方法上,这样可以使整个方法都是同步的,
例如我们去改一下下面的代码变成这个样子
public synchronized void printString(String s){
for(int i = 0;i<s.length();i++){
System.out.print(s.charAt(i));
}
System.out.println();
}
}
线程要进图到同步代码块或者同步方法中,必须要先获得同步监视器的锁定,也就是先获得锁,然后在进入方法,那么应该在什么时候释放锁呢?
1,方法执行结束
2.方法中遇到exception
3.程序中垂涎了退出程序的代码
4.使用了wait方法
volatile
Java语言规范第三版对volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能够被准确和一致性的更新,线程应该确保排它锁单独获得这个变量。
volatile的关键字是Java虚拟机中罪轻量级的同步机制,它确保了对一个变量的更新在其他的线程是可见的。当一个变量声明为volatile,编译器运行时会见识这个变量,读一个volatile的变量值总是会返回某个线程写入的最新值。
除此以外,volatile变量可以禁止指令重排序优化。
重排序是指编译器和处理器为了优化程序性能而对指令序列进行的重新排序的一种手段.
指令重排序分为三种,
(1)编译器优化的指令重排序。编译器在不改单线程程序语义的前提下,可以重新安排语句的执行顺序。
(2)指令级并行的重排序。现代处理器采用了的指令级并行技术(ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
(3)内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去是在乱序执行。
这些重排序可能会导致多线程出现内存可见性问题.
注意,对于volatile变量保证线程间的可见性,并不是说所有的情况就不需要加锁了,在:
1.运算接果并不依赖变量的当前值
2.变量不需要与其他变量共同参与不变约束
这两种情况下是不用加锁的,其他还需要通过加锁来保证线程安全
下面我就用一个例子实现volatile。
package com.langsin.threadsafe;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 成功执行加操作
* @author zwd
*/
public class TestVolatileSolution {
int i1=0;
public void go(){
for (int i = 0; i < 30; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for(int j = 0;j<10000;j++){
i1++;
}
}
}).start();
}
while(Thread.activeCount()!=1){
//什么都不做,等待前面所有线程运行完毕。只剩下main线程
}
System.out.println(i1);
}
public static void main(String[] args) {
new TestVolatileSolution().go();
}
}
应该显示为为30*10000
但是显示的数据永远都是达不到,因为有多线程,上面的30个线程会争抢cpu,又是因为i++的操作不是为一个原子操作所以永远也是不能达得到.
下面我们使用一个原子级操作
java.util.concurrent.atomic
AtomicInteger
常用方法
这种方式也称作CAS:compare and swap.
CAS指令需要三个操作数,分别是内存位置V,旧的预期值A,新值N。
使用CAS时,当V符合A时,使用N更新V的值,否则就不执行更新操作。无论是否更新,都返回V的值,这整个是一个原子操作。
int addAndGet(int delta)
以原子方式将给定值与当前值相加。
boolean compareAndSet(int expect, int update)
如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
int decrementAndGet()
以原子方式将当前值减 1。
double doubleValue()
以 double 形式返回指定的数值。
float floatValue()
以 float 形式返回指定的数值。
int get()
获取当前值。
int getAndAdd(int delta)
以原子方式将给定值与当前值相加。
int getAndDecrement()
以原子方式将当前值减 1。
int getAndIncrement()
以原子方式将当前值加 1。
int getAndSet(int newValue)
以原子方式设置为给定值,并返回旧值。
int incrementAndGet()
以原子方式将当前值加 1。
int intValue()
以 int 形式返回指定的数值。
void lazySet(int newValue)
最后设置为给定值。
long longValue()
以 long 形式返回指定的数值。
void set(int newValue)
设置为给定值。
String toString()
返回当前值的字符串表示形式。
boolean weakCompareAndSet(int expect, int update)
如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。
我们将上面的程序进行修改变成
import java.util.concurrent.atomic.AtomicInteger;
/**
* 成功执行加操作
* @author zwd
*/
public class TestVolatileSolution {
private AtomicInteger i = new AtomicInteger(0);
public void increase(){
i.getAndIncrement();
}
public void go(){
for (int i = 0; i < 30; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for(int j = 0;j<10000;j++){
increase();
}
}
}).start();
}
while(Thread.activeCount()!=1){
//什么都不做,等待前面所有线程运行完毕。只剩下main线程
}
System.out.println(i);
}
public static void main(String[] args) {
new TestVolatileSolution().go();
}
}
执行的结果就会达到30000
这个结果符合我们的预期,也就是说我们将i++的操作变成了原子级别的操作实现了多线程加法,然而这个类其实是依赖于CompareAndSet(int expect,intupdate)的这个方法,
但是我们使用CAS也是会有问题的,
具体参照这位大哥写的特别棒
https://blog.csdn.net/weixin_37598682/article/details/81285176
java.util.concurrent.locks
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。java.util.concurrent.locks
接口 Lock
所有已知实现类: ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock
public interface LockLock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。
synchronized 方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。
虽然 synchronized 方法和语句的范围机制使得使用监视器锁编程方便了很多,而且还帮助避免了很多涉及到锁的常见编程错误,但有时也需要以更为灵活的方式使用锁。例如,某些遍历并发访问的数据结果的算法要求使用 “hand-over-hand” 或 “chain locking”:获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推。Lock 接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁,从而支持使用这种技术。
随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:
Lock l = …;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
Lock 实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。
Lock 类还可以提供与隐式监视器锁完全不同的行为和语义,如保证排序、非重入用法或死锁检测。如果某个实现提供了这样特殊的语义,则该实现必须对这些语义加以记录。
注意,Lock 实例只是普通的对象,其本身可以在 synchronized 语句中作为目标使用。获取 Lock 实例的监视器锁与调用该实例的任何 lock() 方法没有特别的关系。为了避免混淆,建议除了在其自身的实现中之外,决不要以这种方式使用 Lock 实例。
除非另有说明,否则为任何参数传递 null 值都将导致抛出 NullPointerException。
内存同步
所有 Lock 实现都必须 实施与内置监视器锁提供的相同内存同步语义,如 The Java Language Specification, Third Edition (17.4 Memory Model) 中所描述的:
成功的 lock 操作与成功的 Lock 操作具有同样的内存同步效应。
成功的 unlock 操作与成功的 Unlock 操作具有同样的内存同步效应。
不成功的锁定与取消锁定操作以及重入锁定/取消锁定操作都不需要任何内存同步效果。
实现注意事项
三种形式的锁获取(可中断、不可中断和定时)在其性能特征、排序保证或其他实现质量上可能会有所不同。而且,对于给定的 Lock 类,可能没有中断正在进行的 锁获取的能力。因此,并不要求实现为所有三种形式的锁获取定义相同的保证或语义,也不要求其支持中断正在进行的锁获取。实现必需清楚地对每个锁定方法所提供的语义和保证进行记录。还必须遵守此接口中定义的中断语义,以便为锁获取中断提供支持:完全支持中断,或仅在进入方法时支持中断。
由于中断通常意味着取消,而通常又很少进行中断检查,因此,相对于普通方法返回而言,实现可能更喜欢响应某个中断。即使出现在另一个操作后的中断可能会释放线程锁时也是如此。实现应记录此行为。
从以下版本开始:
1.5
另请参见:ReentrantLock, Condition, ReadWriteLock
方法摘要
void lock() 获取锁。
void lockInterruptibly() 如果当前线程未被中断,则获取锁。
Condition newCondition() 返回绑定到此 Lock 实例的新 Condition 实例。
boolean tryLock() 仅在调用时锁为空闲状态才获取该锁。
boolean tryLock(long time, TimeUnit unit) 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
void unlock() 释放锁。
Lock接口有一个实现类ReentrantLock。一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义。
上面是JDK1.6的东西
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 测试多线程情况下使用同步代码块
* @author zwd
*/
public class SafeThreadTest1 {
V v = new V();
public static void main(String[] args) {
SafeThreadTest test = new SafeThreadTest();
test.test();
}
/**
* 开两个线程,分别调用V对象的打印字符串的方法
*/
public void test(){
new Thread(new Runnable() {
@Override
public void run() {
while(true){
v.printString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
v.printString("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
}
}
}).start();
}
/**
* 这个类负责打印字符串
* @author Administrator
*/
class V {
//创建一个锁对象
Lock lock = new ReentrantLock();
/**
* 为了能使方法运行速度减慢,我们一个字符一个字符的打印
* @param s
*/
public void printString(String s){
//加锁,只允许一个线程访问
lock.lock();
try {
for(int i = 0;i<s.length();i++){
System.out.print(s.charAt(i));
}
System.out.println();
}finally{
//解锁,值得注意的是,这里锁的释放放到了finally代码块中,保证解锁工作一定会执行
lock.unlock();
}
}
}
}
另外,ReentrantLock其实比synchronized增加了一些功能,主要有:
等待可中断
这是指的当前持有锁的线程如果长期不释放锁,正在等待的线程可以放弃等待,处理其他事情。
公平锁
这个是说多个线程在等待同一个锁的时候,必须按照申请锁的时间顺序依次获得锁。synchronized中的锁是不公平锁,锁被释放的时候任何一个等待锁的线程都有机会获得锁,ReentrantLock默认也是不公平锁,可以使用构造函数使得其为公平锁。如果为true代表公平锁Lock lock = new ReentrantLock(true);
在性能上,在jdk1.6之前的版本,使用ReentrantLock的性能要好于使用synchronized。而在jdk1.6开始,两者性能均差不多。
Condition 提供了条件锁
利用Condition接口实现条件锁,下面是一个小例子,实现的功能是按顺序打印ABC
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLockCondition {
public static void main(String[] args) {
final LoopTest lt = new LoopTest();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (true)
lt.f1();
}
}, "线程A").start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (true)
lt.f2();
}
}, "线程B").start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
while (true)
lt.f3();
}
}, "线程C").start();
}
}
class LoopTest {
//创建锁对象
Lock lock = new ReentrantLock();
//通过锁对象的newCondition方法创建三个Condition对象
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
int token = 1;
public void f1() {
lock.lock();
try {
while (token != 1) {
try {
//使当前线程进入等待状态,注意是await
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName());
token = 2;
//唤醒condition2
condition2.signal();
} finally {
lock.unlock();
}
}
public void f2() {
lock.lock();
try {
while (token != 2) {
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName());
token = 3;
condition3.signal();
} finally {
lock.unlock();
}
}
public void f3() {
lock.lock();
try {
while (token != 3) {
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName());
token = 1;
condition1.signal();
} finally {
lock.unlock();
}
}
}
程序在LoopTest类中创建了一个Lock对象,通过Lock对象的newCondition创建了三个Condition对象。当三个线程分别调用LoopTest类的f1,f2和f3方法的时候,除了使用了一个int类型的变量(int token = 1;)作为判断线程满足条件以外,可以使用Condition通知具体唤醒哪一个线程,从而完成了多个线程之间的调度。
完成的公平锁,类似wait和notify,根据条件决定是否唤醒一个函数执行一个方法
ReadWriteLock 提供了读写锁
读写锁分为读锁和写锁。多个读锁之间不互斥、读锁和写锁之间互斥、写锁和写锁之间互斥。这一个功能很重要,对并发读取提高了性能。
java.util.concurrent.locks 接口 ReadWriteLock
所有已知实现类: ReentrantReadWriteLock
public interface ReadWriteLockReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
所有 ReadWriteLock 实现都必须保证 writeLock 操作的内存同步效果也要保持与相关 readLock 的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。
与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。
与互斥锁相比,使用读-写锁能否提升性能则取决于读写操作期间读取数据相对于修改数据的频率,以及数据的争用——即在同一时间试图对该数据执行读取或写入操作的线程数。例如,某个最初用数据填充并且之后不经常对其进行修改的 collection,因为经常对其进行搜索(比如搜索某种目录),所以这样的 collection 是使用读-写锁的理想候选者。但是,如果数据更新变得频繁,数据在大部分时间都被独占锁,这时,就算存在并发性增强,也是微不足道的。更进一步地说,如果读取操作所用时间太短,则读-写锁实现(它本身就比互斥锁复杂)的开销将成为主要的执行成本,在许多读-写锁实现仍然通过一小段代码将所有线程序列化时更是如此。最终,只有通过分析和测量,才能确定应用程序是否适合使用读-写锁。
尽管读-写锁的基本操作是直截了当的,但实现仍然必须作出许多决策,这些决策可能会影响给定应用程序中读-写锁的效果。这些策略的例子包括:
在 writer 释放写入锁时,reader 和 writer 都处于等待状态,在这时要确定是授予读取锁还是授予写入锁。Writer 优先比较普遍,因为预期写入所需的时间较短并且不那么频繁。Reader 优先不太普遍,因为如果 reader 正如预期的那样频繁和持久,那么它将导致对于写入操作来说较长的时延。公平或者“按次序”实现也是有可能的。
在 reader 处于活动状态而 writer 处于等待状态时,确定是否向请求读取锁的 reader 授予读取锁。Reader 优先会无限期地延迟 writer,而 writer 优先会减少可能的并发。
确定是否重新进入锁:可以使用带有写入锁的线程重新获取它吗?可以在保持写入锁的同时获取读取锁吗?可以重新进入写入锁本身吗?
可以将写入锁在不允许其他 writer 干涉的情况下降级为读取锁吗?可以优先于其他等待的 reader 或 writer 将读取锁升级为写入锁吗?
当评估给定实现是否适合您的应用程序时,应该考虑所有这些情况。
从以下版本开始: 1.5 另请参见:
ReentrantReadWriteLock, Lock, ReentrantLock
方法摘要
Lock readLock() 返回用于读取操作的锁。
Lock writeLock() 返回用于写入操作的锁。
ReadWriteLock这个类,实现了读写锁,读锁与读锁可以并从,并发,但是写锁一次只是能执行一个,是要去争夺的
package com.langsin.concurrent.lock;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestReadWriteLock {
Random r = new Random();
Data data = new Data();
public void go(){
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
data.get(r.nextInt(100));
}
},"读线程"+i).start();
}
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
data.add(r.nextInt(100));
}
},"写线程"+i).start();
}
}
public static void main(String[] args) {
TestReadWriteLock trw = new TestReadWriteLock();
trw.go();
}
}
class Data{
private List elementData ;
ReadWriteLock rw = new ReentrantReadWriteLock();
Random r = new Random();
public Data(){
init();
}
private void init() {
// TODO Auto-generated method stub
this.elementData = new ArrayList();
for (int i = 0; i < 100; i++) {
elementData.add(i);
}
}
public void add(Object data){
rw.writeLock().lock();
try{
System.out.println("当前线程:"+Thread.currentThread().getName()+"正在写入");
Thread.sleep(r.nextInt(1000));
elementData.add(data);
System.out.println("当前线程:"+Thread.currentThread().getName()+"写入完毕");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
rw.writeLock().unlock();
}
}
public Object get(int index){
rw.readLock().lock();
try{
System.out.println("当前线程:"+Thread.currentThread().getName()+"正在读取");
Thread.sleep(r.nextInt(1000));
System.out.println("当前线程:"+Thread.currentThread().getName()+"读取完毕");
return elementData.get(index);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
rw.readLock().unlock();
}
return null;
}
}
这个就是完成了一个证明的操作,证明了在完成一个读操作的时候,可以完成第二个读操作,第三个读操作,但是在完成写操作的时候,先完成第一个写操作,在完成第二个,大家可以读一下JavaAPI上面的伪代码看一下,
另外笔者指出一点:这里涉及到一个概念叫做锁降级。锁降级是说允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,不能从读锁升级到写锁。
我先介绍到这里吧,剩下的下次再介绍吧