多线程进阶==>juc并发编程
1、什么是juc?
**java.util.concurrent *** java.util工具包
业务:普通的线程代码 Thread
Runnable 没有返回值,效率比Callable相抵较低!
2、线程和进程
进程:一个程序 ,QQ.exe 程序的集合;
一个进程往往可以包含多个线程,至少包含一个!
java默认包含几个线程?2个 main、GC
线程:开了一个进程typora,写字,自动保存(线程负责)
对于java而言:Thread、Runnable、Callable
java真的可以开启线程么?
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* <code>start</code> method) and the other thread (which executes its
* <code>run</code> method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//本地方法,底层c++,java无法直接操作硬件
private native void start0();
并发、并行
并发编程:并发、并行
并发:多线程操作同一个资源
- CPU一核,模拟出来多条线程,天下武功,为快不破,快速交替
并行:(多个人一起行走)
- CPU多核,多个线程可以同时执行
package cn.smallcosmos;
/**
* @Date 2020/7/2 上午11:39
* @Created by zhaoli
*/
public class Demo1 {
public static void main(String[] args) {
//获取CPU核心数
//CPU密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU资源
线程有几个状态 --> 6个
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
wait/sleep的区别
1、来自不同的类
wait–>Object
sleep–>Thread
2、关于锁的释放
wait–>会释放锁
sleep–> 不会释放锁
3、使用的范围不同
wait–>必须在同步控制块中使用
sleep–>可以在任何地方睡
4、是否需要捕获异常
wait–>不需要捕获异常
sleep–>必须捕获异常
3、lock锁(重点)
传统Synchronized
Lock 接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hQBN1DI1-1596633696952)(/tmp/1594112325562.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KsFfquUo-1596633696955)(/tmp/1594113447324.png)]
公平锁
非公平锁(java默认)
package cn.smallcosmos;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Date 2020/7/7 下午4:34
* @Created by zhaoli
*/
public class SaleTicketDemo2 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(() -> {
while(true)
ticket.sale();
},"A").start();
new Thread(() -> {
while(true)
ticket.sale();
},"B").start();
new Thread(() -> {
while(true)
ticket.sale();
},"C").start();
}
}
class Ticket2 {
private int nums = 30;
Lock lock = new ReentrantLock();
public void sale(){
lock.lock();//加锁
try {
if(nums > 0){
System.out.println(Thread.currentThread().getName() + "正在卖第" + nums -- + "票" + "," + "剩余" + nums + "票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
synchronized和Lock区别
1、synchnorized 内置java关键字,Lock是一个java类。
2、synchnorized 无法判断锁的状态,Lock可以判断是否获取到了锁。
3、synchnorized会自动释放锁,Lock必须手动释放锁,如果不释放锁,会发生死锁。
4、synchnorized 线程1(获得锁,阻塞) 线程2(等待);Lock锁就不一定会等待 。
5、synchnorized 可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置)。
6、synchnorized 适合锁少量的代码同步问题,Lock适合大量的同步代码。
锁是什么,如何判断锁是谁?
4、生产者和消费者
生产者和消费者 synchnorized 版本
package cn.smallcosmos.pc;
/**
* @Date 2020/7/7 下午5:56
* @Created by zhaoli
*/
public class TestDemo1 {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.increment();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
}
}
class Data {
private int data;
//+1 生产者
public synchronized void increment() throws InterruptedException {
if (data != 0) {
this.wait();
}
data++;
System.out.println(Thread.currentThread().getName() + "->" + data);
notifyAll();
}
//-1 消费者
public synchronized void decrement() throws InterruptedException {
if (data == 0) {
this.wait();
}
data--;
System.out.println(Thread.currentThread().getName() + "->" + data);
notifyAll();
}
}na
使用if判断会产生虚假唤醒问题,解决办法将if改为while。
虚假唤醒结果分析
可以看到上述结果出现了data为2的情况,不符合之前的预期,出现问题的场景是这样的:当data为1的时候,线程A和B先后获取锁去生产数据的时候会被阻塞住,然后消费者C或者D消费掉数据后去notifyAll()唤醒了线程A和B,被唤醒的A和B没有再次去判断data状态,就去执行后续增加数据的逻辑了,导致两个生产者都执行了increment(),最终data出现了2这种情况。也就是说线程A和B有一个是不应该被唤醒的却被唤醒了,出现这个问题的关键点在于程序中使用到了if判断,只判断了一次data的状态,应该使用while循环去判断
生产者和消费者JUC版本
package cn.smallcosmos.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Date 2020/7/8 上午9:20
* @Created by zhaoli
*/
public class TestDemo2 {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.increment();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.increment();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "C").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "B").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
data.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "D").start();
}
}
class Data2 {
private int data;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//+1 生产者
public void increment() throws InterruptedException {
lock.lock();//加锁
try {
while (data != 0) {
condition.await();//等待
}
data++;
System.out.println(Thread.currentThread().getName() + "->" + data);
condition.signalAll();//唤醒所有
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
//-1 消费者
public void decrement() throws InterruptedException {
lock.lock();
try {
while (data == 0) {
condition.await();
}
data--;
System.out.println(Thread.currentThread().getName() + "->" + data);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
condition精准的通知和唤醒线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1o3J6Sor-1596633696956)(/tmp/1594174231607.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jj5rQUex-1596633696958)(/tmp/1594175143223.png)]
代码测试:
package cn.smallcosmos.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Date 2020/7/8 上午10:29
* @Created by zhaoli
* 执行顺序 ABC
*/
public class TestDemo3 {
public static void main(String[] args) {
data3 data = new data3();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printA();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printB();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printC();
}
},"C").start();
}
}
class data3 {
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;
public void printA(){
lock.lock();
try {
while(number != 1){
condition1.await();
}
number = 2;
System.out.println(Thread.currentThread().getName() + " = " + number);
//唤醒指定的人 B
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while(number != 2){
condition2.await();
}
number = 3;
System.out.println(Thread.currentThread().getName() + " = " + number);
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while(number != 3){
condition3.await();
}
number = 1;
System.out.println(Thread.currentThread().getName() + " = " + number);
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
5、8锁现象
如何判断锁的是谁?
package cn.smallcosmos.demo2;
import java.util.concurrent.TimeUnit;
/**
* @Date 2020/7/8 上午11:02
* @Created by zhaoli
* 8锁问题,实际上是关于锁的8个问题
* 1、标准情况下,两线程是先打印发短信还是打电话? 发短信 打电话`
* 2、sendMsg延迟四秒,两线程是先打印发短信还是打电话? 发短信 打电话`
*/
public class TestDemo1 {
public static void main(String[] args) {
phone phone = new phone();
new Thread(() -> {
phone.sendMsg();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
},"B").start();
}
}
class phone {
//synchronized 锁的对象是方法的调用者
//两个方法使用的是同一个锁(phone)
public synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
package cn.smallcosmos.demo2;
import java.util.concurrent.TimeUnit;
/**
* @Date 2020/7/9 上午9:11
* @Created by zhaoli
* 3、 一个加锁的方法和一个普通方法 ,两线程是先打印发短信还是打电话? hello 发短信`
* 4、 两个对象两个同步方法,两线程是先打印发短信还是打电话? 打电话 发短信`
*
*/
public class TestDemo2{
public static void main(String[] args) {
phone2 phone = new phone2();
// phone2 phone1 = new phone2();
new Thread(() -> {
phone.sendMsg();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
},"B").start();
}
}
class phone2 {
//synchronized 锁的对象是方法的调用者
//两个方法使用的是同一个锁(phone)
//static 静态方法
//类一加载就有了,锁的是class模板
public static synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
package cn.smallcosmos.demo2;
import java.util.concurrent.TimeUnit;
/**
* @Date 2020/7/9 上午9:32
* @Created by zhaoli
* 5、 两个静态加锁方法,两线程是先打印发短信还是打电话? 发短信 打电话`
* 6、 两个对象,两个静态加锁方法,两线程是先打印发短信还是打电话? 发短信 打电话`
*/
public class TestDemo3{
public static void main(String[] args) {
phone3 phone = new phone3();
phone3 phone1 = new phone3();
new Thread(() -> {
phone.sendMsg();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone1.call();
},"B").start();
}
}
class phone3 {
//synchronized 锁的对象是方法的调用者
//两个方法使用的是同一个锁(phone)
public static synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
package cn.smallcosmos.demo2;
import java.util.concurrent.TimeUnit;
/**
* @Date 2020/7/9 上午9:32
* @Created by zhaoli
* 7、一个对象,一个静态同步方法一个非静态同步方法,两线程是先打印发短信还是打电话? 打电话 发短信 `
* 8、两个对象,一个静态同步方法一个非静态同步方法,两线程是先打印发短信还是打电话? 打电话 发短信 `
*/
public class TestDemo4{
public static void main(String[] args) {
phone4 phone = new phone4();
phone4 phone1 = new phone4();
new Thread(() -> {
phone.sendMsg();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone1.call();
},"B").start();
}
}
class phone4 {
//synchronized 锁的对象是方法的调用者
//两个方法使用的是同一个锁(phone)
public static synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
小结
new this 锁的是对象
static class 锁的是类模板
6、集合类不安全
list不安全
package cn.smallcosmos.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @Date 2020/7/9 上午9:58
* @Created by zhaoli
* java.util.ConcurrentModificationException 并发修改异常保证可见性
*
*/
public class TestList {
public static void main(String[] args) {
/**
* 解决方案
* 1、List<String> list = new Vector<>();不推荐
* 2、List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3、List<String> list = new CopyOnWriteArrayList<>();
*
*/
// List<String> list = new ArrayList();
// List<String> list = new Vector<>();
// List<String> list = Collections.synchronizedList(new ArrayList<>());
//CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略
//多个线程调用的时候,list,先复制原数组,创建一个新数组,插入数据后,再将新数组写入原数组
//在写入是避免覆盖,造成数据问题。
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
set不安全
package cn.smallcosmos.unsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @Date 2020/7/9 上午11:36
* @Created by zhaoli
* java.util.ConcurrentModificationException 并发修改异常
*/
public class TestSet {
public static void main(String[] args) {
// Set<String> set = new HashSet<>();
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
7、Callable
new Thread(Runnable) 参数必须为Runnable
FutureTask实现了Runnable接口
FutureTask(Callable callable)` 创建一个FutureTask,他将在运行时执行指定的Callable。
测试代码:
package cn.smallcosmos.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("调用callable()");
return 1024;
}
}
8、其他辅助类
CountDownLatch(减法计数器)
package cn.smallcosmos.countdownlatch;
import java.util.concurrent.CountDownLatch;
public class TestCountDownLatch {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(7);
for (int i = 1; i <= 7; i++) {
int temp = i;
new Thread(() -> {
System.out.println("第" + temp + "人出门了");
countDownLatch.countDown();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
System.out.println("CLose Door");
}
}
CyclicBarrier(加法计数器) 循环栅栏
package cn.smallcosmos.cyclicbarrier;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class TestCyclicbarrier {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, new Runnable() {
public void run() {
System.out.println("集齐7颗龙珠,召唤神龙!!!");
}
});
for (int i = 1; i <= 7; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "正在集齐第" + temp + "颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
Semaphore(信号量)
package cn.smallcosmos.semaphore;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class TestSemphore {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);//创建一个资源数量为3的信号量
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
try {
semaphore.acquire();//获取一个资源,如果资源数目为0,则等待释放,
System.out.println(Thread.currentThread().getName() + "抢到了车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();//释放资源,资源数目+1
}
}, String.valueOf(i)).start();
}
}
}
9、读写锁
ReadWriteLock
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1cZk00lN-1596633696960)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200725201455899.png)]
package cn.smallcosmos.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
* ReadWriteLock
* 读-读 可以共存
* 写-读 不能共存
* 写-写 不能共存
*/
public class ReadWriteLockDemo1 {
public static void main(String[] args) {
// MyCache myCache = new MyCache();
MyCacheLock myCache = new MyCacheLock();
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.setKey(temp + "",temp + "");
},String.valueOf(i)).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.get(temp + "");
},String.valueOf(i)).start();
}
}
}
//加锁
class MyCacheLock {
private volatile Map<String,Object> map = new HashMap<>();
// 读写锁
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//存
public void setKey(String key,Object value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + value);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
//取
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取" + map.get(key));
System.out.println(Thread.currentThread().getName() + "读取OK" );
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
class MyCache {
private volatile Map<String,Object> map = new HashMap<>();
//存
public void setKey(String key,Object value){
System.out.println(Thread.currentThread().getName() + "写入" + value);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "写入OK");
}
//取
public void get(String key){
System.out.println(Thread.currentThread().getName() + "读取" + map.get(key));
System.out.println(Thread.currentThread().getName() + "读取OK" );
}
}
10、阻塞队列
BlockingQueue
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kvfube0X-1596633696961)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200725204231292.png)]
四组API
方式 | 抛出异常 | 有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer()有参 |
移除 | remove() | poll() | take() | poll()有参 |
判断队列首部 | element() | peek() | - | - |
/**
* 抛出异常
* */
public static void test1(){
ArrayBlockingQueue bq = new ArrayBlockingQueue<>(3);
System.out.println(bq.add("a"));
System.out.println(bq.add("b"));
System.out.println(bq.add("c"));
//java.lang.IllegalStateException: Queue full
//System.out.println(bq.add("d"));
System.out.println(bq.remove());
System.out.println(bq.remove());
System.out.println(bq.remove());
//java.util.NoSuchElementException
//System.out.println(bq.remove());
}
/**
* 有返回值,没有异常
*/
public static void test2(){
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d")); //返回false,不抛出异常
System.out.println(blockingQueue.peek());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll()); //返回null,不抛出异常
}
/**
*阻塞等待
*/
public static void test3() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d"); //程序会阻塞到这个地方,直到这个元素放进去
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());//程序会阻塞到这个地方,直到可以取出来一个元素
}
/**
* 超时等待
* @throws InterruptedException
*/
public static void test4() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//System.out.println(blockingQueue.offer("d",2, TimeUnit.SECONDS)); //等待超过两秒,程序就会结束
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
}
SynchronousQueue(同步队列)
SynchronousQueue这种队列的特点是不缓存数据,而是缓存线程,线程分为生产者线程和消费者线程,一个生产者线程和一个消费者线程是互补的,当一个生产者线程遇到一个消费者线程的时候就会直接进行数据交换,所以这种队列的技术点比较高,理解起来难度较大。一个线程只能缓存一个数据,当一个线程插入数据之后就会被阻塞,直到另外一个线程消费了其中的数据。
package cn.smallcosmos.bq;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "==>" + "put1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + "==>" + "put2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + "==>" + "put3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "==>" + blockingQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "==>" + blockingQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "==>" + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dl9kRD1c-1596633696962)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200727204222653.png)]
11、线程池(重点)
线程池:三大方法、七大参数、四个拒绝策略
池化技术
程序运行的本质==》占用系统资源 优化资源的使用==》池化技术
池化技术:事先准备一些资源,需要使用来池子里拿,用完归还到池子,减少了资源创建和销毁的消耗,从而提高资源利用。
线程池的好处
1、降低资源消耗
2、提高响应速度
3、方便管理
线程复用可以控制最大并发数,管理线程
三大方法
package cn.smallcosmos.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestExecutors {
public static void main(String[] args) {
// ExecutorService threadpool = Executors.newSingleThreadExecutor();//单个线程
// ExecutorService threadpool = Executors.newFixedThreadPool(5);//固定大小的线程池
ExecutorService threadpool = Executors.newCachedThreadPool();//可伸缩,依赖于电脑性能
for (int i = 0; i < 100; i++) {
threadpool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "ok");
});
}
threadpool.shutdown();
}
}
七大参数
源码分析
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//本质:ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,//核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//存活时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler//拒绝策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OjwzxN5U-1596633696963)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200727213634178.png)]
手动创建一个线程池
四种拒绝策略
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NFhZLf7G-1596633696964)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200727225243220.png)]
package cn.smallcosmos.pool;
import java.util.concurrent.*;
/**
* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
* ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务
* ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
*/
public class TestExecutors {
public static void main(String[] args) {
ExecutorService threadpool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardPolicy()
);
//最大承载量:Queue + max
for (int i = 1; i <= 9; i++) {
threadpool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "ok");
});
}
threadpool.shutdown();
}
}
了解和拓展
如何设置线程池的最大大小
//获取电脑CPU总核心数
Runtime.getRuntime().availableProcessors()
IO密集型
这类任务的CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。常见的大部分任务都是IO密集型任务,比如Web应用。对于IO密集型任务,任务越多,CPU效率越高(但也有限度)。
一般配置线程数=CPU总核心数 * 2 +1
CPU密集型
要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
一般配置线程数=CPU总核心数+1(+1是为了利用等待空闲)
12、四大函数式接口(必须掌握)
lambda表达式、链式编程、函数式接口、stream流式计算
函数式接口 只有一个方法的接口
@FunctionalInterface //函数式接口
public interface Runnable {
void run();
}
代码测试
函数型接口
package cn.smallcosmos.function;
import java.util.function.Function;
/**
* Function函数型接口,有一个输入参数,有一个输出
* 只要是函数型接口就可以用lambda表达式简化
*/
public class Demo01 {
public static void main(String[] args) {
// Function function = new Function<String,String>() {
// @Override
// public String apply(String s) {
// return s;
// }
// };
Function function = (str) -> {return str;};
System.out.println(function.apply("aaa"));
}
}
断定型接口
package cn.smallcosmos.function;
import java.util.function.Predicate;
/**
* 断定型接口 : 只有一个输入参数,并且返回值只能是boolean类型
*/
public class Demo2 {
public static void main(String[] args) {
// Predicate<String> predicate = new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.isEmpty();
// }
// };
Predicate<String> predicate = (str) -> {return str.isEmpty();};
System.out.println(predicate.test("aaa"));
}
}
消费型接口
package cn.smallcosmos.function;
import java.util.function.Consumer;
/**
* 消费型接口:只有一个输入参数,没有返回值
*/
public class Demo03 {
public static void main(String[] args) {
// Consumer consumer = new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// };
Consumer consumer = (str) -> { System.out.println(str);};
consumer.accept("aaa");
}
}
供给型接口
package cn.smallcosmos.function;
import java.util.function.Supplier;
/**
* 供给型接口:没有输入参数,有一个返回值
*/
public class Demo04 {
public static void main(String[] args) {
// Supplier<Integer> supplier = new Supplier<Integer>() {
// @Override
// public Integer get() {
// return 1024;
// }
// };
Supplier<Integer> supplier = () -> {return 1024;};
System.out.println(supplier.get());
}
}
Stream流式计算
什么是Stream流式计算
大数据:存储加计算
集合,MySQL本质就是存储东西的‘
计算交给流来操作
package cn.smallcosmos.function;
import java.util.Arrays;
import java.util.List;
/**
* 1、ID必须是偶数
* 2、年龄必须大于23岁
* 3、用户名转为大写
* 4、用户名字母倒着排序
* 5、只输出一个用户
*/
public class Test {
public static void main(String[] args) {
Student u1 = new Student(1,20,"a");
Student u2 = new Student(2,21,"b");
Student u3 = new Student(3,22,"c");
Student u4 = new Student(4,24,"d");
Student u5 = new Student(6,25,"e");
List<Student> students = Arrays.asList(u1, u2, u3, u4, u5);
students.stream()
.filter((u) ->{return u.getId() % 2 == 0;})
.filter((u) -> {return u.getAge() > 23;})
.map((u) -> {return u.getName().toUpperCase();})
.sorted((uu1,uu2) -> {return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::println);
}
}
13、ForkJoin(分支合并)
什么是ForkJoin
ForkJoin在JDK1.7之后出现,并行执行任务,提高效率,大数据量
大数据:Map Reduce(把大任务拆分为小任务)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4jTMQdLu-1596633696965)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200730204451908.png)]
ForkJoin特点:工作窃取
这里面操作的都是双端队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a1s2IXBr-1596633696966)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200730205010180.png)]
package cn.smallcosmos.function;
import java.util.concurrent.RecursiveTask;
public class ForkJoinDemo extends RecursiveTask<Long> {
private long start;
private long end;
private long temp = 10000L;
public ForkJoinDemo() {
}
public ForkJoinDemo(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long sum = 0;
if(end - start < temp){
for(long i = start;i <= end;i++){
sum += i;
}
return sum;
}else{
long mid = (start + end) / 2;
//将任务拆分为两部分
ForkJoinDemo task1 = new ForkJoinDemo(start, mid);
task1.fork();
ForkJoinDemo task2 = new ForkJoinDemo(mid + 1, end);
task2.fork();
//合并结果
return task1.join() + task2.join();
}
}
}
//测试代码
package cn.smallcosmos.function;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
public class TestForkJoin {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//10_0000_0000 100_0000_0000
test1();//392 8025
// test2(); //346 3190
// test3();//352 2048
}
//普通方法
public static void test1(){
long start = System.currentTimeMillis();
long sum = 0L;
for(long i = 0L;i <= 100_0000_0000L;i++){
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("sum = " + sum + "时间:" + (end - start));
}
//使用ForkJoin
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> submit = forkJoinPool.submit(new ForkJoinDemo(0L, 100_0000_0000L));
Long sum = submit.get(); long end = System.currentTimeMillis();
System.out.println("sum = " + sum + "时间:" + (end - start));
}
//Stream流式计算
public static void test3() {
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0L, 100_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum = " + sum + "时间:" + (end - start));
}
}
14、异步回调
Future 对将来某个事件结果进行建模
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pSpsVRZI-1596633696967)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200802102108070.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lhT7F315-1596633696968)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200802172107872.png)]
代码测试
package cn.smallcosmos.future;
import java.util.concurrent.CompletableFuture;
public class Demo01 {
public static void main(String[] args) throws Exception {
// 创建异步执行任务:
CompletableFuture<Double> cf = CompletableFuture.supplyAsync(Demo01::fetchPrice);
// 如果执行成功:
cf.thenAccept((result) -> {
System.out.println("price: " + result);
});
// 如果执行异常:
cf.exceptionally((e) -> {
e.printStackTrace();
return null;
});
System.out.println("111111");
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
Thread.sleep(20000);
}
static Double fetchPrice() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
if (Math.random() < 0.3) {
throw new RuntimeException("fetch price failed!");
}
return 5 + Math.random() * 20;
}
}
执行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HfPOFcgD-1596633696969)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200802170623832.png)]
可见CompletableFuture
的优点是:
- 异步任务结束时,会自动回调某个对象的方法;
- 异步任务出错时,会自动回调某个对象的方法;
- 主线程设置好回调后,不再关心异步任务的执行。
如果只是实现了异步回调机制,我们还看不出CompletableFuture
相比Future
的优势。CompletableFuture
更强大的功能是,多个CompletableFuture
可以串行执行,例如,定义两个CompletableFuture
,第一个CompletableFuture
根据证券名称查询证券代码,第二个CompletableFuture
根据证券代码查询证券价格,这两个CompletableFuture
实现串行操作如下:
public class Main {
public static void main(String[] args) throws Exception {
// 第一个任务:
CompletableFuture<String> cfQuery = CompletableFuture.supplyAsync(() -> {
return queryCode("中国石油");
});
// cfQuery成功后继续执行下一个任务:
CompletableFuture<Double> cfFetch = cfQuery.thenApplyAsync((code) -> {
return fetchPrice(code);
});
// cfFetch成功后打印结果:
cfFetch.thenAccept((result) -> {
System.out.println("price: " + result);
});
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
Thread.sleep(2000);
}
static String queryCode(String name) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
return "601857";
}
static Double fetchPrice(String code) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
return 5 + Math.random() * 20;
}
}
除了串行执行外,多个CompletableFuture
还可以并行执行。例如,我们考虑这样的场景:
同时从新浪和网易查询证券代码,只要任意一个返回结果,就进行下一步查询价格,查询价格也同时从新浪和网易查询,只要任意一个返回结果,就完成操作:
public class Main {
public static void main(String[] args) throws Exception {
// 两个CompletableFuture执行异步查询:
CompletableFuture<String> cfQueryFromSina = CompletableFuture.supplyAsync(() -> {
return queryCode("中国石油", "https://finance.sina.com.cn/code/");
});
CompletableFuture<String> cfQueryFrom163 = CompletableFuture.supplyAsync(() -> {
return queryCode("中国石油", "https://money.163.com/code/");
});
// 用anyOf合并为一个新的CompletableFuture:
CompletableFuture<Object> cfQuery = CompletableFuture.anyOf(cfQueryFromSina, cfQueryFrom163);
// 两个CompletableFuture执行异步查询:
CompletableFuture<Double> cfFetchFromSina = cfQuery.thenApplyAsync((code) -> {
return fetchPrice((String) code, "https://finance.sina.com.cn/price/");
});
CompletableFuture<Double> cfFetchFrom163 = cfQuery.thenApplyAsync((code) -> {
return fetchPrice((String) code, "https://money.163.com/price/");
});
// 用anyOf合并为一个新的CompletableFuture:
CompletableFuture<Object> cfFetch = CompletableFuture.anyOf(cfFetchFromSina, cfFetchFrom163);
// 最终结果:
cfFetch.thenAccept((result) -> {
System.out.println("price: " + result);
});
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
Thread.sleep(200);
}
static String queryCode(String name, String url) {
System.out.println("query code from " + url + "...");
try {
Thread.sleep((long) (Math.random() * 100));
} catch (InterruptedException e) {
}
return "601857";
}
static Double fetchPrice(String code, String url) {
System.out.println("query price from " + url + "...");
try {
Thread.sleep((long) (Math.random() * 100));
} catch (InterruptedException e) {
}
return 5 + Math.random() * 20;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lQYk4dFn-1596633696969)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200802172418198.png)]
除了anyOf()
可以实现“任意个CompletableFuture
只要一个成功”,allOf()
可以实现“所有CompletableFuture
都必须成功”,这些组合操作可以实现非常复杂的异步流程控制。最后我们注意CompletableFuture
的命名规则:
-
xxx()
:表示该方法将继续在已有的线程中执行; -
xxxAsync()
:表示将异步在线程池中执行。
CompletableFuture
可以指定异步处理流程:
thenAccept()
处理正常结果;exceptional()
处理异常结果;thenApplyAsync()
用于串行化另一个CompletableFuture
;anyOf()
和allOf()
用于并行化多个CompletableFuture
。
15、JMM
请你谈谈对对volatile的理解
volatile是jvm提供的轻量级的同步机制。
1、保证可见性
2、不保证原子性
3、防止指令重排
什么是JMM
JMM java内存模型,不存在的东西,概念!约定!
关于JMM一些同步的约定
1、线程解锁前,必须吧共享变量立刻刷回主存。
2、线程加锁前,必须把主存中的最新值读到工作内存中去。
3、加锁和解锁是同一把锁。
线程 工作内存 主内存
8种操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ifsFJkc-1596633696970)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200802182117778.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fg4qpOO9-1596633696971)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200802182400525.png)]
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
-
- lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
-
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
问题:线程不知道主内存中的值发生了变化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g19DPUC2-1596633696972)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200802183802282.png)]
16、Volatile
保证可见性
package cn.smallcosmos.volatile1;
import java.util.concurrent.TimeUnit;
public class TestJMM {
static volatile int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> { //如果不加volatile,线程1对于主内存中的num变化感知不到,会造成死循环,程序无法结束
while(num == 0){
}
}).start();
TimeUnit.SECONDS.sleep(3);
num = 1;
System.out.println(num);
}
}
不保证原子性
原子性:不可分割
package cn.smallcosmos.volatile1;
public class TestJMM02 {
//volatile不保证原子性
private volatile static int num = 0;
public static void add(){
num++; //不是原子性操作
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 2000; j++) {
add();
}
}).start();
}
while(Thread.activeCount() > 2){ //main GC
Thread.yield();
}
System.out.println(num);
}
}
运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SiXsRJRW-1596633696973)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200802190303693.png)]
字节码文件(javap -c TestJMM02)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ipyN6hdf-1596633696974)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200802190058234.png)]
原子类:使用原子类解决原子性问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwP2lUoZ-1596633696975)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200803202029125.png)]
package cn.smallcosmos.volatile1;
import java.util.concurrent.atomic.AtomicInteger;
public class TestJMM02 {
private static AtomicInteger num = new AtomicInteger();
public static void add(){
// num++; //不是原子性操作
num.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 2000; j++) {
add();
}
}).start();
}
while(Thread.activeCount() > 2){ //main GC
Thread.yield();
}
System.out.println(num);
}
}
运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-abLFtEfh-1596633696976)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200803202835338.png)]
这些类的底层都是和操作系统挂钩!在内存中修改值!unsafe类是一个很特殊的存在
什么是指令重排
你写的程序,计算机并不是按照你写的那样执行的。
源代码 --》编译器优化的重排–》指令并行也可能会重排–》内存系统也会重排–》执行
计算机在进行指令重排是考虑:数据间的依赖性
int x = 1;//1
int y = 2;//2
x = x + 1;//3
y = x * x;//4
我们所期望的执行顺序 1234 ,但是可能执行的顺序是 1324 2134
可不可能是4123!
可能造成影响的结果 ab xy 初始值都是0
线程A | 线程B |
---|---|
x = a | y = b |
b = 1 | a = 2 |
线程A | 线程B |
---|---|
b = 1 | a = 2 |
x = a | y = b |
期望的结果x = 0;y = 0;
指令重排可能会造成的结果:x = 2;y = 1;
volatile 可以避免指令重排
内存屏障。CPU指令。作用:
1、保证特定的操作的执行顺序
可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
17、彻底玩转单例模式
饿汉式、DCL懒汉式,深究!
饿汉式
package cn.smallcosmos.singleton;
//饿汉式单例模式
public class Demo01 {
private Demo01(){
System.out.println(Thread.currentThread().getName());
}
//在类加载的时候进行初始化,可能会浪费内存空间
private final static Demo01 demo01 = new Demo01();
public static Demo01 getDemo01(){
return demo01;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
Demo01.getDemo01();
}).start();
}
}
}
懒汉式
package cn.smallcosmos.singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
//懒汉式单例模式
public class Demo02 {
private static boolean zzl = false;
private Demo02(){
// System.out.println(Thread.currentThread().getName());
// synchronized (Demo02.class){
// if(demo02 != null){
// throw new RuntimeException("不要试图通过反射来进行破坏");
// }
// }
if(zzl == false){
zzl = true;
}else{
throw new RuntimeException("不要试图通过反射来进行破坏");
}
}
private volatile static Demo02 demo02 = null;
//双重检验锁模式的懒汉单例模式
/**
* 1、分配内存空间
* 2、执行构造方法,实例化对象
* 3、把这个引用指向这个空间
*
*
* 期望执行顺序 123
* 可能执行顺序 132 A
* B 在A还没完成构造的时候,这时候B判断当前实例不为空,造成问题;
* 使用volatile禁止指令重排来解决。
*/
public static Demo02 getDemo02(){
if(demo02 == null){
synchronized (Demo02.class){
if(demo02 == null){
demo02 = new Demo02();//不是一个原子性操作
}
}
}
return demo02;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// for (int i = 0; i < 10; i++) {
//
// new Thread(() -> {
// Demo02.getDemo02();
// }).start();
// }
// Demo02 demo02 = Demo02.getDemo02();
// System.out.println(demo02);
//使用反射暴力破解单例模式
Field zzl = Demo02.class.getDeclaredField("zzl");
zzl.setAccessible(true);
Constructor<Demo02> constructor = Demo02.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
Demo02 instance = constructor.newInstance();
zzl.set(instance,false);
Demo02 instance2 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
枚举实现单例模式
package cn.smallcosmos.singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
//枚举实现单例模式
//枚举是不能被反射的,所以枚举才是实现单例模式的最佳方式
public enum EnumSingleton {
SINGLETON;
public static EnumSingleton getInstance(){
return SINGLETON;
}
}
class test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingleton instance = EnumSingleton.SINGLETON;
// EnumSingleton instance2 = EnumSingleton.SINGLETON;
System.out.println(instance);
// System.out.println(instance2);
/**
* 报错java.lang.NoSuchMethodException: cn.smallcosmos.singleton.EnumSingleton.<init>()
* 通过反编译可以看到枚举是没有空参构造方法的,有一个有参构造(String,int),IDEA和javap反编译欺骗了我们。
*/
// Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(null);
/**
* 报错java.lang.IllegalArgumentException: Cannot reflectively create enum objects
* 这个才是我们希望看到的结果
*/
Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
EnumSingleton instance1 = constructor.newInstance();
System.out.println(instance1);
}
}
javap 反编译结果(这个结果是不正确的)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pmJnVI8w-1596633696976)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200803222613302.png)]
通过jad反编译得到的结果
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingleton.java
package cn.smallcosmos.singleton;
public final class EnumSingleton extends Enum
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(cn/smallcosmos/singleton/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
public static EnumSingleton getInstance()
{
return SINGLETON;
}
public static final EnumSingleton SINGLETON;
private static final EnumSingleton $VALUES[];
static
{
SINGLETON = new EnumSingleton("SINGLETON", 0);
$VALUES = (new EnumSingleton[] {
SINGLETON
});
}
}
18、深入理解CAS
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ut3cSwVw-1596633696977)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200804202300183.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Baf4jVWZ-1596633696978)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200804202320439.png)]
ABA问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QHf8EPZS-1596633696979)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200804202358297.png)]
19、原子引用(解决ABA问题)
20、各种锁
公平锁 非公平锁
公平锁:非常公平,先来后到
非公平锁:非常不公平,可以插队(默认都是非公平锁)
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可重入锁
可重入锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JmKF5EyE-1596633696980)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200804211831917.png)]
自旋锁
package cn.smallcosmos.lock;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
//自定义自旋锁
public class SpinLockDemo {
private Thread thread = Thread.currentThread();
private AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock(){
System.out.println(Thread.currentThread().getName()+"-->" + "myLock");
while(!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void myUnLock(){
System.out.println(Thread.currentThread().getName() + "-->" + "myUnLock");
atomicReference.compareAndSet(thread,null);
}
public static void main(String[] args) throws InterruptedException {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(() -> {
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
} finally {
spinLockDemo.myUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(2);
new Thread(() -> {
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e) {
e.printStackTrace();
} finally {
spinLockDemo.myUnLock();
}
},"T2").start();
}
}
死锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lttobSJc-1596633696981)(C:\Users\11915\AppData\Roaming\Typora\typora-user-images\image-20200805211751366.png)]
package cn.smallcosmos.lock;
import java.util.concurrent.TimeUnit;
public class DeadLock {
public static void main(String[] args) {
Resource resource = new Resource("lockA","lockB");
new Thread(() -> {
try {
resource.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(() -> {
resource.del();
},"T2").start();
}
}
class Resource{
int a = 0;
int b = 1;
String lockA;
String lockB;
public Resource(String lockA,String lockB){
this.lockA = lockA;
this.lockB = lockB;
}
public void add() throws InterruptedException {
synchronized (lockA){
System.out.println(Thread.currentThread().getName() + "获取到" + lockA + "准备获取"+lockB);
a++;
System.out.println("a=" + a);
TimeUnit.SECONDS.sleep(3); //保证在第一个线程还没有执行完第二个线程进入
synchronized (lockB){
b--;
System.out.println("b=" + b);
}
}
}
public void del() {
synchronized (lockB){
System.out.println(Thread.currentThread().getName() + "获取到" + lockB + "准备获取"+lockA);
b--;
System.out.println("b=" + b);
synchronized (lockA){
a++;
System.out.println("a=" + a);
}
}
}
}
在获取多个锁的时候,不同线程获取多个不同对象的锁可能导致死锁。对于上述代码,线程T1和线程T2如果分别执行add()
和dec()
方法时:
-
线程T1:进入
add()
,获得lockA
; -
线程T2:进入
dec()
,获得lockB
。 -
线程T1:准备获得
lockB
,失败,等待中; -
线程T2:准备获得
lockA
,失败,等待中。
此时,两个线程各自持有不同的锁,然后各自试图获取对方手里的锁,造成了双方无限等待下去,这就是死锁。
死锁发生后,没有任何机制能解除死锁,只能强制结束JVM进程。
因此,在编写多线程应用时,要特别注意防止死锁。因为死锁一旦形成,就只能强制结束进程。
那么我们应该如何避免死锁呢?答案是:线程获取锁的顺序要一致。即严格按照先获取lockA
,再获取lockB
的顺序.