线程概述
什么是进程
进程是系统进行资源分配基本单位,也是独立运行的单位。
什么是线程
线程又称为轻量级进程,它是进程内一个相对独立的、可调度的执行单元,也是CPU的基本调度单位
俩者区别
- 进程是系统进行资源分配的基本单位,线程是CPU的基本调度单位。
- 一个进程至少拥有一个线程。
- 进程之间不能共享数据,而进程中的线程可以。
线程组成
- CPU时间片:操作系统会为每个线程单位分配执行时间
- 运行数据:
- 堆空间:存储线程需要使用的对象,多个线程可以共享堆中的对象。
- 栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈。
- 代码逻辑
线程特点
- 线程抢占式执行。
- 效率高。
- 可防止单一线程长时间独占CPU。
- 在单核CPU中,宏观上同时执行,微观上顺序执行。
JAVA如何创建线程
继承Thread类
package com.zsl;
public class ThreadOne extends Thread{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("子线程编号:"+i);
}
}
}
package com.zsl;
import org.junit.Test;
public class TestUtils {
@Test
public void ThreadOneTest(){
System.out.println("开始主线程");
ThreadOne threadOne = new ThreadOne();
threadOne.start();
System.out.println("结束主线程");
}
}
/*
开始主线程
结束主线程
子线程编号:0
子线程编号:1
子线程编号:2
子线程编号:3
子线程编号:4
子线程编号:5
......
*/
实现Runnable接口
package com.zsl.threadLearning;
/**
* @author m1767
*/
public class ThreadCallable implements Runnable{
private final Integer SIZE = 100000;
@Override
public void run() {
for (int i = 0; i < SIZE; i++) {
System.out.println("子线程-"+i);
}
}
public static void main(String[] args) {
ThreadCallable threadCallable = new ThreadCallable();
Thread thread = new Thread(threadCallable);
thread.start();
System.out.println("主线程结束");
}
}
实现Callable接口(线程池进行讲解)
线程的状态
初始状态
- 线程被创建的时候处于初始状态
就绪状态
- 线程对象调用start方法后进入就绪状态。
运行状态
- 获取处理器后,进入运行状态,等到时间片结束后,就进入就绪状态了
等待状态
- 线程无法继续执行的状态。比如调用sleep方法。
终止状态
- 主线程结束或者该线程的run方法结束后进入终止状态,并释放CPU
线程常用方法
sleep()方法
public static void sleep(long millis)当前现场休眠
public class ThreadCallable implements Runnable{
private final Integer SIZE = 10;
@Override
public void run() {
for (int i = 0; i < SIZE; i++) {
System.out.println("子线程-"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadCallable threadCallable = new ThreadCallable();
Thread thread = new Thread(threadCallable);
thread.start();
System.out.println("主线程结束");
}
yield()方法
public static void yield()当前线程主动休眠millis毫秒。
public class ThreadCallable implements Runnable{
private final Integer SIZE = 10;
@Override
public void run() {
for (int i = 0; i < SIZE; i++) {
System.out.println("子线程-"+Thread.currentThread().getName()+";i="+i);
Thread.yield();
}
}
public static void main(String[] args) {
ThreadCallable threadCallable = new ThreadCallable();
Thread thread = new Thread(threadCallable);
Thread thread1 = new Thread(threadCallable);
thread.start();
thread1.start();
System.out.println("主线程结束");
}
}
join()方法
public final void join()允许其他线程加入到当前线程中。当某线程调用该方法时,加入并阻塞当前线程,直到加入的线程执行完毕,当前线程才继续执行。
public class ThreadCallable implements Runnable{
private final Integer SIZE = 10000;
@Override
public void run() {
for (int i = 0; i < SIZE; i++) {
System.out.println("子线程-"+Thread.currentThread().getName()+";i="+i);
Thread.yield();
}
}
public static void main(String[] args) throws InterruptedException {
ThreadCallable threadCallable = new ThreadCallable();
Thread thread = new Thread(threadCallable);
thread.start();
thread.join();
for (int i = 0; i <10 ; i++) {
System.out.println("主线程======"+i);
}
System.out.println("主线程结束");
}
}
子线程加入主线程后阻塞了主线程、直到子线程执行完,才执行主线程。
setPriority(int newPriority)方法
setPriority(int newPriority)改变该线程的优先级,线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。
public class ThreadCallable implements Runnable{
private final Integer SIZE = 100;
@Override
public void run() {
for (int i = 0; i < SIZE; i++) {
System.out.println("子线程-"+Thread.currentThread().getName()+";i="+i);
Thread.yield();
}
}
public static void main(String[] args) throws InterruptedException {
ThreadCallable threadCallable = new ThreadCallable();
Thread thread = new Thread(threadCallable);
Thread thread1 = new Thread(threadCallable);
Thread thread2 = new Thread(threadCallable);
thread.setPriority(1);
thread1.setPriority(10);
thread.start();
thread1.start();
thread2.start();
for (int i = 0; i <10 ; i++) {
System.out.println("主线程======"+i);
}
System.out.println("主线程结束");
}
}
setDaemon(boolean on)方法
public final void setDaemon(boolean on) 如果参数为true,则标记该线程为守护线程。
在JAVA中线程有两类:用户线程(前台线程)、守护线程(后台线程)。守护可以理解为守护用户线程。如果程序中所有用户线程都执行完毕了,守护线程会自动结束。垃圾回收线程属于守护线程。
@SuppressWarnings("all")
public class ThreadCallable implements Runnable{
private final Integer SIZE = 100;
@Override
public void run() {
for (int i = 0; i < SIZE; i++) {
System.out.println("子线程-"+Thread.currentThread().getName()+";i="+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadCallable threadCallable = new ThreadCallable();
Thread thread = new Thread(threadCallable);
thread.setDaemon(true);
thread.start();
for (int i = 0; i <10 ; i++) {
System.out.println("主线程======"+i);
Thread.sleep(1000);
}
System.out.println("主线程结束");
}
}
线程安全
对于线程安全问题,我们用一个简单的例子进行描述。
public class ThreadSafe {
public static int index =0;
public static void main(String[] args) throws InterruptedException {
String[] names = new String[5];
Runnable runnableA = new Runnable() {
@Override
public void run() {
names[index]="hello";
index++;
}
};
Runnable runnableB=new Runnable() {
@Override
public void run() {
names[index]="world";
index++;
}
};
Thread A=new Thread(runnableA);
Thread B=new Thread(runnableB);
A.start();
B.start();
//加入主线程,用来阻塞主线程使最后的输出语句最后执行
A.join();
B.join();
System.out.println(Arrays.toString(names));
}
}
输出的其中一种情况为[hello, world, null, null, null]/[world, null, null, null, null],这里对第二种情况进行说明。线程A存hello的时候,CPU被线程B抢占了,由于index没有发生变化还是为0,此时存word完全覆盖掉了hello,然后执行index,最后导致这个结果
多线程安全问题
当多线程并发访问临界资源时,如果破坏了原子操作,可能会造成数据不一致。
- 临界资源:共享资源(对于同一个对象),一次仅允许一个线程使用,才可以保证其正确性。
- 原子操作:不可分割的多步操作,被视为一个整体,其顺序和步骤不可打乱或缺省,比如上一段代码的存hello和存world应当被看成两个原子操作。
同步方式(1)
//对临界资源对象加锁
synchronized(临界资源对象){
//代码(原子操作)
}
public class ThreadSafe {
public static int index =0;
public static void main(String[] args) throws InterruptedException {
String[] names = new String[5];
Runnable runnableA = new Runnable() {
@Override
public void run() {
synchronized (names){
names[index]="hello";
index++;
}
}
};
Runnable runnableB=new Runnable() {
@Override
public void run() {
synchronized (names){
names[index]="world";
index++;
}
}
};
Thread A=new Thread(runnableA);
Thread B=new Thread(runnableB);
A.start();
B.start();
//加入主线程,用来阻塞主线程使最后的输出语句最后执行
B.join();
A.join();
System.out.println(Arrays.toString(names));
}
}
每个对象都有一个互斥锁标记,用来分配给线程的。
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。
线程退出同步代码块时,会释放相应的互斥锁标记。
线程基本状态
public enum State {
NEW,//初始状态
RUNNABLE,//就绪状态和执行状态
BLOCKED,//BLOCKED 阻塞状态
WAITING,//WAITING (无期限)等待状态
TIMED_WAITING,//TIMED_WAITING 有限等待状态
TERMINATED;//TERMINATED 终止状态
}
线程正在等待时的状态。线程被以下方法所调用就会进入等待状态:
Object.wait无参方法
Thread.join无参方法
LockSupport.park
wait方法可以让当前线程进入等待状态,需要其他线程调用此线程对象的notify方法或者notifyAll方法来唤醒此线程;调用join方法的线程需要等到被调用线程终止才能结束等待状态。
TIMED_WAITING 有限等待状态
线程在指定时间后才能结束等待的一种等待状态。是由于调用了以下方法所引起的一种状态:
Thread.sleep
Object.wait带参方法
Thread.join带参方法
LockSupport.parkNanos
LockSupport.parkUntil
同步方式(2)
//对当前对象(this)加锁
synchronized 返回值类型 方法名称(形参列表){
//代码(原子操作)
}
死锁
死锁:
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
/**
* 锁对象(筷子)
*/
class Chopsticks {
}
public class ThreadSafe {
public static int index = 0;
public static void main(String[] args) throws InterruptedException {
//创建两个锁对象(两根筷子)
Chopsticks chopsticks1=new Chopsticks();
Chopsticks chopsticks2=new Chopsticks();
Runnable A=new Runnable() {
@Override
public void run() {
//持有第一根筷子
synchronized (chopsticks1) {
System.out.println("A拿到了一根筷子。");
//持有第二根筷子
synchronized (chopsticks2) {
System.out.println("A拿到了两根筷子,开始恰饭。");
}
}
}
};
Runnable B=new Runnable() {
@Override
public void run() {
//持有第一根筷子
synchronized (chopsticks2) {
System.out.println("B拿到了一根筷子。");
//持有第二根筷子
synchronized (chopsticks1) {
System.out.println("B拿到了两根筷子,开始恰饭。");
}
}
}
};
new Thread(A).start();
new Thread(B).start();
}
}
运行结果(程序没有终止):A在等待B,B又在等待A。造成死锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gMvQhPEC-1674030532273)(/upload/2023/01/1673946339412-69a39251-ec41-4ffe-bf01-9b94be537562.png)]
线程通信
对数据的共享。
public class AddMoney implements Runnable{
private Blank blank;
public AddMoney(Blank blank){
this.blank = blank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
this.blank.addMoney(101.0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class SubMoney implements Runnable{
private Blank blank;
public SubMoney(Blank blank){
this.blank = blank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
this.blank.take(101.0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Blank {
private Double money;
private Double nowDateMoney;
boolean flag = false;
public Blank(Double money) {
this.money = money;
}
public synchronized void addMoney(Double money) throws InterruptedException {
if (flag) {
this.wait();
}
this.money += money;
System.out.println("您本次存钱=》" + this.money);
flag = true;
this.notify();
}
public synchronized void take(Double money) throws InterruptedException {
if (!flag) {
this.wait();
}
this.money += money;
System.out.println("您本次取钱=》" + this.nowDateMoney);
flag = false;
this.notify();
}
}
public class ThreadMain {
public static void main(String[] args) {
//创建银行卡对象
Blank blank=new Blank(1000.0);
//创建操作
AddMoney addMoney=new AddMoney(blank);
SubMoney subMoney=new SubMoney(blank);
//创建线程对象并启动
new Thread(addMoney).start();
new Thread(subMoney).start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Dt3Wf7E-1674030532274)(/upload/2023/01/1673950694922-a005aede-fb19-4d99-98d8-13f1f23fb03c.png)]
生产者消费者
在个车间仓库,生产者不断的生产商品,但是仓库慢了就要停止生产;消费者消费商品,仓库里空了就无法进行消费。所以俩者要保持同步。
@SuppressWarnings("all")
public class ProductFactory {
private String[] products;
private int index;
public ProductFactory(){
this.index = -1;
this.products = new String[5];
}
public synchronized void productor() throws InterruptedException {
while (this.index >= 4){
this.wait();
}
this.products[++this.index] = "product";
System.out.println(Thread.currentThread().getName()+"生产了一个产品,产品数量:"+(index+1));
System.out.println(Arrays.toString(products));
this.notifyAll();
}
public synchronized void consumer() throws InterruptedException {
while (this.index <0){
this.wait();
}
this.products[this.index--] = "null";
System.out.println(Thread.currentThread().getName()+"消费了一个产品,产品数量:"+(index+1));
System.out.println(Arrays.toString(products));
this.notifyAll();
}
}
public class Productor implements Runnable{
ProductFactory productBuf=new ProductFactory();
public Productor() {
}
public Productor(ProductFactory productBuf) {
this.productBuf=productBuf;
}
@Override
public void run() {
//生产30个产品
for(int i=0;i<30;i++) {
try {
productBuf.productor();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Consumer implements Runnable{
ProductFactory productBuf=new ProductFactory();
public Consumer() {
}
public Consumer(ProductFactory productBuf) {
this.productBuf=productBuf;
}
@Override
public void run() {
//消费30个产品
for(int i=0;i<30;i++) {
try {
productBuf.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@SuppressWarnings("all")
public class testProduct {
public static void main(String[] args) {
ProductFactory productBuf=new ProductFactory();
Productor productor=new Productor(productBuf);
Consumer consumer=new Consumer(productBuf);
new Thread(productor,"生产者1号").start();
new Thread(productor,"生产者2号").start();
new Thread(consumer,"消费者1号").start();
new Thread(consumer,"消费者2号").start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UQHAuHU7-1674030532274)(/upload/2023/01/1674006538571-2cbfacd2-b9e7-4fd9-bb65-d21dc52a4225.png)]
线程池
线程池概念
线程存在的问题:
- 线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。
- 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成性能下降。
所以线程池的出现是为了解决以上问题:
- 线程容器,可设定线程分配的数量。
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
- 避免频繁的创建和销毁。
线程池原理
线程池有俩个线程,此时有三个任务。线程池中的俩个线程先分别完成任务1/2,任务3进入等待状态。执行完前面俩个任务后,再执行任务3.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-69gR2MzN-1674030532275)(/upload/2023/01/1674007277766-03c5e34e-2187-4b3f-8b7c-00465e7a1cce.png)]
创建线程池
常用的线程池接口的类(所在包java.util.concurrent)
Executor:线程池的顶级接口。
ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码。
Executors工厂类:创建线程池的工具类。
创建固定线程个数的线程池。
创建缓存线程池,由任务的多少决定。
创建单线程池。
创建调度线程池。调度:周期、定时执行。
通过newFixedThreadPool(int nThreads)获得固定数量的线程池。参数:指定线程池中线程的数量。
通过newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,没有上限。
@SuppressWarnings("all")
public class ExecutorsTest {
public static void main(String[] args) {
ProductFactory productBuf=new ProductFactory();
Productor productor=new Productor(productBuf);
Consumer consumer=new Consumer(productBuf);
//创建固定线程个数的线程池
//ExecutorService executorService=Executors.newFixedThreadPool(4);
// 创建缓存线程池,线程个数由任务个数决定
ExecutorService executorService = Executors.newCachedThreadPool();
//1.3创建单线程线程池
//Executors.newSingleThreadExecutor();
//1.4创建调度线程池
//Executors.newScheduledThreadPool(corePoolSize);
executorService.submit(productor);
executorService.submit(productor);
executorService.submit(consumer);
executorService.submit(consumer);
//等待所有已执行的任务执行完毕后关闭线程池,不再接受新任务
executorService.shutdown();
//会试图停止所有正在执行的任务
//executorService.shutdownNow();
}
}
Callable接口
public interface Callable<V>{
public V call() throws Exception;
}
- JDK1.5加入,与Runnable接口类似,实现之后代表一个线程任务。
- Callable具有泛型返回值、可以声明异常。
与Runnable接口的区别:
- Callable接口中call方法有返回值,Runnable接口中run方法没有返回值。
- Callable接口中call方法有声明异常,Runnable接口中run方法没有异常。
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i < 100; i++) {
sum += i;
}
return sum;
}
};
// Thread 的构造方法没有带callable的构造方法
// 可以通过FutureTask对象转化成可执行任务,FutureTask 表示将要执行的任务
//该类实现了RunnableFuture<V>接口,而该接口又继承了Runnable类
FutureTask<Integer> task = new FutureTask<>(callable);
Thread thread = new Thread(task);
thread.start();
System.out.println("和="+task.get());
}
}
线程池的使用
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i < 100; i++) {
sum += i;
}
return sum;
}
};
Future<Integer> future = executorService.submit(callable);
System.out.println("和="+future.get());
executorService.shutdown();
}
}
Future接口
表示将要完成任务的结果。
用法如上。
线程的同步与异步
- 同步形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。当主线程调用子线程执行任务时,必须等到子线程返回结果后才能继续。
- 异步形容一次方法调用,异步一旦开始就像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。异步有多条执行路径。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0lzINrRq-1674030532275)(/upload/2023/01/1674009552136-72f1bc6f-2238-4f34-a250-de1f43fa48fe.png)]
Lock接口
- JDK1.5加入,与synchronized比较,不仅显示定义,而且结构更灵活。
- 提供了更多实用性方法,功能更强大、性能更优越。
常用方法:
- void lock获取锁,如果锁被占用,当前线程则进入等待状态。
- boolean tryLock()尝试获取锁(成功返回true,失败返回false,不阻塞)
- void unlock()释放锁。
重入锁
- ReentrantLock: Lock接口的实现类,与synchronized一样具有互斥锁功能。所谓重入锁,是指一个线程拿到该锁后,还可以再次成功获取,而不会因为该锁已经被持有(尽管是自己所持有)而陷入等待状态(死锁)。之前说过的synchronized也是可重入锁。
@SuppressWarnings("all")
public class Chopsticks {
private boolean getOneFlag = false;
private boolean getAnotherFlag = false;
public synchronized void getOne(){
this.getOneFlag = true;
System.out.println("当前用餐人员:"+Thread.currentThread().getName()+"拿了一个筷子");
if (getAnotherFlag){
canEat();
this.getAnotherFlag = false;
this.getOneFlag = false;
}else{
getAnother();
}
}
public synchronized void getAnother(){
this.getAnotherFlag = true;
System.out.println("当前用餐人员:"+Thread.currentThread().getName()+"拿了一个筷子");
if (getOneFlag){
canEat();
this.getAnotherFlag = false;
this.getOneFlag = false;
}else{
getOne();
}
}
public synchronized void canEat() {
System.out.println(Thread.currentThread().getName()+"拿到了两根筷子,开恰!");
}
}
public class ChopsticksTest {
public static void main(String[] args) {
Chopsticks chopsticks = new Chopsticks();
Runnable runnableA = new Runnable() {
@Override
public void run() {
chopsticks.getOne();
}
};
Runnable runnableB = new Runnable() {
@Override
public void run() {
chopsticks.getOne();
}
};
Thread threadA = new Thread(runnableA,"A");
Thread threadB = new Thread(runnableB,"B");
threadA.start();
threadB.start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MacOCBoC-1674030532276)(/upload/2023/01/1674025936210-86094c81-77b4-4a25-8ebe-a793def3d0c7.png)]
锁的对象是Chopsticks,当A线程获取到锁后,即使没有执行完,线程B抢占了CPU,由于没有线程B没有获取锁,也只能继续等待。线程A会继续执行后续的代码,执行完之后线程B才获取锁,进入getAnother()方法,开始执行代码。
重入锁的使用
public class Ticket implements Runnable {
int numbers = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "买了一张票,还剩" + (--numbers) + "张。");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Ticket ticket = new Ticket();
ExecutorService executorService = Executors.newFixedThreadPool(50);
for (int i = 0; i < 50; i++) {
executorService.submit( ticket);
}
executorService.shutdown();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4vs4mdVL-1674030532276)(/upload/2023/01/1674027111819-37430f80-335b-47d2-afcf-7dd38a7972c1.png)]
注意:有几个lock(),就要写几个unlock;
读写锁
ReentrantReadWriteLock:
- 一种支持一写多读的同步锁,读写分离,可以分别分配读锁和写锁。
- 支持多次分配读锁,使多个读操作可以并发执行。
互斥规则:
- 写----写:互斥,一个线程在写的同时其他线程会被阻塞。
- 读----写:互斥,读的时候不能写,写的时候不能读。
- 读----读:不互斥、不阻塞。
在读操作远远高于写操作的环境中,可在保证线程安全的情况下,提高运行效率。
@SuppressWarnings("all")
//演示读写锁的使用
public class ReadWriteLock {
//创建读写锁对象
ReentrantReadWriteLock rrlLock=new ReentrantReadWriteLock();
ReentrantReadWriteLock.ReadLock readLock=rrlLock.readLock();//获得读锁
ReentrantReadWriteLock.WriteLock writeLock=rrlLock.writeLock();//获得写锁
private int value=999;
//读方法
public int getValue() {
readLock.lock();//开启读锁
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.value;
} finally {
readLock.unlock();//释放读锁
}
}
//写方法
public void setValue(int value) {
writeLock.lock();//开启写锁
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.value=value;
} finally {
writeLock.unlock();//释放写锁
}
}
public static void main(String[] args) {
ExecutorService eService= Executors.newFixedThreadPool(20);
ReadWriteLock rwlLock=new ReadWriteLock();
Runnable read=new Runnable() {
@Override
public void run() {
System.out.println(rwlLock.getValue());
}
};
Runnable write=new Runnable() {
@Override
public void run() {
rwlLock.setValue(666);
System.out.println("改写为666");
}
};
//读18次
for(int i=0;i<18;i++) {
eService.submit(read);
}
//写2次
for(int i=0;i<2;i++) {
eService.submit(write);
}
//读18次
for(int i=0;i<18;i++) {
eService.submit(read);
}
eService.shutdown();
}
}