文章目录
- JAVA JUC
- 一、Volatile 关键字-内存可见性
- 二、原子变量与CAS算法
- 三、模拟CAS算法
- 四、ConcurrentHashMap锁分段机制
- 1、hashmap和hashtable区别:
- 2、ConCurrentHashMap采用“锁分段”机制
- 3、CopyOnWriteArrayList/CopyOnWriteArraySet : “写入并复制”
- 4、CountDownLatch(==闭锁==,在完成某些运算是,只有其他所有线程的运算全部完成,当前运算才继续执行)
- 四、创建执行线程的方式三:实现Callable接口
- 五、Lock同步锁
- 六、虚假唤醒问题(生产者消费者案例)
- 1、解决方法一:使用wait()和notifyAll()方法进行解决
- 2、由于解决方法一带来了==虚假唤醒问题==:
- 3、 解决方法:去掉else;(保证唤醒一定能够被执行。)
- 3、 产生问题原因:(虚假唤醒问题)
- 4、 虚假唤醒问题解决方式:JDK中特别提醒,在某一种情况中,虚假唤醒会总存在,因此wait()方法必须总是使用在循环中。最终解决方法把if变成while。
- 5、 本课涉及到的知识点:
- 七、解决线程安全问题之同步锁-condition
- 1、Condition
- 2、condition中方法与同步锁方法对应:
- 3、Condition 实例实质上被绑定到一个锁上。要为特定Lock 实例获得Condition 实例,请使用其newCondition() 方法。
- 4、通过lock()获得线程通信的方式:lock.newCondition();
- 八、线程按序交替(线程通信condition)
- 1、编写一个程序,开启三个线程,这三个线程的ID分别为ABC,其中A打印1次,B打印1次,C打印1次,每个线程将自己的ID在屏幕上打印10遍,要求输出的结果必须按照顺序显示。如:ABCABCABC......依次递归
- 2、加大难度:编写一个程序,开启三个线程,这三个线程的ID分别为ABC,其中A打印5次,B打印15次,C打印20次每个线程将自己的ID在屏幕上打印10遍,要求输出的结果必须按照顺序显示。
- 九、读写锁ReadWriteLock
- 十、线程八锁
- 十一、ThreadPool线程池
- 十二、线程池的调度
- 十三、FrokJoinPool分支合并框架-工作窃取
JAVA JUC
一、Volatile 关键字-内存可见性
1、java线程的6种状态Thread.state:
NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
新建、运行、阻塞、等待、超时等待、终止。
2、JAVA多线程:判断 干活 改标志位加唤醒通知(详见Thread.start()方法)
3.一共两个线程
一个thread线程,一个main线程,两个线程同时执行
- 第一个线程是改值
- 第二个线程是读值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qm9OTL48-1583231778310)(A01B1760CDC34D1CA290AD4A80485983)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5p0YtecE-1583231778313)(AAE2604868124D699D302FD37AA1FD8A)]
- 主存中flag的值开始是false,线程1读取数据并进行了改值,但是由于main线程使用while true结构对主存中的值进行读取,速度非常快。因此,main线程中的值是false。由于读取速度的问题造成了内存的可见性问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YdUEBQ66-1583231778337)(9BDF0F58788A49E5B50F93E192BAA840)]
(1)同步锁:synchronized关键字,使两个线程同步
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lDzxVtuR-1583231778339)(E7F3082E440C4247A1C38A0E61423D97)]
但是,使用了同步锁造成效率非常低。一个线程持有锁,另外线程来判断,就会造成阻塞悬挂。因此效率极低。
(2)Volatile关键字
当多个线程进行操作共享数据时,可以保证内存中的数据可见。
相较于 synchronized 是一种较为轻量级的同步策略。
注意:
- Volatile修饰的变量的操作都是在主存中完成的
- 由于数据每次都从主存中直接进行读取,因此相较于什么都不加线程效率会变低,但是比synchronized要高。
- Volatile效率低的原因是因为JVM底层使用重排序进行优化,使用Volatile效率低的原因是,JVM底层不能进行重排序。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DMbN2upV-1583231778343)(B26AADADD5F2452BBCBF53194B7A67D2)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R7UhuIsU-1583231778353)(3EBBF7D6118543A38801AB6E818FEB6E)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q63EJzGg-1583231778355)(5B1A85267D41473EA100092E97061810)]
(3)Volatile与Scnchronized比较
- 相较于 synchronized关键字Volatile只是一个轻量级的同步策略
- Volatile不具备互斥性,Synchronized是互斥锁(互斥:一个线程使用另一个线程不能够进行访问)。Volatile所有的访问都在主存中进行,因此一个线程访问时另一个线程依然能够进行访问。
- Volatile不能保证变量的原子性(原子性:不可分割性)
二、原子变量与CAS算法
1、i ++ 的原子性问题:i++ 的操作实际上分为三个步骤“读-改-写”
int i = 10;
i = i++; //最后答案是i的结果10
原因是在计算机底层进行如下操作:
int temp = i;
i = i+1;
i = temp;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7HCMHfmO-1583231778360)(A8874983D8464C97B333816DDA9C6B51)]
产生原因:
多个线程同时读取并操作主存中的数据,本身需要各个线程的操作并不独立,但是各个线程之间互相割裂(如图所示,都进行了加1的操作)。此时使用Volatile修饰并不能解决原子性问题。(Volatile这是将线程放到主存中进行操作并没有其他的影响,因此并不能解决原子性问题)。
2、原子变量:JDK1.5以后 java.util.concurrent.atomic 包下提供了常用的原子变量
(1)原子变量特性
- volatile 保证内存可见性(里面封装的变量都用volatile进行修饰,以保证内存可见性)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-35HepKEE-1583231778362)(8EF6A0346EC6488593C4E8A31A69E259)] - CAS(Compare-And-Swap) 算法保证数据变量的原子性
- CAS 算法是硬件对于并发操作的支持
- CAS 包含了三个操作数:①内存值 V ②预估值 A ③更新值 B
(当且仅当 V == A 时, V = B; 否则,不会执行任何操作。)
(2)原子性的解决过程:(先读取内存值,再进行比较,再进行赋值和写入)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6A3Zz0AM-1583231778368)(402ADF828CA34BA6BB78D2A5E41AA56B)]
- 如图,首先线程1读取主存中的数据,V=0,A=0,经过加1操作B=1,此时V==A,因此,成功写入主存变量值变为1.
- 线程2进行读取,V=0,A=1,B经过加一操作,因此V!=A,因此线程2不能够写入。然后线程2再讲V读取成1,B再进行加一操作此时进行下一轮的写入。
- 有且只有一个线程会成功
- 效率高,原因是:当一个线程没有读取成功是,它不会放弃该线程CPU使用权,而是再尝试继续读入,继续更新数据,因此效率是高的。(高于同步锁效率很多,缺点是自己写的算法多)
(3)原子变量的使用:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LGqScINr-1583231778370)(7A588E1B6CDF465D89AA2F7ABED87D4A)]
三、模拟CAS算法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WcREDpGK-1583231778372)(3F0D018E7FA4493FA29A45492C63A4F5)]
四、ConcurrentHashMap锁分段机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a5jfC941-1583231778374)(B294CCAD23D746F3AAB187199A500E2E)]
1、hashmap和hashtable区别:
- hashmap是线程不安全的,hashtable是线程安全的(效率低)
- hashmap和hashtable底层是一样的,只不过hashtable有锁,而且是锁整个表。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3oOmeOD6-1583231778376)(C8BB0FAA077B483A974F8657759677B5)]
- 当许多线程来访问hashtable时,只有一个线程能够进入。相当于原来的并行改成了串行,因此导致了效率低下。
- hashtable对复合操作来说也是线程不安全的。如图,contants操作有一个锁,put操作也有一个锁。当进行此线程时,contants完成后很有可能另外一个线程也是添加操作,就将原来不在的元素put进去了,而现有的程序还没有完成put操作,此时造成了线程不安全的问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BBe9CbA6-1583231778379)(11E85F79DE31489E830FA10A9A20F052)]
2、ConCurrentHashMap采用“锁分段”机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rN5m0u7E-1583231778380)(1650BA751A534B349C1F92F1B4B5D5EA)]
不仅线程安全而且保证了并行效率高。并且提供了复合操作的方法,用于提高效率。
- JDK1.8以后,在底层依然大量的采用了CAS算法解决HashTable的问题。(CAS可以理解为无锁算法)。
3、CopyOnWriteArrayList/CopyOnWriteArraySet : “写入并复制”
- 线程安全,但是效率低(因为每次写入都会复制,适合进行迭代操作,并发迭代操作适合用这个)
- 注意:添加操作多时,效率低,因为每次添加时都会进行复制,开销非常的大。并发迭代操作多时可以选择。
4、CountDownLatch(闭锁,在完成某些运算是,只有其他所有线程的运算全部完成,当前运算才继续执行)
- CountDownLatch是一个同步辅助类,在完成一组正在其他线程中进行的操作之前,他允许一个或多个线程一直等待。
- 闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行:
- 确保某个计算在其需要的所有资源都被初始化之后才继续执行;
- 确保某个服务再其依赖的其他服务都已经启动之后才启动;
- 等待直到某个操作所有的参与者都准备就绪再继续启动。
import java.util.concurrent.CountDownLatch;
/*
* CountDownLatch :闭锁,在完成某些运算是,只有其他所有线程的运算全部完成,当前运算才继续执行
*/
public class TestCountDownLatch {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(5);
LatchDemo ld = new LatchDemo(latch);
long start = System.currentTimeMillis();
for (int i = 0; i < 5; i++) {
new Thread(ld).start();
}
try {
latch.await();
} catch (InterruptedException e) {
}
long end = System.currentTimeMillis();
System.out.println("耗费时间为:" + (end - start));
}
}
class LatchDemo implements Runnable {
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
synchronized (this) {
try {
for (int i = 0; i < 50000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
} finally {
latch.countDown();
}
}
}
}
四、创建执行线程的方式三:实现Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*
* 一、创建执行线程的方式三:实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
*
* 二、执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
*/
public class TestCallable {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
//1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
//2.接收线程运算后的结果
try {
Integer sum = result.get(); //FutureTask 可用于 闭锁
System.out.println(sum);
System.out.println("------------------------------------");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadDemo implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
return sum;
}
}
/*class ThreadDemo implements Runnable{
er
@Override
public void run() {
}
}*/
- 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
- 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。FutureTask 是 Future 接口的实现类
- 当Thread(task).start()这个线程执行完成后,try catch这个线程才会执行。因此,futuretask相当于闭锁的效果,可用于闭锁,即前面执行完成后再执行当前线程。
- FutureTask继承了Runnable和Future所以可以用于闭锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DRpDd28h-1583231778382)(FC7B2521B322487ABEE5A03D8669B771)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fh9ABJaL-1583231778383)(1050997440664B3BBE9C06DC3A0D370A)]
五、Lock同步锁
1、用于解决多线程安全问题的方式:
Synchronized关键字:隐式锁
- 同步代码块
- 同步方法
JDK1.5以后
- 同步锁:显示锁,需要用lock()的方式上锁,必须通过unlock()的方式释放锁(更灵活的方式)。
注意:
- 为了保证unlock()方法一定被执行,因此一般把unlock()方法放在finally里面。
- 由于必须安全释放unlock()才能使用锁,因此同步锁方法同样存在安全问题。不能够完全的替代Sychronized方法,看情况使用。
- Sychronized关键字是一种基于底层的关键字,JVM会帮助维护。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket, "window 1").start();
new Thread(ticket, "window 2").start();
new Thread(ticket, "window 3").start();
}
}
class Ticket implements Runnable{
private int tick = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();//上锁
try {
if (tick>0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} System.out.println(Thread.currentThread().getName()+"完成售票,余票为:"+ --tick);
}
if (tick==0) {
break;
}//小bug
} finally {
lock.unlock();//释放锁
}
}
}
}
六、虚假唤醒问题(生产者消费者案例)
添加和创建数据的线程:生产者线程
删除和销毁数据的线程:消费者线程
生产者线程过快,造成数据丢失
消费者线程过快,造成重复数据错误数据等问题
问题代码如下图:
import javax.xml.stream.events.StartDocument;
/*
* 生产者和消费者案例
*/
public class TestProductorAndConsumer {
public static void main(String[] args) {
clerk clerk = new clerk();
productor productor = new productor(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(productor,"proA").start();
new Thread(consumer,"conB").start();
}
}
//店员
class clerk{
private int product = 0;
//进货
public synchronized void get() {
if (product>=10) {
System.out.println("产品已满");
}else {
System.out.println(Thread.currentThread().getName()+ ":"+ ++product);
}
}
//卖货
public synchronized void sale() {
if (product<=0) {
System.out.println("缺货");
}else {
System.out.println(Thread.currentThread().getName()+ ":"+ --product);
}
}
}
//继承是因为生产者可能有多个
class productor implements Runnable{
private clerk clerk;
public productor(testJUC.clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.get();
}
}
}
//消费者
class Consumer implements Runnable{
private clerk clerk;
public Consumer(testJUC.clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yqokWbpw-1583231778389)(C795EAAB90ED46ADBEF1CCBEC100BE63)]
其中一种情况,商品已经缺货还依然在出售。
1、解决方法一:使用wait()和notifyAll()方法进行解决
class clerk{
private int product = 0;
//进货
public synchronized void get() {
if (product>=10) {
System.out.println("产品已满");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+ ":"+ ++product);
this.notifyAll();
}
}
//卖货
public synchronized void sale() {
if (product<=0) {
System.out.println("缺货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()+ ":"+ --product);
this.notifyAll();
}
}
}
2、由于解决方法一带来了虚假唤醒问题:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TDbFTjw9-1583231778390)(6F3DEE1D3F49413CAA5FB35C908AC035)]
- 虚假唤醒问题产生原因图示:(问题发生在get和sale的else里面)
(假设生产者productor容量为一)
消费者线程剩余循环次数:1,生产者剩余循环次数:2.
a、首先线程进入消费者线程,此时product=0,而消费者线程循环剩余次数为0,再进入消费者线程是会在wait()的位置进去并继续执行,消费者线程wait()并释放锁的资源。
b、进入生产者线程后满足else条件,productor线程开始生产,++product并notifyall(),生产者消费者同时抢锁。
c、假设被消费者抢到,进入消费者线程,继续之前的wait的位置,虽然此时product=1.但是由于wait的位置因此不能进入else。程序wait()并释放所有资源。此时消费者线程循环剩余次数为0次。
d、此时生产者线程循环次数还剩1次,因此一定进入生产者线程,此时product==1满足product>=1条件,进入条件并开始wait();
e、问题生产者开始wait()后没有任何线程再去唤醒它,程序开始陷入锁定,不能退出也不能继续执行。
f、假设此时被消费者线程抢到,那么直接进入上一次wait的地方,即此时消费者线程进入等待状态而循环次数为0. 不能够notiALL()其他的线程,也不能够执行自己的循环,整个程序陷入不能被唤醒的锁定状态。
3、 解决方法:去掉else;(保证唤醒一定能够被执行。)
- 去掉else后,如果多个生产者多个消费者仍出现问题:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vayifgXU-1583231778392)(D2AE933A12934F6A8F7D670AC636F716)]
3、 产生问题原因:(虚假唤醒问题)
product==0,并被一个消费者抢到线程,消费者进入线程wait()。释放后,又被另一个消费者抢到线程,依旧wait()。再被生产者抢到线程执行一次product++,notifyAll(),又被消费者抢到了线程,此时进入–.notifyAll()后又被另一个消费者抢到线程,依旧–。就会出现负数的情况。而且如果被消费者多次抢到线程负数次数也会增加。
4、 虚假唤醒问题解决方式:JDK中特别提醒,在某一种情况中,虚假唤醒会总存在,因此wait()方法必须总是使用在循环中。最终解决方法把if变成while。
5、 本课涉及到的知识点:
1、虚假唤醒问题
2、生产者消费者问题
3、同步锁
4、等待唤醒机制
七、解决线程安全问题之同步锁-condition
1、Condition
Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个Lock 可能与多个Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的Object 版本中的不同。
2、condition中方法与同步锁方法对应:
-
await()->wait();
-
notify->siginal();
-
notifyAll()->signalAll();
3、Condition 实例实质上被绑定到一个锁上。要为特定Lock 实例获得Condition 实例,请使用其newCondition() 方法。
4、通过lock()获得线程通信的方式:lock.newCondition();
示例代码如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 生产者和消费者案例
*/
public class TestProductorAndConsumerFORLock {
public static void main(String[] args) {
TTclerk clerk = new TTclerk();
TTProductor TTproductor = new TTProductor(clerk);
TTconsumer TTconsumer = new TTconsumer(clerk);
new Thread(TTproductor,"proA").start();
new Thread(TTconsumer,"conB").start();
new Thread(TTproductor,"proC").start();
new Thread(TTconsumer,"conD").start();
}
}
//店员
class TTclerk{
private int product = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//进货
public void get() throws InterruptedException {
lock.lock();
//ifs
try {
while(product>=1) {
System.out.println("产品已满");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}/*
else {
System.out.println(Thread.currentThread().getName()+ ":"+ ++product);
this.notifyAll();
}*/
System.out.println(Thread.currentThread().getName()+ ":"+ ++product);
condition.signalAll();
} finally {
lock.unlock();
}
}
//卖货
public void sale() {
lock.lock();
try {
//if
while(product<=0) {
System.out.println("缺货");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}/*else {
System.out.println(Thread.currentThread().getName()+ ":"+ --product);
this.notifyAll();
}*/
System.out.println(Thread.currentThread().getName()+ ":"+ --product);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
//继承是因为生产者可能有多个
class TTProductor implements Runnable{
private TTclerk clerk;
public TTProductor(TTclerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
clerk.get();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class TTconsumer implements Runnable{
private TTclerk clerk;
public TTconsumer(TTclerk clerk2) {
this.clerk = clerk2;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}
八、线程按序交替(线程通信condition)
1、编写一个程序,开启三个线程,这三个线程的ID分别为ABC,其中A打印1次,B打印1次,C打印1次,每个线程将自己的ID在屏幕上打印10遍,要求输出的结果必须按照顺序显示。如:ABCABCABC…依次递归
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestABCAlternate {
public static void main(String[] args) {
AlternateDemo alternateDemo = new AlternateDemo();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
alternateDemo.loopA(i);
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
alternateDemo.loopB(i);
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
alternateDemo.loopC(i);
System.out.println("-------------------------------");
}
}
},"C").start();
}
}
class AlternateDemo{
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void loopA(int totalLoop) {
lock.lock();
try {
//1、判断是否执行第一个线程
if (number!=1) {
condition1.await();
}
//2、打印
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
}
//3、唤醒
number = 2;
condition2.signal();//唤醒线程2
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void loopB(int totalLoop) {
lock.lock();
try {
//1、判断是否执行第二个线程
if (number!=2) {
condition2.await();
}
//2、打印
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
}
//3、唤醒
number = 3;
condition3.signal();//唤醒线程3
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void loopC(int totalLoop) {
lock.lock();
try {
//1、判断是否执行第三个线程
if (number!=3) {
condition3.await();
}
//2、打印
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
}
//3、唤醒
number = 1;
condition1.signal();//唤醒线程1
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jIn0GT0c-1583231778394)(97A131F7960C499183E8EAB30F82350B)]
2、加大难度:编写一个程序,开启三个线程,这三个线程的ID分别为ABC,其中A打印5次,B打印15次,C打印20次每个线程将自己的ID在屏幕上打印10遍,要求输出的结果必须按照顺序显示。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestABCAlternate {
public static void main(String[] args) {
AlternateDemo alternateDemo = new AlternateDemo();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
alternateDemo.loopA(i);
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
alternateDemo.loopB(i);
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
alternateDemo.loopC(i);
System.out.println("-------------------------------");
}
}
},"C").start();
}
}
class AlternateDemo{
private int number = 1;
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void loopA(int totalLoop) {
lock.lock();
try {
//1、判断是否执行第一个线程
if (number!=1) {
condition1.await();
}
//2、打印
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
}
//3、唤醒
number = 2;
condition2.signal();//唤醒线程2
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void loopB(int totalLoop) {
lock.lock();
try {
//1、判断是否执行第二个线程
if (number!=2) {
condition2.await();
}
//2、打印
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
}
//3、唤醒
number = 3;
condition3.signal();//唤醒线程3
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void loopC(int totalLoop) {
lock.lock();
try {
//1、判断是否执行第三个线程
if (number!=3) {
condition3.await();
}
//2、打印
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totalLoop);
}
//3、唤醒
number = 1;
condition1.signal();//唤醒线程1
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hkAwegdm-1583231778397)(F42C62CE2E214CA9AB77D1C7B927C8B7)]
九、读写锁ReadWriteLock
1、读写锁是一种乐观锁,之前写一次读一次,现在可以写一次读很多次。
2、什么情况下用读写锁:
- 两个线程都在进行写入,此时需要保证线程安全(写写)
- 一个线程读一个线程写(读写)读线程可以多个线程并发的持有(长时间没有写的情况下),写线程锁必须是独占的
- 读读情况不需要互斥不需要用
- readLock()
- writeLock()
代码示例:
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestReadWriteLock {
public static void main(String[] args) {
ReadWriteLockDemo readWriteLockDemo = new ReadWriteLockDemo();
new Thread(new Runnable() {
@Override
public void run() {
readWriteLockDemo.set((int) (Math.random()*101));
}
},"Write").start();
for (int i = 1; i <= 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
readWriteLockDemo.get();
}
}).start();
}
}
}
class ReadWriteLockDemo{
private int number = 0;
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void get() {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+":"+number);
} finally {
lock.readLock().unlock();
}
}
public void set(int number) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName());
this.number = number;
} finally {
lock.writeLock().unlock();
}
}
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FAQiFdGa-1583231778398)(2A04CF80CBC54002AADDE3134D4FE37C)]
- 注意:
- 如果读线程比写线程更开始运行则读出来的值是初始值0。后面比写线程慢的读出来的是写入的值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ytGgMF9V-1583231778399)(42AC670392F741B6B65AC88FDBF956A1)]
十、线程八锁
1、判断打印输出的是什么?one还是two
- (1)两个同步方法,两个线程标准打印(synchronized关键字)
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
}
}
class Number{
public synchronized void getOne() {
System.out.println("one");
}
public synchronized void getTwo() {
System.out.println("two");
}
}
结果:one 和 two 都打印
- (2)在getOne方法中加入3秒延迟
package testJUC;
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
}
}
class Number{
public synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo() {
System.out.println("two");
}
}
结果:程序运行三秒后依此打印出one和two
- (3)新增一个普通方法getThree
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getThree();
}
}).start();
}
}
class Number{
public synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo() {
System.out.println("two");
}
public void getThree() {
System.out.println("Three");
}
}
结果:
先打出three过三秒再打出one和two
- (4)创建两个Number对象,number和number2,number调用getOne方法,number2调用getTwo方法
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number2.getTwo();
}
}).start();
// new Thread(new Runnable() {
//
// @Override
// public void run() {
// number.getThree();
// }
// }).start();
}
}
class Number{
public synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo() {
System.out.println("two");
}
public void getThree() {
System.out.println("Three");
}
}
结果:打印two三秒后打印出来one
- (5)修改getOne为静态同步方法(static synchronized),使用一个number对象调用getOne()和getTwo()方法
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
// new Thread(new Runnable() {
//
// @Override
// public void run() {
// number.getThree();
// }
// }).start();
}
}
class Number{
public static synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo() {
System.out.println("two");
}
public void getThree() {
System.out.println("Three");
}
}
结果:打印two三秒后打印出来one
- (6)两个静态方法
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number.getTwo();
}
}).start();
// new Thread(new Runnable() {
//
// @Override
// public void run() {
// number.getThree();
// }
// }).start();
}
}
class Number{
public static synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public static synchronized void getTwo() {
System.out.println("two");
}
public void getThree() {
System.out.println("Three");
}
}
结果:三秒后打印one和two
- (7)两个对象,number调用静态同步方法getOne(),number2调用非静态同步方法getTwo()
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number2.getTwo();
}
}).start();
// new Thread(new Runnable() {
//
// @Override
// public void run() {
// number.getThree();
// }
// }).start();
}
}
class Number{
public static synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public synchronized void getTwo() {
System.out.println("two");
}
public void getThree() {
System.out.println("Three");
}
}
结果:打印two三秒后打印出来one
- (8)两个都是静态同步方法,分别用两个对象调用
public class TestThread8Monitor {
public static void main(String[] args) {
Number number = new Number();
Number number2 = new Number();
new Thread(new Runnable() {
@Override
public void run() {
number.getOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
number2.getTwo();
}
}).start();
// new Thread(new Runnable() {
//
// @Override
// public void run() {
// number.getThree();
// }
// }).start();
}
}
class Number{
public static synchronized void getOne() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one");
}
public static synchronized void getTwo() {
System.out.println("two");
}
public void getThree() {
System.out.println("Three");
}
}
结果:三秒后打印one和two
规律
- 在某一时刻,只有一个对象持有锁;另外一个线程不能抢到。
线程八锁的关键:
- 非静态方法的锁默认为this,静态方法的锁为对应的class(number.class)实例;
- 某一时刻内,只能有一个线程持有锁,无论几个方法
十一、ThreadPool线程池
问题:每次new Thread().start()启动线程用完后销毁线程,当任务很多的话频繁的创建线程销毁线程很浪费资源。(数据库连接池)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lbPIOpRV-1583231778401)(782DA82B3DA44AC49148CBDDCFBE996F)]
数据库连接池
每次创建与数据库的连接再取消很麻烦,于是建立一个中间连接池。连接池中的连接与数据库一直保持联系,当需要用到数据传输时直接连接连接池中的连接就可以,不用的话放在连接池中。这样做能够减少资源的浪费并保持高效连接。(谁用谁拿)(线程池道理一样)
1、线程池:
提供了一个个线程队列,队列中保存着所有等待状态的线程,避免了创建与销毁额外开销,提高了响应速度。
2、线程池的体系结构
- java.util.concurrent.Excetor(Interface):线程池核心接口,负责维护线程的使用和调度的根接口
- ExcetorService(子接口):线程池的主要接口
- ThreadPoolExcutor:线程池的实现类
- ScheduleExcutorService(子接口):负责线程调度
- ScheduledThreadPoolExcutor:实现类,继承了ThreadPoolExcutor(线程池的实现类),实现了ScheduleExcutorService(负责线程调度的接口),因此兼具线程池的功能和线程调度功能
- ExcetorService(子接口):线程池的主要接口
3、工具类
- (ExcetorService) newFixedThreadPool():创建固定大小的线程池
- 返回值:(ExcetorService)类型的根接口
- (ExcetorService)newCachedThreadPool() :缓存线程池,线程池的大小不固定,根据需求自动的更改数量
- (ExcetorService)newSingleThreadExcutor():创建单个的线程池。线程池中只有一个线程。
- (ScheduledThreadPoolExcutor) newScheduledThreadPool():创建固定大小的线程,可以延迟或定时的执行任务。
- java工厂模式自动配备好了线程的内部配置,如有其他需要可以根据API文档再进行配置。
4、线程池实现Runnable接口
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
public static void main(String[] args) {
//1、创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
TTTTTTThreadDemo tDemo = new TTTTTTThreadDemo();
//2、为线程池中的线程分配任务
// pool.submit(tDemo);
for (int i = 0; i < 3; i++) {
pool.submit(tDemo);
}
//3、关闭线程池
pool.shutdown();//等待现有任务执行完成之后才会关闭,关闭之后不会再接受新的任务
//pool.shutdownNow();//不管现有线程是否执行完都会立即关闭
}
}
class TTTTTTThreadDemo implements Runnable{
private int i = 0;
@Override
public void run() {
while (i<=100) {
System.out.println(Thread.currentThread().getName()+":"+i++);
}
}
}
5、线程池实现Callable接口
import java.util.ArrayList;
import java.util.List;
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 TestThreadPool {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newFixedThreadPool(5);
List<Future<Integer>> list = new ArrayList<Future<Integer>>();
for (int i = 0; i < 10; i++) {
Future<Integer> future = pool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum+=i;
}
return sum;
}
});
list.add(future);
}
for (Future<Integer> future : list) {
System.out.println(future.get());
}
pool.shutdown();
}
}
十二、线程池的调度
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class TestScheduledThreadPool {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 10; i++) {
Future<Integer> future = pool.schedule(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int number = new Random().nextInt(100);
System.out.println(Thread.currentThread().getName()+":"+number);
return number;
}
}, 1, TimeUnit.SECONDS);
System.out.println(future.get());
}
pool.shutdown();
}
}
十三、FrokJoinPool分支合并框架-工作窃取
1、
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oq747ur5-1583231778402)(3A6081AF3FE8456B9378D1AA911936A6)]
并行求值:多个线程同时对一个一个小任务进行求值。
2、
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-foalJDzZ-1583231778403)(671F5FF40E584D6DBBF2E3A64F124502)]
工作窃取模式:
- 把不同的的任务分到不同的线程中执行,线程池设计阻塞问题。不同线程的任务分配在不同的内核上进行完成。一旦某内核上的某一个线程阻塞,这个内核上的后续线程尽行不下去。未阻塞的内核处于忙碌状态,阻塞的线程处于不能继续进行工作的状态,造成CPU内核空闲,并没有更加合理利用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vGxUJHhV-1583231778404)(873BB497EA5B490792E97662DD6FE20D)]
- 因此在jdk1.7以后出现了Frok/Join框架采用了工作窃取模式,把大任务拆成小任务,并把小任务放到对应的线程中,形成一个双端的队列。一旦某一个线程在获取线程任务时获取不到,此时,会随机从其他的内核的队伍的末尾偷一个线程继续执行任务。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YZqEfSHR-1583231778406)(63FF349868304D859E843032607622D8)]
3、手写frokjoin框架:
- 建立类FrokJoinSumCaculate模拟FrokJoin工作模式计算,该类继承了RecursiveTask类。
- RecursiveTask和RecursiveAction类功能相近,区别在于RecursiveTask有返回值,而RecursiveAction没有返回值。
- RecursiveTask类继承了ForkJoinTask类(JDK1.7),ForkJoinTask类继承了Future类(JDK1.5)。
- test2方法使用了java8新特性,并行流
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
import org.junit.Test;
public class TestForkJoinPool {
public static void main(String[] args) {
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L, 100000000L);
Long sum = pool.invoke(task);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//166-1996-10590
}
@Test
public void test1(){
Instant start = Instant.now();
long sum = 0L;
for (long i = 0L; i <= 50000000000L; i++) {
sum += i;
}
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//35-3142-15704
}
//java8 新特性,并行流
@Test
public void test2(){
Instant start = Instant.now();
Long sum = LongStream.rangeClosed(0L, 50000000000L)
.parallel()//并行流
.reduce(0L, Long::sum);
System.out.println(sum);
Instant end = Instant.now();
System.out.println("耗费时间为:" + Duration.between(start, end).toMillis());//1536-8118
}
}
class ForkJoinSumCalculate extends RecursiveTask<Long>{
private static final long serialVersionUID = -259195479995561737L;
private long start;
private long end;
private static final long THURSHOLD = 10000L; //临界值
public ForkJoinSumCalculate(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if(length <= THURSHOLD){
long sum = 0L;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
}else{
long middle = (start + end) / 2;
ForkJoinSumCalculate left = new ForkJoinSumCalculate(start, middle);
left.fork(); //进行拆分,同时压入线程队列
ForkJoinSumCalculate right = new ForkJoinSumCalculate(middle+1, end);
right.fork(); //
return left.join() + right.join();
}
}
}
来源:CSDN
作者:cpqmdbwpxc
链接:https://blog.csdn.net/cpqmdbwpxc/article/details/104638547