多线程拓展-JUC
JUC并发编程
什么是JUC
java.util工具包
线程和进程
进程:一个程序,QQ.exe MuSic.exe 程序的集合
一个进程可以包含多个线程,至少包含一个!
Java默认有几个线程? 两个:main(内核线程),GC(守护线程,也叫垃圾回收线程)
线程:开了一个进程例如Typora,写字,保存等(由线程负责)
对于java而言:Thread,Runnable,Callable
Java可以开启线程吗? 不行,线程的开启是通过调用本地方法status0开启的,Java是没有权限开启的
并发编程:并发、并行
并发:多个线程操作同一个资源
CPU一核,模拟出来多条线程,线程快速交替
并行:类似于多个人一起行走
CPU多核,多个线程可以同时执行,线程池
并发变成本质:充分利用CPU的资源
线程状态
线程的六大状态
创建(NEW),运行(RUNNABLE),阻塞(WAIT),等待(waiting),超时等待(TimedWaitting),死亡(TERMINATED()
wait/sleep的区别
1.属于不同的类
wait属于===>Object类
sleep属于==>Thread类
2.关于锁的释放
wait 会释放锁,sleep睡觉了,抱着锁睡觉,不会释放!
3.使用的范围是不同的
wait必须在同步代码块中,sleep可以在任何地方睡
Lock锁
传统的synchronized
通过利用同步锁把多个线程进行排队,防止线程争抢同一资源
Lock
公平锁:根据顺序
非公平锁:可以插队
Lock代码中的三部曲:
1.new ReentrantLock()
2.Lock.lock(); //加锁
3.finally=> Lock.unlock();解锁
Synchronized 和 Lock 的区别
1.Synchronized 是内置的Java关键字,Lock是一个Java类
2.Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
3.Synchronized 会自动释放锁,lock必须手动释放锁!如果不释放锁,就会发生死锁
4.Synchronized 多线程情况下,线程1(获得锁,阻塞)、线程2(等待,一直等待);lock锁就不一定等待下去
5.Synchronized 可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置);
6.Synchronized 适合少量的代码同步问题,Lock适合大量的同步代码!
线程中消费者和生产的问题
package com.juc;
/**
* 线程之间的通信问题:生产者和消费者问题!!!! 实现方式:等待唤醒,通知唤醒
* 通过线程交替 A B 操作同一个变量 num = 0
* A = num + 1
* B = num - 1
* */
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
},"B").start();
}
}
// 判断等待 业务 通知
class Data{ //数字 资源类
private int number = 0;
//+1
public synchronized void increment(){
if(number == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
//通知其他线程,我+1完毕了
System.out.println(Thread.currentThread().getName() +"->" +number);
this.notifyAll();
}
//-1
public synchronized void decrement(){
if (number == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
System.out.println(Thread.currentThread().getName() +"->" +number);
//通知其他线程,我-1完毕了
this.notifyAll();
}
}
JUC版的生产者和消费者
package com.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//等待、业务、通知
class Data2{ //数字资源类
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// condition.await();
// condition.signalAll();
//+1
public synchronized void increment() throws InterruptedException{
lock.lock();
try {
while (number != 0) {
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// this.notifyAll();
condition.signalAll();
//通知其他线程,我+1完毕了
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
//-1
public synchronized void decrement() throws InterruptedException{
lock.lock();
try {
while(number ==0){
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>" + number);
//通知其他线程,我-1完毕了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,优势和补充
Condition 精准的通知和唤醒线程
package com.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.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 ;
// 唤醒,唤醒指定的 B
condition2.signal();
System.out.println(Thread.currentThread().getName() + "=AAAAAAAAAAAA");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try { //等待
while (number != 2){
condition2.await();
}
number =3 ;
// 唤醒,唤醒指定的 C
condition3.signal();
System.out.println(Thread.currentThread().getName() + "=>BBBBBBBBBBBBB");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try { //等待
while (number != 3){
condition3.await();
}
number =1 ;
// 唤醒,唤醒指定的 A
condition1.signal();
System.out.println(Thread.currentThread().getName() + "=>CCCCCCCCCCCCC");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
八锁现象
package com.juc.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁,就是关于锁的8个问题
* 1.标准情况下,两个线程先打印 发短信 还是 打电话? 1/发短信 2/打电话
* 2.可以通过延迟4秒,两个线程先打印 发短信 还是 打电话?
* 3.两个对象,两个同步方法
* 4.增加两个静态的同步方法,只有一个对象
* 5.一个静态方法,一个普通方法
* */
public class Test4 {
public static void main(String[] args) {
// 两个对象,两个调用者
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
//锁的存在
new Thread(()->{
phone1.sendSMs();
},"A").start();
//捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone1.call();
},"B").start();
new Thread(()->{
phone2.hello();
},"C").start();
}
}
class Phone4{
//synchronized 锁的对象是方法的调用者
//两个方法用的是同一个锁,谁先拿到谁执行
//类一加载就有了,锁的是class,意思就是两个方法用的是同一个锁
//静态的同步方法,锁的的是class类模板
public static synchronized void sendSMs(){
//捕获
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("你好");
}}
总结
-
synchronized 锁的对象是方法的调用者
两个方法用的是同一个锁,谁先拿到谁执行 -
增加两个静态的同步方法,只有一个对象
静态的同步方法,锁的的是class类模板 -
类锁和对象锁的区别:
如果多线程同时访问同一类的 类锁(synchronized 修饰的静态方法)以及对象锁(synchronized 修饰的非静态方法)这两个方法执行是异步的,原因:类锁和对象锁是2中不同的锁。
类锁对该类的所有对象都能起作用,而对象锁不能。
四大函数式接口
集合类不安全
List不安全
package com.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
//java.util.ConcurrentModificationException 并发修改异常!
public class TestList {
public static void main(String[] args) {
// List<String> list = Arrays.asList("1","2","3");
// list.forEach(System.out::println);
// 并发下ArrayList 不安全的
/**
* 解决方案:
* 1、List<String> list = new Vector<>();
* 2、List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3.List<String> list =new CopyOnWriteArrayList<>();
* //CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略
* //在写入的时候避免覆盖,造成数据问题
* //读写分离
* //CopyOnWrite 相比于 Vetor 好在那里??
* CopyOnWrite(Lock锁) 和 Vetor(synchronized效率比较低)
*
* */
List<String> list = new Vector<>();
c
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 com.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* java.util.ConcurrentModificationException
*
* 解决方案:
* 1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
* 2. Set<String> set = new CopyOnWriteArraySet<>();
*/
public class TestSet {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>();
Set<String> set = Collections.synchronizedSet(new HashSet<>());
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();
}
}
}
HashSet的底层是什么?
hashset的底层是hashmap
public HashSet() {
map = new HashMap<>();
}
//add set 本质就是 map key是无法重复的!
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//不变的值
private static final Object PRESENT = new Object();
Map不安全
map默认等价于什么??
加载因子(通常情况下:0.75f ),加载容量(通常情况下:16 )
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName() , UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
Callable
1.可以有返回值
2.可以抛出异常
3.方法不同,run()/call()
4.支持泛型,泛型的参数是方法的返回值
package com.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread().start(); //怎么启动callable
MyThread myThread = new MyThread();
//适配类
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask,"A").start();
//获取callable的返回结果
Integer o = (Integer) futureTask.get();
System.out.println(o);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() {
System.out.println("call()");
return 1024;
}
}
小细节
1.有缓存
2.结果可能需要等待,会阻塞!
常用的辅助类(需要掌握)
CountDownLatch、CyclisBarrier、Semaphore
CountDownLatch
CountDownLatch: 是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。
package com.asist;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
// 线程减法计数器
public static void main(String[] args) throws InterruptedException{
// 总数是6,必须要执行任务的时候,再使用!
CountDownLatch countDownLatch = new CountDownLatch (6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"Go Out ");
countDownLatch.countDown();//数量减1
},String.valueOf(i)).start();
}
//等待计数器归零再往下执行
countDownLatch.await();
System.out.println("close door");
}
}
countDownLatch.countDown(); 数量减1
countDownLatch.await(); 等待计数器归零再往下执行
每次有线程调用()数量-1,假设计数器变为0,countDownLatch.await()就会被唤醒,继续执行!
CyclisBarrier
CyclicBarrier:是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。CyclicBarrier是通过ReentrantLock(独占锁)和Condition来实现的
package com.asist;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
/**
* 集齐7颗龙珠
* 召唤神龙
* */
CyclicBarrier cyclicBarrier =new CyclicBarrier(8,()->{
System.out.println("召唤神龙成功!");
});
for (int i = 0; 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();
}
}).start();
}
}
}
CyclicBarrier 与 CountDownLatch 区别
- CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
- CountDownLatch 参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。CyclicBarrier 参与的线程职责是一样的。
Semaphore
- 一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire都会阻塞,直到许可证可用,然后才能使用它。 每个release添加许可证,潜在地释放阻塞获取方。 但是,没有使用实际的许可证对象;
Semaphore
只保留可用数量的计数,并相应地执行。
package com.asist;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SeamPhoreDemo {
public static void main(String[] args) {
//线程数量
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(()->{
//aquire() 得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
//release()释放
},String.valueOf(i)).start();
}
}
}
semaphore.acquire(); 获得,假设如果已经满了,等待,等待被释放为止!
semaphore.release();释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用:多个共享资源的互斥作用!并发限流,控制最大的线程数!
读写锁(ReadWriteLock)
-
所有
ReadWriteLock
实现必须保证的存储器同步效应writeLock
操作如在指定Lock接口也保持相对于所述相关联的readLock
。 也就是说,一个线程成功获取读锁定将会看到在之前发布的写锁定所做的所有更新。读写锁允许访问共享数据时的并发性高于互斥锁所允许的并发性。 它利用了这样一个事实:一次只有一个线程( 写入线程)可以修改共享数据,在许多情况下,任何数量的线程都可以同时读取数据(因此读取器线程)。 从理论上讲,通过使用读写锁允许的并发性增加将导致性能改进超过使用互斥锁。 实际上,并发性的增加只能在多处理器上完全实现,然后只有在共享数据的访问模式是合适的时才可以
package com.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReadWriteLock
* 独占锁(写锁,一次只能被一个线程占有)
* 共享锁(读锁,多个线程可以同时占有)
* 读-读 可以共存!
* 读-写 不可共存!
* 写-写 不可共存!
* */
public class ReadWriteLockDemo {
public static void main(String[] args) {
MaCacheLock maCache = new MaCacheLock();
for (int i = 1; i <= 5; i++) {
//写入
final int temp = i ;
new Thread(()->{
maCache.put(temp + "" , temp +"");
},String.valueOf(i)).start();
}
//读取
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(()->{
maCache.get(temp + "");
},String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存
* */
class MaCacheLock{
private volatile Map<String,Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//存,写入的时候,只希望同时只有一个线程写
public void put(String key, Object value){
readWriteLock.writeLock().lock();
try { System.out.println(Thread.currentThread().getName() + "写入" + key);
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() + "读取"+key);
Object o = map.get(key); System.out.println(Thread.currentThread().getName() + "读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
小结:
在读写锁的线程中,put方法必须通过 readWriteLock.writeLock().lock();等方法进行加锁,防止在写入过程中其他线程插入进程扰乱顺序;
在get方法中,读锁可以多个共存
阻塞队列(BlockingQueue)
FIFO(First input First Output)
写入:如果队列满了,就必须阻塞等待
取出:如果队列空的,必须阻塞等待生产
阻塞队列
什么情况使用阻塞队列:1.多线程,线程池!
学会使用队列:添加,移除
四组API
方式 | 抛出异常 | 有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(,,) |
移除 | remove() | poll() | take() | poll(,) |
判断队列首 | element() | peek() | - | - |
package com.bq;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) throws InterruptedException {
// test1();
// test2();
// test3();
test4();
}
/**
* 抛出异常
* */
public static void test1(){
//有返回值,无异常
//队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//java.lang.IllegalStateException: Queue full 抛出异常!
//System.out.println(blockingQueue.add("d"));
//检测队首元素
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
// java.util.NoSuchElementException 抛出异常!
// System.out.println(blockingQueue.remove());
}
/**
* 有返回值,没有异常
* */
public static void test2(){
//有返回值,无异常
// 队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
//false
System.out.println(blockingQueue.offer("d"));
//检测队首元素
System.out.println(blockingQueue.peek());
System.out.println("======================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//null
System.out.println(blockingQueue.poll());
}
/**
* 等待阻塞(一直阻塞)
* */
public static void test3() throws InterruptedException {
//队列大小
ArrayBlockingQueue 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());
}
/**
*
等待阻塞(阻塞超时)
* */
public static void test4() throws InterruptedException {
//队列大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
//等待两秒超时退出
blockingQueue.offer("d",2, TimeUnit.SECONDS);
System.out.println("===================================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//等待超过两秒退出
blockingQueue.poll(2,TimeUnit.SECONDS);
}
}
SynchronousQueue 同步队列
没有容量
进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
put、take
package com.bq;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 同步队列
* 和其他的BLockingQueue不一样,SynchronousQueue 不存储原始
* put了一个元素,必须从里面take取出来,否则不能在put进去值!
* */
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue blockingQueue = new SynchronousQueue();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+" put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+" put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
线程池
线程池有哪些?
newfixedthreadpool
newcachedthreadpool
newsinglethreadexecutor
newScheduledThreadPool
池化技术
程序运行,本质:占用系统的资源!优化资源的使用!=>池化技术
池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后还回去去
线程池的好处:
1、降低资源的消耗
2、提高响应的速度
3、方便管理
线程复、可以控制最大并发数、管理线程
线程3大方法
package com.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//Executors 工具类、3大方法.
//使用线程池之后,使用线程池创建线程
public class Demo01 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单个线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok ");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
7大参数
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//本质
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
手动创建线程池
package com.pool;
import java.util.concurrent.*;
//Executors 工具类、3大方法.
//使用线程池之后,使用线程池创建线程
public class Demo01 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单个线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
// ExecutorService threadPool = Executors.newCachedThreadPool();
ExecutorService threadPool = new ThreadPoolExecutor(
2, //核心大小
5, //最大线程数
3, //空闲线程存活时间
TimeUnit.SECONDS, //空闲线程存活时间单位
//工作队列
new LinkedBlockingQueue<>(3), //按照FIFO排序
Executors.defaultThreadFactory(), // 线程工厂,用来设定线程名、是否为daemon线程
new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略
try {
//最大承载:Queue + max
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok ");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池关闭
threadPool.shutdown();
}
}
}
4种拒绝策略
4大拒绝策略
* new ThreadPoolExecutor.AbortPolicy() //线程满了,还有新的进来就弃之,抛出异常
-
new ThreadPoolExecutor.CallerRunsPolicy() //从哪里来回哪里去
-
new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
-
new ThreadPoolExecutor.DiscardOldestPolicy //队列满了,尝试去和最早的竞争,也不会抛出异常
小结
池的最大的大小如何去设置:
了解:IO密集型,CPU密集型:(调优)
//最大线程如何定义
System.out.println(Runtime.getRuntime().availableProcessors());
// 1、CPU 密集型 ,几核就定义为几可以保证CPU效率最高
// 2、IO 密集型 > 判断程序中十分耗IO得线程,
//程序 15个大型任务 io十分占用资源
四大函数型接口
1.Function<T, R>函数型接口
2.Predicate断定型接口
3.Consumer消费型接口
4.Supplier供给型接口
function接口
代码测试
package com.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 str) {
// return str;
// }
// };
Function<String,String> function =(str)->{
return str ;
};
System.out.println(function.apply("asd"));
}
}
Predicate断定型接口
代码测试 断定型接口:有一个输入参数,返回值只能是布值!
package com.function;
import java.util.function.Predicate;
/**
* 断定型接口:有一个输入参数,返回值只能是布尔值!
*
* */
public class PredicateDemo {
public static void main(String[] args) {
//判断字符串是否为空?
// Predicate<String> predicate = new Predicate<String>() {
// @Override
// public boolean test(String str) {
//
// return str.isEmpty();
// }
// };
Predicate<String> predicate = (str)->{return str.isEmpty();};
System.out.println(predicate.test("asd"));
}
}
Consumer消费型接口
代码测试
package com.function;
import java.util.function.Consumer;
/**
* 消费性接口:只有输入,没有返回值
* */
public class ConsumerDemo {
public static void main(String[] args) {
Consumer<String> consumer= (str)->{ System.out.println(str); };
consumer.accept("abcdefg");
}
}
Supplier供给型接口
代码测试
package com.function;
import java.util.function.Supplier;
/**
* 供给型接口 :没有输入,有返回
* */
public class SupplierDemo {
public static void main(String[] args) {
// Supplier supplier = new Supplier<Integer>(){
// @Override
// public Integer get() {
// return 1024;
// }
// };
Supplier supplier = ()->{return 1024;};
System.out.println(supplier.get());
}
}
Stream流式计算
什么是Stream流式计算
大数据:存储 + 计算
集合、Mysql本质就是存储东西的;
计算都应该交给流来操作!
代码示例
javapackage com.stream;
import java.util.Arrays;
import java.util.List;
/**
* 题目要求:一分钟内完成此题,只能用一行代码实现!
* 现在有5个用户!筛选:
* 1、ID 必须是偶数
* 2、年龄必须大于23岁
* 3、用户名转为大写字母
* 4、用户名字倒着排序
* 5、只输出一个用户!
*
* */
public class Test1 {
public static void main(String[] args) {
User u1 = new User(1,"a",21);
User u2 = new User(1,"b",22);
User u3 = new User(1,"c",23);
User u4 = new User(1,"d",24);
User u5 = new User(1,"e",25);
//集合就是存储
List<User> list = Arrays.asList(u1,u2,u3,u4,u5);
//计算交给Stream流
//lambda表达式、链式编程、函数式接口、Stream流式计算
list.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);
}
}
ForkJoin
计算案例
package com.forkjoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test2();
}
public static void test1(){
Long sum = 0L;
long start = System.currentTimeMillis();
for (Long i = 1L; i < 10_0000_0000; 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> task = new ForkJoinDemo(0L,10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum => " + sum + " 时间:"+ (end -start));
}
}
使用并行流
public static void test3() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
//并行流
long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0,Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum => " + sum + " 时间:"+ (end -start));
}
异步回调
package com.future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* 异步调用:CompletableFuture
* //异步执行
* //成功回调
* //失败回调
* */
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回值的 runAsync 异步回调
// CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
//
// });
// System.out.println("1111");
// completableFuture.get();//获取阻塞执行结果
//有返回值的 supplyAsync 异步回调
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync => Integer");
int i = 10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t=>" + t); //正常的返回结果
System.out.println("u=>" + u);
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 233;
}).get());
/**
* success code 200
* error code 500 404
* */
}
}
JMM
Volatile是Java虚拟机的轻量级同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
什么是JMM
JMM:Java内存模型,不存在的东西,概念!约定!
关于JMM的一些同步约定:
1、线程解锁前,必须把共享变量立刻刷回主存
2、线程加锁前,必须读取主存种的最新值到工作内存中
3、加锁和解锁是同一把锁
JMM即为JAVA 内存模型(java memory model)。因为在不同的硬件生产商和不同的操作系统下,内存的访问逻辑有一定的差异,结果就是当你的代码在某个系统环境下运行良好,并且线程安全,但是换了个系统就出现各种问题。Java内存模型,就是为了屏蔽系统和硬件的差异,让一套代码在不同平台下能到达相同的访问结果。JMM从java 5开始的JSR-133发布后,已经成熟和完善起来
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
线程、工作内存、主内存
8种操作
若线程B修改了值,但是线程A不能及时可见
package com.tvolatile;
import java.util.concurrent.TimeUnit;
public class JMMDemo1 {
private static int num = 0;
public static void main(String[] args) {
new Thread(()->{
while (num == 0) {
}
}).start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
//解决此问题可以通过
private volatile static int num = 0;
Volatile
保证可见性
private volatile static int num = 0;
不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败
package com.tvolatile;
public class VDDemo2 {
//不保证原子性
private volatile static int num = 0;
public static void add(){
num++;
}
public static void main(String[] args) {
//理论上num结果的应该为 2 万
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) { //main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+""+ num);
}
}
解决办法:使用原子类处理原子性问题(Atomic)
这些类的底层直接喝操作系统挂钩!在内存中修改值!Unsafe类是个很特殊的存在!
package com.tvolatile;
import java.util.concurrent.atomic.AtomicInteger;
public class VDDemo2 {
//不保证原子性
//原子类的 Integer
private volatile static AtomicInteger num = new AtomicInteger() ;
public static void add(){
// num++; //不是一个原子性的操作
num.getAndIncrement(); // AtomicInteger + 1 方法, CAS
}
public static void main(String[] args) {
//理论上num结果的应该为 2 万
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) { //main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+""+ num);
}
}
指令重排
什么是 指令重排:你写的程序,计算机并不是按照你写的那样去执行的。
源代码-->编译器优化的重排-->指令并行也可能会重排-->内存系统也会重排-->执行
int x = 1;
int y = 2;
x = x + 5;
y = x + x;
我们所期望的:1234 但是可能执行的时候会变成 2134 1324
可不可能是 4123
可能造成影响的结果
线程A | 线程B |
---|---|
x = a | y = b |
b = 1 | a = 2 |
正常结果: x = 0 , y = 0;但是可能指令重排
线程A | 线程B |
---|---|
b = a | a = 2 |
x = a | y = b |
指令重排导致的诡异结果: x = 2; y = 1 ;
Volatile可以避免指令重排:
内存屏障, CPU 指令。作用:
1、 保证特定的顺序!
2、可以保证某些变量的内存可见性(利用Volatile实现了可见性)
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
深入了解CAS
Unsafe类
unsafe底层原理
CAS:比较当前工作内存的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!
缺点:
1、循环耗时
2、一次性只能保证一个共享变量的原子性
3、ABA问题
CAS: ABA问题(狸猫换太子)
深入了解ABA问题
原子问题
带版本号的原子操作
解决ABA问题,引入原子引用 对应的思想就是:“乐观锁”
注意事项:
Integer使用了对象缓存机制,范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存机制,而new一定会创建新的对象分配新的内存空间;
package com.cas;
import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CASDemo {
// CAS compartAndSet : 比较并交换!
//对于我们的sql:乐观锁!
public static void main(String[] args) {
// AtomicStampedReference 注意:如果泛型 是一个包装类,注意对象的引用问题
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("a1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1);
System.out.println("a2=>" + stamp);
System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1));
System.out.println("a3=>" + atomicStampedReference.getStamp());
},"a").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("b1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp,
stamp + 1));
System.out.println("b1=>" + stamp);
},"b").start();
}
}
自旋锁
spinlock
package com.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
// ReentrantLock reentrantLock = new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
//底层使用的自旋锁CAS
SpinLockDemo lock = new SpinLockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.myUnlock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.myUnlock();
}
},"T2").start();
}
}
package com.lock;
import java.util.concurrent.atomic.AtomicReference;
//自旋锁
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName());
//自旋锁
while (!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void myUnlock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==>myUnlock");
atomicReference.compareAndSet(thread,null);
}
}
死锁
package com.deadlock;
import java.util.concurrent.TimeUnit;
public class DeadLockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA,lockB),"T1").start();
new Thread(new MyThread(lockB,lockA),"T2").start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized(lockA){
System.out.println(Thread.currentThread().getName() + "lock:" + lockA +"=>get" + lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"lock:" + lockB + "=>get" + lockA);
}
}
}
}
解决死锁问题
1、使用jps-l
定位进程号
2、使用jstack
进程号
面试工作中!排查问题:
1、日志(ELK日志等)
2、堆栈信息
Brinkley, Alan. The Unfinished Nation. New York: Knopf, 1993.