JUC并发编程
1、什么是JUC
- JUC的意思就是java并发编程工具包,与JUC相关的有三个包:java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks。实现多线程有三种方式:Thread、Runnable、Callable,其中Callable就位于concurrent包下
2、进程/线程
-
进程:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
-
线程:通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的程度。
-
进程的例子:一个程序,QQ.exe,Music.exe等程序的集合
-
一个进程往往可以包含多个线程,至少包含一个!
-
Java默认有两个线程(main,GC)
-
GC的基本原理: 1. 对于程序员来说,用new关键字即在堆中分配了内存,我们称之为“可达”。对于GC来说,只要所有被引用的对象为null时,我们称之为“不可达”,就将进行内存的回收。 2. 当一个对象被创建时,GC开始监控这个对象的大小、内存地址及使用情况。GC采用有向图的方式记录和管理堆(heap)中的所有对象,通过这种方式可以明确哪些对象是可达的,哪些不是。当确定为不可达时,则对其进行回收。 3. 保证GC在不同平台的实现问题,java规范对其很多行为没有进行严格的规定。对于采用什么算法,什么时候进行回收等。
-
-
线程的例子:如在使用word的时候突然电脑关机,重新开机的时候word会重新恢复当前的数据
-
对于Java而已实现多线程有三种方式:Thread、Runnable、Callable。
-
但java是开不了线程的!!!
-
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();
并发、并行
并发编程:并发、并行
1、并发(多线程操作同一个资源)
- CPU一核,模拟出来多条线程,快速交替
2、并行(多个人一起行走)
- CPU多核,多个线程可以同时执行;线程池
-
package Demo01;
public class Test01 {
public static void main(String[] args) {
//获取CPU的核数
//CPU密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
3、并发编程的本质:充分利用CPU的资源
➢线程的状态:
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
//新生
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
//运行
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
//堵塞
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
//等待(一直处于等待状态)
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
//超时等待
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
//终止
TERMINATED;
}
➢wait/sleep的区别
1、来自不同的类
-
wait=>Object
-
sleep=>Thread
2、关于锁的释放
- wait会释放锁
- sleep不会释放锁
3、使用的范围是不同的
- wait必须在同步代码块中
- sleep可以在任意地方
4、是否需要捕获异常
- wait不需要捕获异常
- sleep必须要捕获异常
3、Lock锁(重点)
➢传统的Synchronized
代码示例:
package Demo01;
//基本的买票例子
/*
真正的多线程开发,在公司中的开发中,降低耦合性
线程就是一个单独的资源类,没有任何附属的操作!
1、包含属性、方法
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类放入线程
Ticket ticket=new Ticket();
//@FunctionalInterface 函数式接口 jdk1.8 lambda表达式 (参数)->{代码}
new Thread(()->{
for (int i = 1; i < 40; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 1; i < 40; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 1; i < 40; i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类 OOP
class Ticket{
//属性、方法
private int number=30;
//买票的方式
public void sale(){
if (number>=0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
}
}
}
➢Lock接口
➢公平锁:十分公平,可以先到后到
➢非公平锁:十分不公平,可以插队(默认是非公平锁)
代码示例:
package Demo01;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//基本的买票例子
/*
真正的多线程开发,在公司中的开发中,降低耦合性
线程就是一个单独的资源类,没有任何附属的操作!
1、包含属性、方法
*/
public class SaleTicketDemo02 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类放入线程
Ticket2 ticket=new Ticket2();
new Thread(()->{ for (int i = 1; i < 40; i++) ticket.sale(); },"A").start();
new Thread(()->{ for (int i = 1; i < 40; i++) ticket.sale(); },"B").start();
new Thread(()->{ for (int i = 1; i < 40; i++) ticket.sale(); },"C").start();
}
}
//Lock
//1. new ReentrantLock();
//2. lock.lock();//加锁
//3.finally=> lock.unlock();//解锁
class Ticket2{
//属性、方法
private int number=30;
Lock lock=new ReentrantLock();
//买票的方式
public void sale(){
lock.lock();//加锁
try {
if (number>=0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
}
➢Synchronized和Lock锁的区别
1、Synchronized 内置的java关键字,Lock 是一个类
2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
3、Synchronized 会自动释放锁,Lock必须要手动释放锁!如果不释放锁,会出现死锁情况
4、Synchronized 线程1(获得锁,堵塞)、线程2(等待,一直等待),Lock锁就不一定一直等待
5、Synchronized 可重入锁,不可以中断,非公平,Lock,可重入锁,可以判断锁,默认非公平(可自行设置)
6、Synchronized 适合锁少量的大妈同步问题,Lock适合锁大量的同步代码!
4、什么是锁?
5、生产者与消费者
面试:单例模式,排序算法,生产者与消费者,死锁
代码示例:
package PC;
/*
线程之间的通信问题:生产者与消费者问题! 等待唤醒,通知唤醒
线程交替执行 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.increament();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decreament();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//判断等待,业务,通知
class Data{ //数字 资源类
private int number=0;
//+1
public synchronized void increament() throws InterruptedException {
if(number!=0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,+1完毕
this.notifyAll();
}
//-1
public synchronized void decreament() throws InterruptedException {
if(number==0){
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,+1完毕
this.notifyAll();
}
}
问题存在:A、B、C、D四个线程!
这里将if改为while判断即可
代码示例:
package PC;
/*
线程之间的通信问题:生产者与消费者问题! 等待唤醒,通知唤醒
线程交替执行 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.increament();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decreament();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increament();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decreament();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//判断等待,业务,通知
class Data{ //数字 资源类
private int number=0;
//+1
public synchronized void increament() throws InterruptedException {
while(number!=0){
//等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,+1完毕
this.notifyAll();
}
//-1
public synchronized void decreament() throws InterruptedException {
while(number==0){
//等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//通知其他线程,+1完毕
this.notifyAll();
}
}
6、集合不安全
1、List不安全!
代码示例:
package unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
//java.util.ConcurrentModificationException 并发修改异常!
public class ListTest {
public static void main(String[] args) {
// List<String> list = Arrays.asList("1", "2", "3");
// list.forEach(System.out::println);
//并发的情况下 ArrayList是不安全 可以用Synchronized解决;
/*
解决方案:
1.List<String> list=new Vector<>();
2.List<String> list= Collections.synchronizedList(new ArrayList<>());
3. List<String> list=new CopyOnWriteArrayList<>();
*/
//CopyOnWrite 写入时复制 COW是计算机设计领域的一种优化策略
//多个线程调用的时候,list 读取的时候,固定的写入(覆盖)
//在写入的时候避免覆盖,造成数据问题
List<String> list=new CopyOnWriteArrayList<>();
for (int i = 1; i <=10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
2.、Set不安全!
代码示例:
package unsafe;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/*
ConcurrentModificationException 并发修改异常!
*/
public class SetTest {
public static void main(String[] args) {
// HashSet<String> set = new HashSet<>();
/*
解决方案:
1.Set<Object> set = Collections.synchronizedSet(new HashSet<>());
2. Set<Object> set = new CopyOnWriteArraySet<>();
*/
Set<Object> 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();
}
}
}
-
hashSet底层是什么?
public HashSet() { map = new HashMap<>(); } //add set 本质就是map kye是无法重复的! public boolean add(E e) { return map.put(e, PRESENT)==null; } private static final Object PRESENT = new Object(); //不变的值
3、HashMap不安全!
代码示例:
package unsafe;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/*
ConcurrentModificationException 并发异常!
*/
public class MapTest {
public static void main(String[] args) {
//map是这样用的吗? 不是,工作中不用HashMap
// 默认等价于什么?new HashMap<>(16,0.75);
// HashMap<String, String> map = new HashMap<>();
/*
解决方案:
1.Map<String,String> map = new ConcurrentHashMap<>();
*/
Map<String,String> map = new ConcurrentHashMap<>();
//加载因子,初始化容量
for (int i = 1; i <= 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
7、Callable
1.可以有返回值
2.可以抛出异常
3.方法不同
代码示例:
package Callabe;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CalableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new MyThread()).start();
// new Thread(new FutureTask<V>(Callable)).start();
//怎么启动Callable
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);//适配类
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();//结果会被缓存,提高效率
Integer o = (Integer) futureTask.get(); //获取Callable的返回结果
//这个get方法可能会产生堵塞!需把它放在最后 或者使用异步通信来处理!
System.out.println(o);
}
}
//class MyThread implements Runnable{
// @Override
// public void run() {
//
// }
//}
class MyThread implements Callable<Integer> {
@Override
public Integer call() {
System.out.println("call()");
return 1024;
}
}
8、常用的辅助类
8.1、CountDownLatch
减法计数器:
代码示例:
package add;
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 = 1; 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();//等待计数器归零,然后再向下执行
每次有线程调用countDown()数量-1,假设计数器变为0,countDownLatch.await()就会被唤醒,继续执行!
8.2、CyclicBarrier
加法计数器:
代码示例:
package add;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功!");
});
for (int i = 1; i <= 7; i++) {
final int temp=i;
//lambda不能直接拿到i
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
8.3、Semaphore
Semaphore:信号量
代码示例:
package add;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
//需要输入线程数量(停车位) 限流!
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try {
semaphore.acquire(); //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,然后唤醒等待的线程
- 作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!
9、读写锁
ReadWriteLock
代码示例:
package rwl;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*
独占锁(写锁) 一次只能被一个线程占有
共享锁(读锁) 多个线程可以同时占有
ReadWriteLock
读-读 可以共存!
读-写 不能共存!
写-写 不能共存!
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
//写入
for (int i = 1; i <= 5; i++) {
final int temp=i;
new Thread(()->{
myCache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
//读取
for (int i = 1; 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 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()+"写入成功");
} 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()+"读取成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
/*
自定义缓存
*/
class MyCache{
private volatile Map<String,Object> map=new HashMap<>();
//存,(写)
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入成功");
}
//取,(读)
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o=map.get(key);
System.out.println(Thread.currentThread().getName()+"读取成功");
}
}
10、堵塞队列
-
写入:如果队列满了,就必须堵塞等待
-
读取:如果是队列是空的,必须堵塞等待生产
-
什么时候会使用堵塞队列:多线程并发处理,线程池!
-
学会使用队列:添加、移除
-
四组API
方式 抛出异常 不会抛出异常(有返回值) 堵塞等待 超时等待 添加 add() offer() put() offer() 移除 remove() poll() take() poll() 检测队首元素 element peek() - - 1、抛出异常
代码示例:
package BlockingQueue; import java.util.concurrent.ArrayBlockingQueue; public class Test { public static void main(String[] args) { test1(); } /* 抛出异常 */ 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")); System.out.println(blockingQueue.element()); //查看队首元素 System.out.println("=============================================="); //IllegalStateException: Queue full 抛出异常 队列已满 //System.out.println(blockingQueue.add("d")); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); System.out.println(blockingQueue.remove()); //NoSuchElementException 抛出异常 队列中已没有元素 //System.out.println(blockingQueue.remove()); } }
2、不会抛出异常(有返回值)
代码示例:
package BlockingQueue; import java.util.concurrent.ArrayBlockingQueue; public class Test { public static void main(String[] args) { test2(); } /*{ 不会抛出异常(有返回值) */ 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")); System.out.println(blockingQueue.peek()); //取队首元素 System.out.println("============================"); //队列已满,不抛出异常,返回boolean值false! //System.out.println(blockingQueue.offer("d")); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); System.out.println(blockingQueue.poll()); //队列已空 不抛出异常,放回null System.out.println(blockingQueue.poll()); } }
3、堵塞等待
代码示例:
//等待,阻塞(一直阻塞) 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());//没有这个元素,一直堵塞 }
4、超时等待
代码示例:
//等待,阻塞(等待超时) 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)
- SynchronousQueue 同步队列
代码示例:
package BlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
//同步队列
//和其他的BlockingQueue 是不一样的! SynchronousQueue 不存储元素的
//SynchronousQueue put进去一个元素,必须从里面take一个元素出来 否则不能再put进去元素
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> 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(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();
}
}
11、线程池
线程池:三大方法、七大参数、四中拒绝策略
- 池化技术
- 程序的运行,本质:占用系统的资源!优化资源的使用!=>池化技术
- 线程池、连接池、内存池、对象池…(若一直创建,销毁,这样十分浪费)
- 池化技术:事先准备好一些资源,如要使用,来这个池里拿,用完之后返还
- 线程池的好处:
- 降低资源的消耗
- 提高响应的速度
- 方便管理
- 线程池的作用:线程的复用、可以控制最大并发数、管理线程
1、线程池的三大方法
代码示例:
package pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// Executor 工具类 有三大方法
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 < 10; i++) {
//new Thread().start();
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池使用完后需要关闭
threadPool.shutdown();
}
}
}
2.、线程池的七大参数
源码分析:
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, // Integer.MAX_VALUE,约等于21亿
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- 手动创建一个线程池
代码示例:
package pool;
import java.util.concurrent.*;
// Executor 工具类 有三大方法
/*
new ThreadPoolExecutor.AbortPolicy() //人数满了,还有人来,这是不处理这个人,抛出异常
new ThreadPoolExecutor.CallerRunsPolicy()//哪里来哪里回去
new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试和最早的竞争,也不会抛出异常
*/
public class Demo02 {
public static void main(String[] args) {
//第一个参数是核心线程
//第二个参数是最大线程数
//第三个参数时超时后,关闭最大线程数,留下核心线程
//自定义线程池!工作,ThreadPoolExecutor
ExecutorService threadPool=new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
// new ThreadPoolExecutor.AbortPolicy()
// new ThreadPoolExecutor.CallerRunsPolicy()
// new ThreadPoolExecutor.DiscardPolicy()
new ThreadPoolExecutor.DiscardOldestPolicy()
);
try {
//最大承载 Deque+max
//超过 就会抛出异常 RejectedExecutionException
for (int i = 0; i < 9; i++) {
//new Thread().start();
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" OK");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//线程池使用完后需要关闭
threadPool.shutdown();
}
}
}
- 四种拒绝策略
代码示例:
new ThreadPoolExecutor.AbortPolicy() //人数满了,还有人来,这是不处理这个人,抛出异常
new ThreadPoolExecutor.CallerRunsPolicy()//哪里来哪里回去
new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试和最早的竞争,也不会抛出异常
12、四大函数式接口
必掌握:lambda表达式、链式编程、函数式接口、Stream流式计算。
-
函数式接口:只有一个方法的接口
-
代码示例:
-
@FunctionalInterface public interface Runnable { public abstract void run(); } //超级多@FunctionalInterface //简化编程模型,在新版本的框架底层大量应用 //foreach(消费者类型的函数式接口)
1、函数式接口Function
代码示例:
package function;
import java.util.function.Function;
/*
Function 函数型接口 有一个输入参数,有一个输出
只要是 函数式接口 可以用lambd表达式简化
*/
public class Demo01 {
public static void main(String[] args) {
//工具类:输出输入的值
// Function function=new Function<String,String>(){
// @Override
// public String apply(String str) {
// return str;
// }
// };
Function function=(str)->{
return str;
};
System.out.println(function.apply("asd"));
}
}
2、断定型接口
代码示例:
package function;
import java.util.function.Predicate;
/*
Predicate 断定型接口:有一个输入参数,返回值是boolean值
*/
public class Demo02 {
public static void main(String[] args) {
//判断字符串是否为空
// Predicate 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"));
}
}
3、Consumer消费型接口
代码示例:
package function;
import java.util.function.Consumer;
/*
Consumer 消费者函数式接口 只有输入,没有返回值
*/
public class Demo03 {
public static void main(String[] args) {
// Consumer<String> consumer= new Consumer<String>() {
// @Override
// public void accept(String str ) {
// System.out.println(str);
// }
// };
Consumer<String> consumer=(str)->{
return ;
};
consumer.accept("asd");
}
}
.4、Supplier 供给型接口
代码示例:
package function;
import java.util.function.Supplier;
/*
Supplier 没有参数,只有返回值
*/
public class Demo04 {
public static void main(String[] args) {
// Supplier supplier= new Supplier<Integer>() {
// @Override
// public Integer get() {
// System.out.println("get");
// return 1024;
// }
// };
Supplier supplier=()->{
return 1024;
};
System.out.println(supplier.get());
}
}
13、Stream流式计算
- 什么是Stream流式计算?
- 大数据:存储+计算
- 存储:集合、MySQL(本质就用来存储的)
- 计算都应该交给流来操作!
14、forKJoin
- forKJoin用来并行执行任务,提高效率!在大数据量中
- 大数据:Map Reduce(把大任务拆分为小任务)
ForkJoin:代码示例:
package ForKJoin;
/*
求和计算的任务!
如何使用forkjoin
1.forkjoinPool 通过它来执行
2.计算任务 execue(ForkJoinTask<?> task)
3.计算类要继承ForkJoinTask
*/
import java.util.concurrent.RecursiveTask;
public class ForKJoinDemo extends RecursiveTask<Long> {
private Long start; //1
private Long end; //1990900000
//临界值
private Long temp=10000L;
public ForKJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
// public void test(){
// if (end - start > temp) {
// //走分支合并计算
// }else{
// int sum=0;
// for (int i = 0; i <=10_0000_0000; i++) {
// sum+=i;
// }
// System.out.println(sum);
// }
// }
//计算方法
@Override
protected Long compute(){
if ((end - start )<temp) {
Long sum = 0L;
for (Long i = start; i <= end; i++) {
sum += i;
}
return sum;
}
else{ //forkjoin 递归
Long middle=(start+end)/2; //中间值
ForKJoinDemo task1 = new ForKJoinDemo(start, middle);
task1.fork(); //拆分任务,将任务压入线程队列
ForKJoinDemo task2 = new ForKJoinDemo(middle+1,end);
task2.fork();
return task1.join()+ task2.join();
}
}
}
测试代码示例:
package ForKJoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// test1(); //需要时间43678
// test2(); //17556
test3(); //7855
}
//普通程序员
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> sumbit=forkJoinPool.submit(task);//提交任务
Long sum=sumbit.get();
long end = System.currentTimeMillis();
System.out.println("sum="+sum+"时间"+(end-start));
}
public static void test3(){
long start = System.currentTimeMillis();
//Stream 并行流
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+"时间"+(end-start));
}
}
15、异步回调
- Future 设计的初衷:对将来某个事件的结果进行建模!
代码示例:
package Future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/*
异步调用:CompletableFuture
异步执行
成功回调
失败回调
*/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//发起一个请求
//没有返回值的 异步回调 runAsync
// CompletableFuture<Void> objectCompletableFuture =CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
// });
// System.out.println("1111");
// objectCompletableFuture.get(); //堵塞获取执行结果
//有返回值的异步回调 supplyAsync
//ajax 有一个成功和失败的回调 失败了返回的是错误信息
CompletableFuture<Integer> objectCompletableFuture =CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
int i=10/0; //制造一个错误
return 1024;
});
System.out.println(objectCompletableFuture.whenComplete((t, u) -> {
System.out.println("t=>" + t); //正常的返回结果
System.out.println("u=>" + u); //若出现异常,则输出的是错误信息
}).exceptionally((e) -> {
System.out.println(e.getMessage());//打印异常信息
return 250; //可以获取到错误的结果
}).get());
/*
success 200
error 404 500
*/
}
}
16、JMM
-
谈谈对Volatile的理解
- Volatile是Java虚拟机提供的一个轻量级的同步机制
- 1、保证可见性
- 2、不保证原子性
- 3、禁止指令重排
-
什么是JMM
- Java内存模型,不存在的东西,是一种概念!一种约定!
-
关于JMM的一些同步约定:
- 1、线程解锁前,必须把共享变量立刻刷回主存
- 2、线程加锁前,必须读取主存中的最新值到工作内存中!
- 3、必须保证加锁和解锁是同步一把锁
-
线程 分有工作内存、主内存!
-
出现的问题
- read 读取,作用于主内存把变量从主内存中读取到本本地内存。
- load 加载,主要作用本地内存,把从主内存中读取的变量加载到本地内存的变量副本中
- use 使用,主要作用本地内存,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。、
- assign 赋值 作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store 存储 作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
- write 写入 作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
- lock 锁定 :作用于主内存的变量,把一个变量标识为一条线程独占状态。
- unlock 解锁:作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
所以看似简单的通信其实是这八种状态来实现的。
同时在Java内存模型中明确规定了要执行这些操作需要满足以下规则:
- 不允许read和load、store和write的操作单独出现。
- 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
- 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
- 一个变量在同一时刻只允许一条线程对其进行lock操作,lock和unlock必须成对出现
- 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
- 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。
上边问题的代码示例:
package Volatile;
import java.util.concurrent.TimeUnit;
public class JMMDemo {
private static int num=0;
public static void main(String[] args) { //主线程 main线程
new Thread(()->{ //线程1
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
问题:程序不知道主存内的程序已被修改了值,这时需要Volatile来解决这个问题!
17、Volatile
-
1、保证可见性
代码示例:
package tVolatile; import java.util.concurrent.TimeUnit; public class JMMDemo { //不加 volatile 程序就会死循环 //加 volatile 保证可见性 private volatile static int num=0; public static void main(String[] args) { //主线程 main线程 new Thread(()->{ //线程1 对主内存的变化是不知道的 while (num==0){ } }).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } num=1; System.out.println(num); } }
-
不保证原子性
- 什么是原子性:即不可分割(ACID原则)
- 如线程A在执行的时候,是不能被打扰和不能被分割的,要么同时成功要么同时失败
代码示例:
package tVolatile;
/*
synchronized 保证原子性
volatile 不保证原子性
*/
public class VDemo02 {
private volatile static int num=0;
public static void add(){
num++;
}
public static void main(String[] args) {
//理论上 num结果是20000
for (int i = 1; i <=20 ; i++) {
new Thread(()->{
for (int j=0;j<1000;j++){
add();
}
}).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
- 如果不加lock和synchronized,如何保证原子性
- 使用原子类,解决原子性问题!
代码示例:
package tVolatile;
/*
synchronized 保证原子性
volatile 不保证原子性
*/
import java.util.concurrent.atomic.AtomicInteger;
public class VDemo02 {
//原子类的Integer
private volatile static AtomicInteger num=new AtomicInteger();
public static void add(){
//num++; //不是一个原子性操作
num.getAndIncrement(); //是 AtomicInteger 的+1的方法,getAndIncrement();并不是一个简单的+1操作,而是运用到了底层的CAS
}
public static void main(String[] args) {
//理论上 num结果是20000
for (int i = 1; i <=20 ; i++) {
new Thread(()->{
for (int j=0;j<1000;j++){
add();
}
}).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
==这些类的底层都直接和操作系统挂钩!如getAndIncrement();是在内存中直接修改值!==Unsafe是一个很特殊的存在!
-
指令重排
-
什么是指令重排:即我们写的程序,计算机并不是按照我们写的那样去执行的。
-
源代码–>编译器优化的重排–>指令并行也可能会重排–>内存系统也会重排–>执行
-
==处理器在进行指令重排的时候,会考虑数据之间的依赖性问题! ==
-
int x=1;//1 int y=2;//2 x=x+5; //3 y=x*x; //4 //我们所期待的是:1234 但是可能执行的时候会变成2134 1324 //但可不可能变成 4123 (不可能的)---第四行代码依赖于第一行代码!!!
可能造成的结果:a b x y 这四个值默认都是为0;
线程A 线程B x=a y=b b=1 a=2 正常的结果:x=0,y=0,但是由于指令重排
线程A 线程B b=1 a=2 x=a y=b 指令重排导致的诡异结果:x=2,y=1;
- Volatile 可避免指令重排
- 内存屏障,即CPU指令。作用
- 1、保证特定操作的执行顺序!
- 可以保证某些变量的内存可变性!(利用这些特性Volatile 实现了可见性)
- 内存屏障,即CPU指令。作用
- Volatile 可避免指令重排
Volatile 是可以保证 可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
-
18、彻底玩转 单例模式
-
饿汉式、DCL懒汉式!
-
1、饿汉式
-
代码示例:
-
package single; //只要是单例模式,一定要构造器私有 //饿汉式单例 public class Hungry { //很可能会浪费空间 private byte[] data1=new byte[1024*1024]; private byte[] data2=new byte[1024*1024]; private byte[] data3=new byte[1024*1024]; private byte[] data4=new byte[1024*1024]; private Hungry(){ } private final static Hungry HUNGRY=new Hungry(); public static Hungry getInstance(){ return HUNGRY; } }
-
DCL懒汉式
-
代码示例:
-
package single; import java.lang.reflect.Constructor; import java.lang.reflect.Field; //只要是单例模式,一定要构造器私有 //DCL懒汉式单例 public class LazyMan { private static boolean qiyuan=false; private LazyMan(){ synchronized (LazyMan.class){ if (qiyuan==false){ qiyuan=true; }else{ throw new RuntimeException("不要试图使用反射破坏异常"); } } System.out.println(Thread.currentThread().getName()+"ok"); } private volatile static LazyMan lazyMan; //双重检测锁模式的 懒汉式单例 简称DCL public static LazyMan getInstance(){ if (lazyMan==null) { synchronized (LazyMan.class){ if (lazyMan==null){ lazyMan=new LazyMan(); //不是一个原子性操作 } } } return lazyMan; } //单线程下 单例可行 //但是在多线程并发的情况下 不可行 public static void main(String[] args) throws Exception { // for (int i = 0; i < 10; i++) { // new Thread(()->{ // LazyMan.getInstance(); // }).start(); // } //反射! // LazyMan instance=LazyMan.getInstance(); Field qiyuan = LazyMan.class.getDeclaredField("qiyuan"); qiyuan.setAccessible(true); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan instance= declaredConstructor.newInstance(); qiyuan.set(instance,false); //instance转换 将原本qiyuan是true的改为false LazyMan instance2 = declaredConstructor.newInstance(); System.out.println(instance); System.out.println(instance2); //反射会破坏单例 } } /* 1.分配内存空间 2.执行构造方法,初始化对象 3.把这个对象指向这个空间 所以这里必须加volatile 即 private volatile static LazyMan lazyMan; */
-
静态内部类
-
代码示例:
-
package single; //只要是单例模式,一定要构造器私有 //静态内部类实现 public class Holder { private Holder(){ } public static Holder getInstance(){ return InnerClass.HOLDING; } public static class InnerClass{ private static final Holder HOLDING=new Holder(); } }
-
单例不安全,因为有反射,这时时候引用枚举
-
代码示例:
-
package single; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; //enum 枚举是什么? 枚举本身也是一个CLASS类 public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingle instance1=EnumSingle.INSTANCE; Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); //破除私有权限 EnumSingle instance2 = declaredConstructor.newInstance(); //.NoSuchMethodException: single.EnumSingle.<init>() System.out.println(instance1); System.out.println(instance2); // EnumSingle instance2=EnumSingle.INSTANCE; // System.out.println( instance1); // System.out.println( instance2); } }
-
-
19、深入理解CAS
-
什么是CAS
-
代码示例:
-
package cas; import java.util.concurrent.atomic.AtomicInteger; public class CASDemo { //CAS compareAndSet 比较并交换 public static void main(String[] args) { //原子类的底层引用了CAS AtomicInteger atomicInteger = new AtomicInteger(2021); //expect期望 update更新 //public final boolean compareAndSet(int expect, int update) //如果我期望的值拿到了,就更新,否则就不更新 CAS是CPU的并发原语! System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); } }
-
Unsafe类
-
-
-
-
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环。
-
CAS中的ABA问题!
-
缺点:
-
1、循环会耗时
-
2、一次性只能保证一个共享变量的原子性
-
3、ABA问题
-
-
代码示例:
-
package cas; import java.util.concurrent.atomic.AtomicInteger; public class CASDemo { //CAS compareAndSet 比较并交换 public static void main(String[] args) { //原子类的底层引用了CAS AtomicInteger atomicInteger = new AtomicInteger(2021); //对于我们平时写的SQL:加一把乐观锁! //expect期望 update更新 //public final boolean compareAndSet(int expect, int update) //如果我期望的值拿到了,就更新,否则就不更新 CAS是CPU的并发原语! //==================捣乱的线程===================== System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); //==================期望的线程===================== System.out.println(atomicInteger.compareAndSet(2021, 2022)); System.out.println(atomicInteger.get()); } }
-
20、原子引用
- 带版本号的原子操作!
==注意点:Integer使用了对象缓存机制,默认范围是-128-127,推荐使用静态工厂方法valueOf获取对象示例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的空间;
所有的相同类型的包装对象之间的值的比较,全部使用equals方法比较。
说明:对于Integer var=?在-128-127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这个一个大坑,推荐使用rquals方法进行判断.
-
原子引用解决ABA问题,对应的思想就是这个乐观锁
-
代码示例:
-
package cas; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicStampedReference; public class CASDemo { //AtomicStampedReference 注意: 如果泛型是一个包装类,注意对象的应用问题 //CAS compareAndSet 比较并交换 public static void main(String[] args) { //原子类的底层引用了CAS //AtomicInteger atomicInteger = new AtomicInteger(2021); //与乐观锁的原理相同 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=>"+atomicStampedReference.getStamp()); 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("b2=>"+atomicStampedReference.getStamp()); },"b").start(); //对于我们平时写的SQL:加一把乐观锁! //expect期望 update更新 //public final boolean compareAndSet(int expect, int update) //如果我期望的值拿到了,就更新,否则就不更新 CAS是CPU的并发原语! //==================捣乱的线程===================== // System.out.println(atomicInteger.compareAndSet(2021, 2022)); // System.out.println(atomicInteger.get()); // // System.out.println(atomicInteger.compareAndSet(2021, 2022)); // System.out.println(atomicInteger.get()); //==================期望的线程===================== // System.out.println(atomicInteger.compareAndSet(2021, 2022)); // System.out.println(atomicInteger.get()); } }
21、各种锁的理解
1、公平锁、非公平锁
公平锁:非常公平,不能插队,必须先来后到!
非公平锁:非常不公平,可以插队(默认都是非公的)
public ReentrantLock() {
sync = new NonfairSync();
}//默认都是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2、可重入锁
可重入锁(递归锁)
-
Synchronized
代码示例:
package Lock; //Synchronized public class Demo01 { public static void main(String[] args) { Phone phone=new Phone(); new Thread(()->{ phone.sms(); },"A").start(); new Thread(()->{ phone.sms(); },"B").start(); } } class Phone{ public synchronized void sms(){ System.out.println(Thread.currentThread().getName()+"sms"); call(); //这里也有锁 } public synchronized void call(){ System.out.println(Thread.currentThread().getName()+"call"); } }
-
Lock
代码示例:
package Lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //Lock public class Demo02 { public static void main(String[] args) { Phone2 phone=new Phone2(); new Thread(()->{ phone.sms(); },"A").start(); new Thread(()->{ phone.sms(); },"B").start(); } } class Phone2{ Lock lock=new ReentrantLock(); public void sms(){ lock.lock(); lock.lock(); //lock 锁必须配对,否则就会死在里面 try { System.out.println(Thread.currentThread().getName()+"sms"); call(); //这里也有锁 } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); lock.unlock(); } } public void call(){ lock.lock(); try { System.out.println(Thread.currentThread().getName()+"call"); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
3、自旋锁
- SpinLock
-
SpinLock代码示例:
-
package Lock; import java.util.concurrent.atomic.AtomicReference; /* 自旋锁 */ public class SpinlockDemo { //int 0 //Thread null AtomicReference<Thread> atomicReference=new AtomicReference<>(); //加锁 public void myLock(){ Thread thread=Thread.currentThread(); System.out.println(Thread.currentThread().getName()+"==>myLock"); //自旋锁 while (!atomicReference.compareAndSet(null,thread)){ //只要thread不为空null 则一直在这循环 //T1 T2 执行完后为空,则退出循环 } } //解锁 public void myUnLock(){ Thread thread=Thread.currentThread(); System.out.println(Thread.currentThread().getName()+"==>myUnLock"); atomicReference.compareAndSet(thread,null); } }
测试代码示例:
package Lock; import java.util.concurrent.TimeUnit; public class TestSpinlock { public static void main(String[] args) throws InterruptedException { // ReentrantLock reentrantLock = new ReentrantLock(); // reentrantLock.lock(); // reentrantLock.unlock(); //底层使用的自旋锁 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(); // lock.myLock(); // lock.myUnLock(); } }
4、死锁
-
死锁是什么?
-
图示例
-
死锁测试,如何排除死锁?
-
死锁测试,代码示例:
-
package Lock; 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 定位进程号
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R5R8Iyrk-1622806294291)(jps%20-l.png)]
-
使用 jstack 查看进程信息,找到进程号,解决死锁问题
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mi3gYvZR-1622806294292)(jstack.png)]
package Lock;
import java.util.concurrent.TimeUnit;
public class TestSpinlock {
public static void main(String[] args) throws InterruptedException {
// ReentrantLock reentrantLock = new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
//底层使用的自旋锁
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();
// lock.myLock();
// lock.myUnLock();
}
}
4、死锁
-
死锁是什么?
-
图示例
-
死锁测试,如何排除死锁?
-
死锁测试,代码示例:
-
package Lock; 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 定位进程号
- -
使用 jstack 查看进程信息,找到进程号,解决死锁问题
-