多线程
一. 进程和线程
1.1 进程
进程就是电脑中一个运行的软件,一个正在运行的程序
在做的一件事情就是一个进程
1.2 线程
线程理解为一个进程中步骤
我们在做一件事情时,分很多步骤,每一个步骤就是一个线程,同一时刻只能做一件事情
做菜是一个进程,买菜,洗菜,切菜,炒菜就是一个一个的线程。
注意:
进程间不能共享数据段地址,但同进程的线程之间可以。
- QQ软件不能使用谷歌内存中数据,360不能使用QQ内存中的数据。
- 同一个进程中的线程,可以贡献数据,会产生数据不安全的情况。
1.3 进程是如何执行的
二. 创建线程
2.1 继承Thread类
class 类 extends Thread {
public void run() {
//这个线程被cpu选中执行时,执行的业务代码
}
}
run方法是使用当前类创建线程,被cpu选中时,实行的业务代码
package com.qfedu;
public class Demo01 {
//main方法 就是一个线程(主线程)
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
mt.start(); // 开启了一个线程,和主线程共同竞争cpu执行时间
for (int i = 0; i < 100; i++) {
System.out.println("main--"+i);
}
}
}
/*
* 创建线程
* 1. 创建类继承Thread
* 2. 重写run方法 该线程执行的业务代码
*/
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("自定义线程--"+i);
}
}
}
2.2 实现Runnable接口
实现Runnable接口,重写run方法
class 类 implements Runnable {
public void run() {
//这个线程被cpu选中执行时,执行的业务代码
}
}
Thread t = new Thread(类的对象);
package com.qfedu;
public class Demo02 {
public static void main(String[] args) {
Runnable runnable = new YourThread();
Thread t = new Thread(runnable, "线程1");
Thread t2 = new Thread(runnable, "线程2");
t.start(); // 开启了一个线程,和主线程共同竞争cpu执行时间
t2.start(); // 开启了一个线程,和线程1共同竞争cpu执行时间
}
}
/*
* 实现Runnable接口
* 重写run方法 该线程执行的业务代码
*/
class YourThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
}
}
}
三. 线程的状态
基本状态:
新建状态:创建线程对象
就绪状态:线程对象执行start()方法
运行状态:被cpu选中,执行run方法,如果在分配的时间片内运行结束,进入死亡状态,如果没有运行 结束,回到就绪状态。
死亡状态:run方法执行结束,进入死亡状态
package com.qfedu;
public class Demo03 {
public static void main(String[] args) {
//1. 新建状态
System.out.println("新建状态");
HerThread mt = new HerThread();
/*
* 2. 就绪状态
*
* 这个线程可以和这个进程中的其他线程,共同竞争cpu的运行时间
*
* 如果竞争到了,就执行它的run方法中代码 会进入运行状态
* - 如果在运行的时间片段内,run方法没有执行结束,那么就回到就绪状态
* - 如果run方法执行结束,该线程就over了
*/
System.out.println("就绪状态");
mt.start();
}
}
class HerThread extends Thread {
@Override
public void run() {
System.out.println("运行状态");
System.out.println("死亡状态");
}
}
四. 卖票实例
- 四个窗口各卖100张票
- 四个窗口共卖100张票
package com.qfedu;
public class Demo04 {
/*
* 四个窗口,每个窗口各卖100张票
*/
public static void main(String[] args) {
MyTicket t1 = new MyTicket("窗口1");
MyTicket t2 = new MyTicket("窗口2");
MyTicket t3 = new MyTicket("窗口3");
MyTicket t4 = new MyTicket("窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class MyTicket extends Thread {
private int count = 100;
public MyTicket(String name) {
super(name);
}
public void run() {
while(true) {
if(count <= 0) {
break;
}
System.out.println(Thread.currentThread().getName()+"卖了,第"+(100 - --count)+"张票");
}
}
}
package com.qfedu;
public class Demo04_2 {
/*
* 四个窗口,共卖100张票
*/
public static void main(String[] args) {
Runnable runnable = new YourTicket();
Thread t1 = new Thread(runnable, "窗口1");
Thread t2 = new Thread(runnable, "窗口2");
Thread t3 = new Thread(runnable, "窗口3");
Thread t4 = new Thread(runnable, "窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class YourTicket implements Runnable {
private int count = 100;
@Override
public void run() {
while(true) {
if(count <= 0) {
break;
}
System.out.println(Thread.currentThread().getName()+"卖了,第"+(100 - --count)+"张票");
}
}
}
五. 线程常用方法
//休眠,啥都不干1000毫秒,进入等待状态,1000毫秒后回到就绪状态
public static native void sleep(long millis) throws InterruptedException;
//加入,随机就变成了顺序,什么t线程执行结束,主线程才会回到就绪状态
public final void join() throws InterruptedException {}
//主动放弃cpu资源,回到就绪状态
public static native void yield();
//设置线程调用优先级 1-10 默认是5
public final void setPriority(int newPriority) {}
//是否是守护线程,非守护线程执行结束 守护线程自动结束
public final void setDaemon(boolean on) {}
线程在执行sleep(long millis) , join() 后进入等待状态
线程在执行yield()后,回到就绪状态
package com.qfedu;
public class Demo05 {
public static void main(String[] args) throws InterruptedException {
for(int i=1; i<100; i++) {
/*
* 休眠,啥都不干1000毫秒,进入等待状态,1000毫秒后回到就绪状态
*
* 在等待的过程中,是不释放锁的
*/
Thread.sleep(1000);
System.out.println("吃西瓜,现在是第" + i +"个");
}
}
}
package com.qfedu;
public class Demo06 {
/*
* 30个桃子
*
* 20个西瓜
*
* 开始随机的吃,如果桃子已经吃了10个,还有西瓜,那就先把西瓜吃完,才能吃桃子
*
*
* 随机 改成 顺序
*/
public static void main(String[] args) throws InterruptedException {
EatWatermelon t = new EatWatermelon();
t.start();
for(int i=1; i<=30; i++) {
System.out.println("吃了第"+i+"个桃子");
if(i == 10) {
//加入,随机就变成了顺序,什么t线程执行结束,主线程才会回到就绪状态
t.join();
}
}
}
}
//吃西瓜线程
class EatWatermelon extends Thread {
@Override
public void run() {
for(int i=1; i<=20; i++) {
System.out.println("吃了第"+i+"个西瓜");
}
}
}
package com.qfedu;
public class Demo07 {
public static void main(String[] args) {
YieldThread yt = new YieldThread();
yt.start();
for(int i=1; i<=100; i++) {
System.out.println("main--"+i);
if(i%3 == 0) {
Thread.yield(); // 如果是3的倍数,就主动放弃cpu资源,回到就绪状态
}
}
}
}
class YieldThread extends Thread {
@Override
public void run() {
for(int i=1; i<=100; i++) {
System.out.println("YieldThread--"+i);
if(i%8 == 0) {
yield(); // 如果是8的倍数,就主动放弃cpu资源,回到就绪状态
}
}
}
}
package com.qfedu;
public class Demo08 {
public static void main(String[] args) {
DaemonThread t1 = new DaemonThread();
t1.setName("非守护线程");
t1.setPriority(10);
DaemonThread t2 = new DaemonThread();
t2.setName("守护线程");
t2.setDaemon(true);
t2.setPriority(1);
t1.start();
t2.start();
}
}
class DaemonThread extends Thread {
@Override
public void run() {
for(int i=1; i<=100; i++) {
System.out.println(getName() + "--"+i);
}
}
}
六. 线程安全
一个进程中的多个线程在执行的过程中可能会对一个变量进行操作,这样就有可能出现数据混乱的问题,可以通过对程序加锁来解决这种情况。
synchronized (对象) {
//代码块
}
被加锁的代码,在同一时刻只能由一个线程执行
想要执行加锁的代码,必须 获取 对象的锁
package com.qfedu;
public class Demo09 {
public static void main(String[] args) {
Runnable runnable = new HerTicket();
Thread t1 = new Thread(runnable, "窗口1");
Thread t2 = new Thread(runnable, "窗口2");
Thread t3 = new Thread(runnable, "窗口3");
Thread t4 = new Thread(runnable, "窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class HerTicket implements Runnable {
private int count = 100;
Object o = new Object();
@Override
public void run() {
while(true) {
//对代码块进行加锁,这个代码块在一个时刻,只能由一个线程执行
synchronized (o) {
if(count <= 0) {
break;
}
System.out.println(Thread.currentThread().getName()+"卖了,第"+(100 - --count)+"张票");
}
}
}
}
线程安全,代码同步
synchronized(对象) {
//代码
}
public synchronized 返回类型 方法名(参数列表) {
//代码块
}
- 加锁的对象,可以是任意对象
- 线程必须获取该对象的锁,才能执行加锁后的代码
- 在执行完加锁后的代码后,会立即释放锁
- 可以对代码块进行代码块,也可以对方法进行加锁
代码块加锁
package com.qfedu;
public class Demo01 {
public static void main(String[] args) {
Runnable runnable = new Ticket();
for(int i=1; i<=4; i++) {
Thread t = new Thread(runnable,"第"+i+"窗口");
t.start();
}
}
}
class Ticket implements Runnable {
int count = 100;
@Override
public void run() {
while(true) {
synchronized (this) {
//如果没票,就不卖了
if(count <= 0) {
break;
}
count--;
System.out.println(Thread.currentThread().getName()+(100-count)+"张票");
}
}
}
}
方法加锁
- 关键字定义在方法上
- 默认加锁的是该类对象
package com.qfedu;
public class Demo02 {
public static void main(String[] args) {
Runnable runnable = new Ticket2();
for(int i=1; i<=4; i++) {
Thread t = new Thread(runnable,"第"+i+"窗口");
t.start();
}
}
}
class Ticket2 implements Runnable {
int count = 100;
@Override
public void run() {
while(true) {
boolean r = sell();
if(r) {
break;
}
}
}
/*
* 如果加锁的代码是整个方法体,且加锁的对象为当前对象
* 可以关键字synchronized定义在方法上
*/
public synchronized boolean sell() {
//如果没票,就不卖了
if(count <= 0) {
return true; //没票了
}
count--;
System.out.println(Thread.currentThread().getName()+(100-count)+"张票");
return false; //不是没票了
}
}
常见加锁类
- StringBuffer
- Vecotr
- Hashtable
package com.qfedu;
public class Demo03 {
/*
* 开启10个线程,每个线程向同一个StringBuilder对象中添加100个字符
*
*
* 开启10个线程,每个线程向同一个ArrayList/Vector对象中添加100个整数,每次打印元素个数
*/
public static void main(String[] args) {
Runnable runnable = new MyThread();
for(int i=1; i<=10; i++) {
Thread t = new Thread(runnable,"第"+i+"窗口");
t.start();
}
}
}
class MyThread implements Runnable {
StringBuffer sf = new StringBuffer();
@Override
public void run() {
for(int i=1; i<=1000; i++) {
sf.append("1");
}
System.out.println(sf.length());
}
}
死锁
- 两个线程都拥有对方执行所需要的锁,但是又无法释放当前的锁,导致两个线程都处于阻塞状态,无法继续执行,称之为死锁。
package com.qfedu;
public class Demo04 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
t1.start();
t2.start();
}
}
class MyLock {
//创建两个对象作为锁
static Object a = new Object();
static Object b = new Object();
}
class T1 extends Thread {
@Override
public void run() {
synchronized (MyLock.a) {
System.out.println("我获取到a了");
synchronized (MyLock.b) {
System.out.println("我获取到b了"); //必须获取b锁,才能执行,释放a锁
}
}
}
}
class T2 extends Thread {
@Override
public void run() {
synchronized (MyLock.b) {
System.out.println("我获取到b了");
synchronized (MyLock.a) {
System.out.println("我获取到a了"); //必须获取a锁,才能执行,释放b锁
}
}
}
}
七. 线程通信
1. 主要方法
//等待,一直等待被唤醒
public final void wait() throws InterruptedException {
wait(0);
}
//等待固定时间数
public final native void wait(long timeout) throws InterruptedException;
//唤醒所有线程
public final native void notifyAll();
//唤醒一个线程
public final native void notify();
- 如果synchronized定义在方法上,那么获取的对象锁,就是该对象。
- wait()是Object类的方法,表示使当前线程进入等待状态,意思就是程序不再往下执行了,等待被唤醒。
- notifyAll(),唤醒所有执行wait()方法处于等待状态的线程,继续往下执行
- notify(),唤醒一个执行wait()方法处于等待状态的线程,继续往下执行
2. 生产者消费者实例
包子铺
- 包子
- 蒸笼
生产者和消费者模式
- 包子
- 包子笼
- 最多放6个包子
- 张师傅,劳师傅,王师傅 往包子笼放包子
- 首先判断,包子笼有没有放满,如果放满了,等着
- 没放满,继续放
- 让他们三个拿包子
- 小明,小红,小芳从包子笼拿包子
- 首先判断,包子笼有没有包子,如果没有了,等着
- 如果有,就拿包子
- 让师傅们继续放
使用程序实现上述的实际场景:
- 创建包子类
- id,productName
- 创建包子笼类
- 放包子
- 取包子
- 创建生产者线程
- 创建消费者线程
包子类:
package com.qfedu;
//包子类
public class Bun {
private Integer id; // 编号
private String productName; //谁做的
public Bun() {
// TODO Auto-generated constructor stub
}
public Bun(Integer id, String productName) {
super();
this.id = id;
this.productName = productName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
}
包子笼类:
package com.qfedu;
//包子笼
public class BunCage {
//存放包子的数组
Bun[] cage = new Bun[6];
//包子笼中包子的个数
int size = 0;
//放包子
public synchronized void putBun(Bun bun) {
//判断是否放满了
while(size >=6) { //每次被唤醒后,还需要继续判断是否已放满
try {
this.wait(); //进入等待状态,必须被唤醒,才能继续执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//放包子操作
cage[size] = bun;
size++;
System.out.println(Thread.currentThread().getName()+"放了第"+bun.getId()+"包子");
//唤醒拿包子
this.notifyAll(); //唤醒所有的线程
}
//拿包子
public synchronized void getBun() {
//判断是否还有包子
while(size <=0) { //每次被唤醒后,还需要继续判断是否有包子
try {
this.wait(); //进入等待状态,必须被唤醒,才能继续执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// size = 5; 4
size--;
Bun bun = cage[size];
cage[size] = null;
System.out.println(Thread.currentThread().getName()+"拿了第"+bun.getId()+"个包子");
//唤醒放包子
this.notifyAll(); //唤醒所有的线程
}
}
生产者线程:
package com.qfedu;
/*
* 包子生产者,放包子
*
*/
public class Product implements Runnable{
//包子笼
BunCage bunCage;
@Override
public void run() {
//每个人,放30个包
for(int i=1; i<=30; i++) {
//放包子
bunCage.putBun(new Bun(i,Thread.currentThread().getName()));
}
}
}
消费者线程:
package com.qfedu;
//消费者消费包子
public class Consumer implements Runnable{
//包子笼
BunCage bunCage;
@Override
public void run() {
//每个人吃三十个
for(int i=1; i<=30; i++) {
//吃包子
bunCage.getBun();
}
}
}
测试:
package com.qfedu;
public class BunTest {
public static void main(String[] args) {
//一个包子笼
BunCage cage = new BunCage();
//生产者
Product product = new Product();
product.bunCage = cage; //往这个笼子里放
//消费者
Consumer consumer = new Consumer();
consumer.bunCage = cage; //往同一个笼子里取
//创建生产者
Thread p1 = new Thread(product, "张师傅");
Thread p2 = new Thread(product, "劳师傅");
Thread p3 = new Thread(product, "王师傅");
//创建消费者
Thread c1 = new Thread(consumer, "小明");
Thread c2 = new Thread(consumer, "小红");
Thread c3 = new Thread(consumer, "小芳");
p1.start();
p2.start();
p3.start();
c1.start();
c2.start();
c3.start();
}
}
3. wait()和sleep()的区别
- wait是等待,sleep是休眠。
- wait()是Object类的方法,所有对象都可以使用,sleep是线程对象的方法。
- 线程执行wait()进入等待状态,会释放锁,而执行sleep()进入等待状态,不会释放锁。
二. 线程池
1. 固定长度
语法:返回的是线程执行服务对象,需要调用submit()定义执行的业务逻辑
每执行一次submit()就需使用一个线程对象执行相应的业务逻辑
ExecutorService es = Executors.newFixedThreadPool(线程数);
package com.qfedu;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo01 {
public static void main(String[] args) {
/*
* 1. 指定长度的线程池 一定指定了具体长度的线程,就不可改变了
*
* 这个池里有固定多的线程对象,可以重复使用
*
* 1.1 submit() 让线程池中的线程执行指定的业务逻辑
*/
ExecutorService es = Executors.newFixedThreadPool(3); //3个线程对象
for(int i=1; i<=4; i++) {
//每执行一次submit() 就开启一个任务
es.submit(new MyThread());
}
//主动的关闭线程池
es.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
for(int i=1; i<=20;i++) {
System.out.println(Thread.currentThread().getName()+"******"+i);
}
}
}
2. 可变长度
语法:
ExecutorService es = Executors.newCachedThreadPool();
执行多少次submit()就开启多少个线程执行
package com.qfedu;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo02 {
public static void main(String[] args) {
/*
* 2. 创建可变长度的线程池
*
* 需要多少有多少
*
* 每submit()一次就创建一个对象执行一次任务
*/
ExecutorService es = Executors.newCachedThreadPool();
Runnable runnable = new Runnable() {
@Override
public void run() {
for(int i=1; i<=20;i++) {
System.out.println(Thread.currentThread().getName()+"******"+i);
}
}
};
for(int i=1; i<=6; i++) {
//
es.submit(runnable);
}
es.shutdown();
}
}
3. 单个长度
语法:
ExecutorService es = Executors.newSingleThreadExecutor();
线程池中只要一个线程
package com.qfedu;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo03 {
public static void main(String[] args) {
/*
* 3. 创建单线程线程池
* 线程池中只有一个线程
*/
ExecutorService es = Executors.newSingleThreadExecutor();
es.submit(new Runnable() {
@Override
public void run() {
for(int i=1; i<=20;i++) {
System.out.println(Thread.currentThread().getName()+"******"+i);
}
}
});
es.shutdown();
}
}
4. 可定时延迟线程池
语法:
//指定长度线程池
ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
//延迟执行一次
ses.schedule(runnable, 3, TimeUnit.SECONDS);
//规定第一次延迟时间及每隔一段时间执行一次 0:第一次延迟时间 3:周期
ses.scheduleAtFixedRate(runnable, 0, 3, TimeUnit.SECONDS);
package com.qfedu;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Demo04 {
public static void main(String[] args) {
/*
* 创建每个一段时间执行的线程池
*/
ScheduledExecutorService ses = Executors.newScheduledThreadPool(3);
Runnable runnable = new Runnable() {
@Override
public void run() { //业务需求
System.out.println("呵呵哒");
}
};
//使用线程池中一个线程规定时间后执行一次该业务需求
// ses.schedule(runnable, 3, TimeUnit.SECONDS);
//0:首次执行的延迟时间 3:周期(每隔一段时间)
ses.scheduleAtFixedRate(runnable, 0, 3, TimeUnit.SECONDS);
}
}
三. Callable
在执行一个线程后,想到得到一个返回值,则需要使用Callable接口
package com.qfedu;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Demo05 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//创建线程池
ExecutorService es = Executors.newCachedThreadPool();
Future<Integer> future = es.submit(new MyCall());
//获取线程的返回值
Integer result = future.get();
System.out.println(result);
}
}
class MyCall implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=1; i<=100; i++) {
sum+=i;
}
return sum;
}
}
package com.qfedu;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Demo05_2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
/*
* 开启两个线程计算1+1000;
*/
//创建线程池
ExecutorService es = Executors.newCachedThreadPool();
//开启一个线程计算1+500的和
Future<Integer> future = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=1; i<=500; i++) {
sum+=i;
}
return sum;
}
});
//开启一个线程计算501+1000的和
Future<Integer> future2 = es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=501; i<=1000; i++) {
sum+=i;
}
return sum;
}
});
int result = future.get()+future2.get();
System.out.println(result);
}
}
四. Lock
Lock lock = new ReentrantLock();
lock.lock(); //上锁
lock.unlock(); //开锁
package com.qfedu;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo01 {
/*
* Lock锁
*/
public static void main(String[] args) {
Runnable runnable = new Ticket();
for(int i=1; i<=4; i++) {
Thread t = new Thread(runnable,"第"+i+"窗口");
t.start();
}
}
}
class Ticket implements Runnable {
//创建一个锁
Lock lock = new ReentrantLock();
int count = 100;
@Override
public void run() {
while(true) {
lock.lock(); //加锁
//如果没票,就不卖了
if(count <= 0) {
break;
}
count--;
System.out.println(Thread.currentThread().getName()+(100-count)+"张票");
lock.unlock(); //解锁
}
}
}
synchronized 和Lock的区别
synchronized 是关键字,Lock是对象
当出现异常时,synchronized 会自动释放锁,Lock就不会释放锁,所以无论如何都要执行unlock方法
package com.qfedu;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo01 {
/*
* Lock锁
*/
public static void main(String[] args) {
Runnable runnable = new Ticket();
for(int i=1; i<=4; i++) {
Thread t = new Thread(runnable,"第"+i+"窗口");
t.start();
}
}
}
class Ticket implements Runnable {
//创建一个锁
Lock lock = new ReentrantLock();
int count = 100;
@Override
public void run() {
while(true) {
try {
lock.lock();
//如果没票,就不卖了
if(count <= 0) {
break;
}
count--;
System.out.println(Thread.currentThread().getName()+(100-count)+"张票");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //保证锁一定会打开
}
}
}
}
1. 读写锁
读 读 不互斥
读 写 互斥
写 写 互斥
package com.qfedu;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
public class ReadWriteDemo {
/*
* 当多个线程操作同一个ReadWriteDemo对象时
*
* 如果一个线程正在执行getValue()方法,其他线程都不能执行读和写的方法
*
* 读 读 互斥 不应该互斥
* 读 写 互斥
* 写 写 互斥
*/
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
//读锁
ReadLock readLock = rwl.readLock();
//写锁
WriteLock writeLocl = rwl.writeLock();
//属性
private String value;
//读
public String getValue() {
try {
readLock.lock(); //读锁
return value;
}finally {
readLock.unlock();
}
}
//写
public void setValue(String value) {
writeLocl.lock();
this.value = value;
writeLocl.unlock();
}
}