是java.util (工具包)下的concurrent包
在 Java 5.0 提供了 java.util.concurrent
(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,
用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中
的 Collection 实现等;
普通的线程代码 使用Thread
Runnable:没有返回值,效率比callable低
线程和进程
进程:一个程序,QQ.exe, Music.exe 程序的集合;
一个进程包含多个线程。java默认有几个线程?2个,main线程和gc线程。
线程:开了一个进程Typora,可以写字,自动保存(都是线程负责的)
java可以开启线程吗?
答:不可以,Thread的start方法是调用了一个名为start0的方法,start0是一个native方法,c++,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 */
}
}
}
//start0是一个native方法, c++写的
private native void start0();
并发、并行
并发编程:并发、并行
并发:多线程操作一个资源
并行:多个人一起行走
package com.kuang.demo01;
public class Test1 {
public static void main(String[] args) {
//获取cpu核数
System.out.println(Runtime.getRuntime().availableProcessors());
// new Thread().start();
}
}
并发编程的本质:充分利用CPU的资源
线程状态
6个状态:
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
* 阻塞
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
*
* - {@link Object#wait() Object.wait} with no timeout
* - {@link #join() Thread.join} with no timeout
* - {@link LockSupport#park() LockSupport.park}
*
*
* A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called Object.wait()
* on an object is waiting for another thread to call
* Object.notify() or Object.notifyAll() on
* that object. A thread that has called Thread.join()
* 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:
*
* - {@link #sleep Thread.sleep}
* - {@link Object#wait(long) Object.wait} with timeout
* - {@link #join(long) Thread.join} with timeout
* - {@link LockSupport#parkNanos LockSupport.parkNanos}
* - {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
wait/sleep区别
来自不同的类(wait来自Object类,sleep来自Thread类),企业当中不会用sleep,而是用java.util.concurrent.TimeUnit.DAYS.sleep(x);
锁的释放
wait会释放,sleep不会
使用的范围不同
wait:必须在同步代码块中
sleep:可以在任何地方睡
是否需要捕获异常
wait不需要,sleep必须要捕获异常,因为可能发生超时等待的情况
synchronized:排队 锁
java.util.concurrent.locks
所有已知实现类:
ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock
用法:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
公平锁:必须先来后到
非公平锁:可以插队(默认)
synchronized 与lock的区别
锁是什么,如何判断锁的是谁
面试手写:单例模式,排序算法,生产者消费者,死锁
package com.kuang.pc;
/**
* 线程之间的通信问题:生产者和消费者问题 等待唤醒 通知唤醒
* 线程交替执行 A B同时操作同一个变量
*/
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 num = 0;
//+1
public synchronized void increment() throws InterruptedException {
if(num!=0){
//wait
this.wait(); //synchronized会锁住当前对象,(当一个线程执行increment方法时,其他线程无法执行decrement方法)
// 但是wait方法将会使线程阻塞,并释放锁
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//notify other thread: work is done
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if(num==0){
//wait
this.wait();
}
num -- ;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//notify other thread:work is done
this.notifyAll();
}
}
synchronized:wait,notify配套
Lock:await,signal配套
代码实现
package com.kuang.pc;
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 (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();
new Thread(()->{for (int i =0;i<10;i++){
try{
data.increment();
}catch (Exception e){
e.printStackTrace();
}
}},"C").start();
new Thread(()->{for (int i =0;i<10;i++){
try{
data.decrement();
}catch (Exception e){
e.printStackTrace();
}
}},"D").start();
}
}
//判断等待 业务 通知
class Data2{
private int num = 0;
Lock l = new ReentrantLock();
Condition condition = l.newCondition();
//+1
public void increment() throws InterruptedException {
//上锁
l.lock();
//业务代码
while(num!=0){
//wait
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//notify other thread: work is done
condition.signalAll();
//解锁
l.unlock();
}
//-1
public void decrement() throws InterruptedException {
l.lock();
while(num==0){
//wait
condition.await();
}
num -- ;
System.out.println(Thread.currentThread().getName()+"=>"+num);
//notify other thread:work is done
condition.signalAll();
l.unlock();
}
}
需求:使线程按顺序唤醒,比如A->B->C->D->A
代码实现精准唤醒:
package com.kuang.pc;
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 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();
}
}
/**
* A执行完调用B,B执行完调用C,C执行完调用A
*/
class Data3{
private Lock l = new ReentrantLock();
private Condition condition1 = l.newCondition();
private Condition condition2 = l.newCondition();
private Condition condition3 = l.newCondition();
private int num = 1; //1A 2B 3C
public void printA(){
l.lock();
try {
//业务 : 判断》执行》通知
while (num!=1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"--AAAAAA");
num=2;
//唤醒指定的线程
condition2.signal(); //为什么condition2对应B呢
//signalAll是唤醒全部,signal是精准唤醒
}catch (Exception e){
e.printStackTrace();
}finally{
l.unlock();
}
}
public void printB(){
l.lock();
try {
//业务 : 判断》执行》通知
while (num!=2){
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"---BBBBB");
num=3;
//唤醒指定的线程
condition3.signal(); //为什么condition2对应B呢
}catch (Exception e){
e.printStackTrace();
}finally{
l.unlock();
}
}
public void printC(){
l.lock();
try {
//业务 : 判断》执行》通知
while (num!=3){
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"----CCCCC");
num = 1;
//唤醒指定的线程
condition1.signal(); //为什么condition2对应B呢
}catch (Exception e){
e.printStackTrace();
}finally{
l.unlock();
}
}
//生产线 下单- 支付 --交易
}
简化了一下,没有将全部8个问题整理下来,省略了一些简单的。
synchronized加在static方法上,锁的是class,类模板
加在普通方法上,锁的是调用该方法的实例对象。
注意的是类模板和实例对象是相互不关联的,即,有一个线程锁了class,执行static方法,并不妨碍另一个线程获得该类的某个实例对象的锁,执行普通方法。
例子1:
package com.kuang.lock8;
import java.util.concurrent.TimeUnit;
/**
* 锁的8个问题
*
* 两个线程操作同一个对象,先拿到锁的线程先执行
*/
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendMsg();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.call();
}).start();
}
}
class Phone{
public synchronized void sendMsg() throws InterruptedException {
Thread.sleep(4000);
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
class Solution{
//最长有效括号 dp问题
public int logest(String s){
int n =s.length();
if(n<2){
return 0;
}
int res=0;
int [] dp= new int[n];
for (int i=n-2;i>=0;i--){
if (s.charAt(i)=='('){
int j=i+dp[i+1]+1;
if(j<n && s.charAt(j)==')'){
dp[i] = dp[i+1]+2;
if(j+1<n){
dp[i]+=dp[j+1];
}
}
res=Math.max(res,dp[i]);
}
}
return res;
}
}
例子2:
package com.kuang.lock8;
import java.util.concurrent.TimeUnit;
/**
* 锁的8个问题
*
* synchronized锁的是对象,两个线程分别操作两个对象,就是互不干扰的,就不会存在等待锁释放的问题。
*/
public class Test2 {
public static void main(String[] args) throws InterruptedException {
Phone2 phone = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{
try {
phone.sendMsg();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.call();
}).start();
}
}
class Phone2{
public synchronized void sendMsg() throws InterruptedException {
Thread.sleep(4000);
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){ //不受锁的影响
System.out.println("hello");
}
}
例子3:
package com.kuang.lock8;
import java.util.concurrent.TimeUnit;
/**
* 锁的8个问题
* 如果某个类有两个static的同步方法,但是该类有两个实例化对象,分别通过这两个对象来调用方法,会不会被锁?
* 事实说明,synchronized加在static方法上,锁的就是class模板,而不是具体对象,即使通过两个不同对象来调用方法,也是只有先拿到锁的先执行。
*/
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Phone3 phone = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
try {
phone.sendMsg();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.call();
}).start();
}
}
class Phone3{
/**
* 如果没有static 那么方法是对象,加了static,该方法就是类的,不实例化就可以调用。
* 没有static的话,那么synchronized锁的是对象,锁住的对象是调用该方法的对象
* 如果加了static,那么该方法就在类加载的时候就有了,此时synchronized锁的是class,而不是对象
* @throws InterruptedException
*/
public static synchronized void sendMsg() throws InterruptedException {
Thread.sleep(4000);
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
public void hello(){ //不受锁的影响
System.out.println("hello");
}
}
例子4:
package com.kuang.lock8;
import java.util.concurrent.TimeUnit;
/**
* 锁的8个问题
* 如果某个类有一个static的同步方法,一个普通同步方法,两个线程操作同一个对象,会怎样?
* 事实证明,synchronized锁住static方法时,锁的是class 类模板,而普通的synchronized方法锁的是对象,二者相互不影响!
* */
public class Test4 {
public static void main(String[] args) throws InterruptedException {
Phone4 phone = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
try {
phone.sendMsg();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.call();
}).start();
}
}
class Phone4{
/**
* 如果没有static 那么方法是对象,加了static,该方法就是类的,不实例化就可以调用。
* 没有static的话,那么synchronized锁的是对象,锁住的对象是调用该方法的对象
* 如果加了static,那么该方法就在类加载的时候就有了,此时synchronized锁的是class,而不是对象
* @throws InterruptedException
*/
public static synchronized void sendMsg() throws InterruptedException {
Thread.sleep(4000);
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){ //不受锁的影响
System.out.println("hello");
}
}
package com.kuang.unsafeCollections;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
//java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
public static void main(String[] args) {
//并发下,ArrayList不安全
/**
* 解决方案:
* 1. List list = new Vector<>();
* 2. 数组的工具类叫Arrays, 集合的所有工具类叫Collections,利用Collections里的一些方法 List list = Collections.synchronizedList(new ArrayList<>());
* 3. List list = new CopyOnWriteArrayList<>(); 使用JUC下的一个ArrayList类
* CopyOnWrite:写入时复制,用到了COW:计算机设计领域的一种策略
* 具体来说 就是,多线程调用时,在读取时是固定的,在写入时先复制一份,插入新元素,最后放回去,这样避免写入覆盖
* Vector使用的是synchronized,写入效率低,而CopyOnWriteArrayList使用的是可重入锁,
*/
List<String> list = new Vector<>();
// List list = Collections.synchronizedList(new ArrayList<>());
// List 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();
}
}
}
是什么:
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
应用场景:
CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索。
package com.kuang.unsafeCollections;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 也会出现并发修改异常 java.util.ConcurrentModificationException
* 解决:
* 1. Set set = Collections.synchronizedSet(new HashSet<>());
* 2. Set set = new CopyOnWriteArraySet<>();
*/
public class SetTest {
public static void main(String[] args) {
// Set set = new HashSet<>();
Set<String> set =new CopyOnWriteArraySet<>();
// Set 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();
}
}
}
就是HashMap
//构造函数
public HashSet() {
map = new HashMap<>();
}
//add方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//PRESENT是个常量
private static final Object PRESENT = new Object();
package com.kuang.unsafeCollections;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* 并发修改异常:java.util.ConcurrentModificationException
* 1. Map map = new ConcurrentHashMap<>();
* 2. Map map = Collections.synchronizedMap(new HashMap<>());
*/
public class MapTest {
public static void main(String[] args) {
//工作中不用HashMap
//默认等价于new HashMap<>(16,0.75)
// Map map = new HashMap<>();
// Map map = Collections.synchronizedMap(new HashMap<>());
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();
}
}
}
类似于Runnable,但是Runnable不会返回值,也不会抛出异常
代码测试:
package com.kuang.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(new MyThreadOld()).start();//Thread只能接收Runnable
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask,"A").start();//FutueTask是Runnable的实现类
new Thread(futureTask,"B").start();//注意,虽然又创建一个线程B执行,但是call只调用一次,原因是:结果会被缓存,效率高
String res = (String) futureTask.get();//获取callable的返回值,这个get方法可能产生阻塞,因为call方法里可能是一个耗时操作,无法很快返回(主线程可能会在此处等待子线程执行完call方法),因此这一句最好写在最后一行;或者使用异步通信去处理
}
}
class MyThreadOld implements Runnable{
@Override
public void run() {
}
}
class MyThread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("call()");
return "null";
}
}
细节:
package com.kuang.helperClass;
//计数器
public class CountDownLatch {
public static void main(String[] args) throws InterruptedException {
//倒计时,总数是6
java.util.concurrent.CountDownLatch countDownLatch = new java.util.concurrent.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();//等待计数器归零,然后再继续向下执行,如果没有这句,就可能lock door出现在 x go out之前
System.out.println("lock door");
}
}
原理:
每次有线程调用countDown(),数量就-1,假设计数器归零,countDownLatch.await()就会被唤醒,继续执行。
简单理解为:加法计数器
package com.kuang.helperClass;
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吗?不能,因为lambda本质是new了一个class的实例,而这个class是一个实现了函数式接口的类
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集到第"+temp+"颗龙珠");
try {
cyclicBarrier.await(); //等待。
// 只有此处与cyclicBarrier有关,所以,也就是需要await()执行七次,才能继续执行?
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
抢车位:六个车,三个位置
package com.kuang.helperClass;
import java.util.concurrent.TimeUnit;
public class Semaphore {
public static void main(String[] args) {
//线程数量,停车位
java.util.concurrent.Semaphore semaphore = new java.util.concurrent.Semaphore(3);
for (int i=0;i<6;i++){
new Thread(()->{
//acquire()得到 release()释放
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();
}
},String.valueOf(i)).start();
}
}
}
原理:
作用:
java.util.concurrent下有三个接口,Condition,Lock,ReadWriteLock。
ReadWriteLock只有一个实现类就是ReentrantReadWriteLock
ReadWriteLock维护一对锁,读锁和写锁,允许多个线程读,只允许一个线程写
package com.kuang.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.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
独占锁:就类似于写锁,一次只能被一个线程持有
共享锁:就类似于读锁,可以同时被多个线程持有
读写锁:
读-读 可以共存
读-写:不可共存
写-写:不可共存
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
//写入
for (int i=0;i<5;i++){
final int temp = i;
new Thread(()->
{
myCache.put(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 MyCache{
private volatile Map<String,Object> map = new HashMap<>();
//存
public void put(String s,Object obj){
System.out.println(Thread.currentThread().getName()+"写入"+s);
map.put(s, obj);
System.out.println(Thread.currentThread().getName()+"写入完毕");
}
//取
public void get(String s){
System.out.println(Thread.currentThread().getName()+"读取"+s);
Object object = map.get(s);
System.out.println(Thread.currentThread().getName()+"读取ok");
}
}
//加锁的自定义缓存
class MyCacheLock{
private volatile Map<String,Object> map = new HashMap<>();
//读写锁,更加细粒度的控制:写入时只允许一个线程写,读取时允许多个线程读
private ReadWriteLock lock = new ReentrantReadWriteLock();
// private Lock lock = new ReentrantLock();//普通锁
//存
public void put(String s,Object obj){
lock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"写入"+s);
map.put(s, obj);
System.out.println(Thread.currentThread().getName()+"写入完毕");
} finally {
lock.writeLock().unlock();
}
}
//取
public void get(String s){
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+s);
Object object = map.get(s);
System.out.println(Thread.currentThread().getName()+"读取ok");
}finally {
lock.readLock().unlock();
}
}
}
参考:
注意:finally也不是一定会被执行,如果在try代码块中,System.exit()强制退出程序,或者在执行try代码块中报错抛出异常(例如5/0),finally代码块就不会执行了。
答案:
与List,Set是同级的,是JUC下的一个接口,实现类有:ArrayBlockingQueue,LinkedBlockingQueue, SynchronizedBlockingQueue,PriorityBlockingQueue等
什么时候使用:多线程并发处理;线程池
**使用:**添加和移出
方式 | 抛出异常 | 有返回值(t或f)不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add(Object obj) | offer(Object o) | put(Object o) | offer(Object o,时间,时间单位) |
移除 | remove() | poll() | take() | pull(时间,时间单位) |
返回队列首部元素 | element() | peek() |
代码演示:
package com.kuang.bq;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
//Collection接口 java.util
//List实现Collection接口 java.util
//Set实现Collection接口 java.util
//Queue实现Collection,BlockingQueue是实现了Queue的接口
test1();
}
/**
* 抛出异常
*/
public static void test1(){
ArrayBlockingQueue<String> 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.add("A"));
System.out.println(blockingQueue.element());//查看队首元素
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//异常, 已经没有元素了
System.out.println(blockingQueue.remove());
}
/**
* 有返回值,不抛出异常
*/
public static void test2(){
ArrayBlockingQueue<String> 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("C"));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//队列已空,不会抛出异常,返回null
System.out.println(blockingQueue.poll());
}
/**
* 等待 阻塞,一直等待
*/
public void test3() throws InterruptedException {
ArrayBlockingQueue<String> 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 void test4() throws InterruptedException {
ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("A");
blockingQueue.offer("A");
blockingQueue.offer("A");
////队列已满,就会等待2秒,如果2秒后还没有位置,就退出
blockingQueue.offer("A",2, TimeUnit.SECONDS);
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
//超过2秒没取出就不取了
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
}
}
没有容量,进去一个元素,只有取出来才能再放,相当于容量为1.
put() take()
package com.kuang.bq;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class SynchronizedBlockingQueue {
public static void main(String[] args) {
//和其他BlockingQueue不一样,不存储元素,
//put了一个元素,就必须先取出后再put
SynchronousQueue<String> synchronizedBlockingQueue = new SynchronousQueue<String>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put 1");
synchronizedBlockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+"put 2");
synchronizedBlockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+"put 3");
synchronizedBlockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronizedBlockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronizedBlockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronizedBlockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
面试掌握:三大方法,7大参数,4大拒绝策略
池化技术
程序运行本质就是占用系统资源,所以要优化资源使用,就有了池化技术
线程池,连接池,内存池,对象池。。。
池化技术:事先准备好一些资源,有人要用就来这些拿,用完还回
线程复用,可以控制最大并发数,管理线程
线程池三大方法
package com.kuang.pool;
import oracle.jrockit.jfr.jdkevents.ThrowableTracer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//Executors就是个工具类,有三大方法 都是用来new一个线程池的
//
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++){
threadPool.execute(()->{ //执行
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}finally {
//需要关闭线程池
threadPool.shutdown();
}
}
}
七大参数
以上三个方法都是new了一个ThreadPoolExecutor并返回
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>());
}
那么什么是ThreadPoolExecutor
//此为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;
}
阿里建议使用ThreadPoolExecutor创建线程池,而不用Executor,更有利于理解底层原理和运行规则,避免资源耗尽的风险
Executor的弊端:
FixedThreadPool和SingleThreadPool
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量请求导致OOM
CachedThreadPool和ScheduledThreadPool
允许的创建的线程数为Integer.MAX_VALUE,可能会堆积大量请求导致OOM
七大参数:
核心线程数:一直运行的线程数量
最大线程数:当阻塞队列满时,能够开启的最大线程数量
如果已经开启了最大线程数量,且阻塞队列满了,这时候如何处理新来的请求,就是拒绝策略
除了几个核心线程之外,其他线程如果一段时间没有业务就释放
最大承载请求:队列长+最大线程数,一旦超出,新请求就执行拒绝策略
手动创建线程池
package com.kuang.pool;
import oracle.jrockit.jfr.jdkevents.ThrowableTracer;
import java.util.concurrent.*;
//Executors就是个工具类,有三大方法 都是用来new一个线程池的
//
public class Demo01 {
public static void main(String[] args) {
System.out.println(Runtime.getRuntime().availableProcessors());//获取cpu核数
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),//这个工厂一般不动
new ThreadPoolExecutor.AbortPolicy()); //这是默认拒绝策略:不处理,抛出异常
try{
for (int i=0;i<8;i++){
threadPool.execute(()->{ //执行
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}finally {
//需要关闭线程池
threadPool.shutdown();
}
}
}
四个拒绝策略:
new ThreadPoolExecutor.AbortPolicy();//抛出异常
new ThreadPoolExecutor.CallerRunsPolicy();//哪来的回哪去,哪个线程交给线程池的请求,就交由哪个线程处理。
new ThreadPoolExecutor.DiscardPolicy();//不抛出异常,直接丢掉任务
new ThreadPoolExecutor.DiscardOldestPolicy();//尝试获取最早的线程,如果失败就丢掉任务,不抛出异常
最大线程数到底该如何定义:
CPU密集型:几核就定义为几,保证CPU效率最高,运行时用代码获取cpu核数
System.out.println(Runtime.getRuntime().availableProcessors());//获取cpu核数
IO密集型:判断你的程序中十分耗IO的线程有多少,只要大于这个数就可以,一般设2倍。
泛型 枚举 反射 注解是旧时代的
新时代必会:lamdba表达式,链式编程,函数式接口,stream流式计算(jdk8刚出现的 )
函数式接口:只有一个方法的接口,比如Runnable接口
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface Runnable
is used
* to create a thread, starting the thread causes the object's
* run
method to be called in that separately executing
* thread.
*
* The general contract of the method run
is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
//
只要是函数式接口就可以用lambda简化
lambda的格式就是()->{} 小括号写参数,大括号写代码,用法就是:比如new Thread() 需要传入一个实现了Runnable接口的类的实例,那么new Thread(()->{System.out.println("");}).start();即可
都来自package java.util.function
//1. 函数型接口
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
//2. 断定型接口,有一个输入参数,返回值是布尔值
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
//3. 消费型接口 只有输入,无返回值
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
//4. 供给型接口
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
//测试一下:
Supplier<Integer> supplier = ()->{return 1;};
System.out.println(supplier.get());
是什么
集合、MySQL本质就是存东西的
计算交给流来做
代码实现:
package com.kuang.stream;
import java.util.Arrays;
import java.util.List;
/**
* 筛选出满足以下条件的用户:
* 1. id为偶数
* 2. 年龄大于23
* 3. 名称转为大写字母
* 4. 用户名字母倒着排序
* 5. 只输出一个用户!
*/
public class Test {
public static void main(String[] args) {
User u1 = new User(1,"a",21);
User u2 = new User(0,"b",23);
User u3 = new User(4,"c",24);
User u4 = new User(6,"d",22);
User u5 = new User(8,"e",26);
//集合就是存储
List<User> users = Arrays.asList(u1,u2,u3,u4,u5);
//计算交给流
users.stream() //stream下的各个方法(filter,map,sorted)里的参数都是函数式接口
.filter((user)->{ return user.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
出现在jdk7,并行执行任务,提高效率!
本质上说,就是把一个大任务分成若干小任务并行处理,每个小任务返回一个结果,最后Join成最终结果
ForkJoin特点:工作窃取(线程A,B同时执行各自的若干个任务,B先处理完,B就拿过A的任务来处理。对每个线程来说,各自的子任务都存在一个双边队列里,自己可以从一端拿任务执行,其他线程可以从另一端获取任务执行)
package com.kuang.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();
test2();
test3();
}
public static void test1(){
long start = System.currentTimeMillis();
long sum = 0L;
for (long i =1L;i<=10_0000_0000L;i++){
sum+=1;
}
long end = System.currentTimeMillis();
System.out.println("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);
// forkJoinPool.execute(task);//执行任务,没有结果
ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务
long sum = submit.get();//获得结果,此处会阻塞等待
long end = System.currentTimeMillis();
System.out.println("sum=时间:"+(end-start)); //这个结果怎么是最慢的????
}
//stream并行流
public static void test3(){
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum=时间:"+(end-start));//这个结果怎么也没快到哪去????
}
}
package com.kuang.forkjoin;
import java.util.concurrent.RecursiveTask;
/**
* 普通->ForkJoin-> Stream并行流
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private long start;
private long end;
//临界值
private long temp=10000L;
public ForkJoinDemo(Long start,Long end){
this.start = start;
this.end=end;
}
//如何使用ForkJoin
//1. forkjoinPool
//2. 计算任务 forkjoinPool.execute(ForkJoinTask task)
// ForkJoinTask有两个子类,一个是适用有返回值任务的RecursiveTask<返回值类型>,一个是适用无返回值任务的RecursiveAction
//只需写一个类继承这两个类之一并重写其方法即可
@Override
protected Long compute() {
if ((end-start)<temp){
long sum = 0L;
for (Long i = start; i <= end; i++){
sum+=i;
}
// System.out.println(sum);
return sum;
}else{
//分支合并计算 很像递归
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();
}
}
public static void main(String[] args) {
}
}
Future:设计的初衷:对将来某个事件 的结果进行建模
Future是一个接口,CompletableFuture是它的一个实现类
package com.kuang.future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
/**
* 异步调用:相当于ajax
* 异步执行
* 成功回调
* 失败回调
*/
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//发起请求
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{ //runAsync是没有返回值的异步回调
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"runAync=>void");
});
System.out.println("1111");
completableFuture.get();//可能会阻塞等待 来获取执行结果
System.out.println("===================================");
//有返回值的异步回调:supplyAsync
CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync=》Integer");
int i =10/0;//故意制造异常
return 1024;
});
System.out.println(
completableFuture1.whenComplete((t, u) -> {
System.out.println("t=" + t); //t是正常运行时返回的1024,若有异常则为null
System.out.println("u=" + u);// u在无异常情况下为null,有异常则为异常的类型
}).exceptionally((e) -> {
System.out.println(e.getMessage());//有异常则会执行这一段,无异常则不执行
return 233;
}).get());
}
}
volatile:java虚拟机提供的轻量级同步机制,没有Synchronized那么强大。它有三个重要点(特征):
参考:https://blog.csdn.net/u011080472/article/details/51337422
JMM:java内存模型,不存在的东西,一种概念,一种约定。
关于JMM的一些同步的约定:
每个线程有自己的执行引擎,自己的工作内存。线程在操作共享资源时,有8个操作,分为4组:
关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下八种操作来完成:
如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作,如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。Java内存模型只要求上述两个操作必须按顺序执行,而没有保证必须是连续执行。也就是read和load之间,store和write之间是可以插入其他指令的,如对主内存中的变量a、b进行访问时,可能的顺序是read a,read b,load b, load a。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 com.kuang.volatileTest;
import java.util.concurrent.TimeUnit;
public class JMMDemo {
private static int num = 0;
public static void main(String[] args) { //主线程
new Thread(()->{ //线程1
while(num==0){
//
}
}).start();
try{
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num); //运行发现,主线程将num改为1,但是线程A并没有停止,
// 因为线程A将num复制到自己的工作内存中,其他线程对num进行修改,主内存发生变化,是线程A看不到的,也就是不可见。
// 所以num变化之后,需要去通知线程A才可以让线程A知道发生了变化
//那能不能让线程A看到共享内存中值的变化呢????
}
}
private volatile static int num = 0; //修改此行代码即可
线程A在执行任务时,不能被打扰,也不能被分割,要么同时成功,要么同时失败
package com.kuang.volatileTest;
public class VDemo2 {
private static int num=0;
private static void add(){
num++;
}
public static void main(String[] args) {
for (int i=0;i<20;i++){
new Thread(()->{
for (int j=0;j<1000;j++){
add();
}
}).start();
}//理论上num结果为2w,但由于多线程都把num读取到了自己的工作内存再执行加法操作,所以会导致结果小于2w
//解决方法是在add方法上加Synchronized,保证只有一个线程执行add方法
// (volatile只能加在变量上,不能加在方法上)volatile不保证原子性,所以在num上加volatile不能解决这个问题
while (Thread.activeCount()>2){//如果还有两个线程就说明还没执行完
Thread.yield();//那么就让主线程礼让
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
如果不加Lock和synchronized怎么样保证原子性??
为什么上例子中只有num++一行代码还不能保证原子性?(分析字节码文件可知,这行代码具体还要分三步:获取static值,执行操作,写回static值)
如下修改即可:使用AtomicXxx包装类做共享变量。这个方法十分高效,因为都是用的底层native方法,用到了CAS原理
package com.kuang.volatileTest;
import java.util.concurrent.atomic.AtomicInteger;
public class VDemo2 {
private volatile static AtomicInteger num=new AtomicInteger(0);
private static void add(){
num.getAndIncrement();//AtomicInteger的加1方法;使用了底层(都是native方法)的CAS,效率极高
}
public static void main(String[] args) {
for (int i=0;i<20;i++){
new Thread(()->{
for (int j=0;j<1000;j++){
add();
}
}).start();
}//理论上num结果为2w,但由于多线程都把num读取到了自己的工作内存再执行加法操作,所以会导致结果小于2w
//解决方法是在add方法上加Synchronized,保证只有一个线程执行add方法
// (volatile只能加在变量上,不能加在方法上)volatile不保证原子性,所以加volatile不能解决这个问题
while (Thread.activeCount()>2){//如果还有两个线程就说明还没执行完
Thread.yield();//那么就让主线程礼让
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
是什么?
处理器在执行指令重排时,会考虑数据的依赖性
int x = 1;//1
int x = 2;//2
x = x + 5;//3
y = x + x;//4
//以上代码,并不是按顺序执行,可能顺序是2134,1324,但考虑数据依赖性,不会是4123
假设x y a b都是0
线程A执行代码 | 线程B执行代码 |
---|---|
x=a | y=b |
b=1 | a=2 |
正常的结果是:x = 0;y = 0;
但由于指令重排,可能线程A先执行b=1;因为这两个语句的顺序对线程A无关紧要;线程B也可能先执行a=2;
这样导致的结果就是:x=2;y=1;
这个结果在理论上 逻辑上是会产生的。
volatile可以避免指令重排。
小结:volatile可以保证可见性,不保证原子性,由于内存屏障,可以避免指令重排现象的产生
内存屏障在单例模式里使用最多,
饿汉式的单例模式中有个DCL懒汉式就用到了内存屏障
package com.kuang.singleton;
//饿汉单例
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;
}
}
package com.kuang.singleton;
//懒汉式单例
public class Lazy {
private Lazy(){
//查看哪个线程执行了构造方法。执行了构造方法就说明new了一个实例对象
System.out.println(Thread.currentThread().getName()+"ok");
}
private volatile static Lazy lazy;
//双重检测锁模式的懒汉式称为DCL懒汉式
public static Lazy getInstance(){
if (lazy == null){
synchronized (Lazy.class){//1. 锁住类模板 因为这个getInstance方法和lazy都是static的
if(lazy==null) { //2. 双重检测 为什么?因为可能2个线程发现lazy==null,然后其中一个线程拿到锁new出了一个实例
//然后第二个线程接着获得锁,此时就需要再一次判断来验证第一个线程已经创建了实例
lazy = new Lazy();
}
//3. 以上还可能出现问题,因为lazy = new Lazy();不是原子性操作,具体分三步完成:1)分配空间 2)在空间中初始化对象 3)将lazy指向该内存空间
//由于指令重排的存在,可能线程A拿到了锁并进行new操作,先分配了空间,然后就指向了这个空间,还没有初始化,
// 此时线程B执行getInstance时执行第一处判断就会发现lazy不为空,就会直接返回,那么返回的就是一个不正确的结果。所以还需要在lazy上加volatile避免指令重排。
}
}
return lazy;
}
//多线程并发的情况下,可能会创建多个对象,也就是说,有若干个线程都见到lazy==null 然后都创建实例,就导致不再是单例模式了,解决方法就是加锁
//
public static void main(String[] args) {
for (int i = 0;i<10;i++){
new Thread(()->{
Lazy.getInstance();
}).start();
}
}
}
package com.kuang.singleton;
//静态内部类实现
public class Holder {
private Holder(){//只要是单例模式就是构造器私有,构造器私有了,其他类中就无法直接new这个类的实例
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
private static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
//所有单例模式创建方式都有一个漏洞,就是可以通过反射来将构造器取消私有化,然后就可以直接new了。
以上来自狂神Java的JUC并发编程视频下的彻底玩转单例模式,一些东西(如枚举类分析)太深入就没有整理。
同样来自狂神的JUC编程视频,看不懂。
package com.kuang.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
//CAS
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
/**
* public final boolean compareAndSet(int expect, int update) {
* return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
* }
* 如果期望的值达到了,就更新,否则不更新
*/
boolean ifsuccess = atomicInteger.compareAndSet(2020,2021);//比较并交换
boolean ifsuccess2 = atomicInteger.compareAndSet(2020,2023);//比较并交换
System.out.println(atomicInteger.get());
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么执行操作,不是则一直循环
缺点:
什么是ABA问题?
两个线程A、B操作同一个共享资源C,A将C的值由期望1改为2(CAS(1,2)),B将C的值由期望1改为3,又将该值由期望3改为了1(CAS(1,3),CAS(3,1)),此时,变量已经被动过了,但是对于A来说并没有察觉到。
如何解决?答:带版本号的原子操作!使用原子引用AtomicStampedReference,思想是乐观锁。
package com.kuang.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CASDemo {
//CAS
public static void main(String[] args) {
// AtomicInteger atomicInteger = new AtomicInteger(2020);
// /**
// * public final boolean compareAndSet(int expect, int update) {
// * return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
// * }
// * 如果期望的值达到了,就更新,否则不更新
// */
// boolean ifsuccess = atomicInteger.compareAndSet(2020,2021);//比较并交换
//
// boolean ifsuccess2 = atomicInteger.compareAndSet(2020,2023);//比较并交换
//
// System.out.println(atomicInteger.get());
//注意如果泛型是包装类,注意对象的引用问题!!!
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();
}
atomicStampedReference.compareAndSet(1,6,stamp,stamp+1);
//版本号已经变了,所以修改不会成功。这种记录版本的思想与乐观锁类似。
System.out.println("b2="+atomicStampedReference.getStamp());
},"b").start();
}
}
坑:Integer使用对象缓存机制,默认范围是 -128-127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。
相同类型的包装类之间的值比较尽量使用equals,因为对于Integer var = ?在-128到127之间的赋值,Integer对象是在IntegerCache.cache中产生,会复用已有对象,这个区间的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象。这是个大坑。
公平锁:非常公平,不能插队,必须先来后到,获取不到锁的时候,会自动加入队列,等待线程释放后,队列的第一个线程获取锁
非公平锁:获取不到锁的时候,会自动加入队列,等待线程释放锁后所有等待的线程同时去竞争。不公平,可以插队(比如两个线程一个耗时长,一个耗时短,那么就需要运行耗时短的先拿到锁执行,因此默认是非公平锁)
Lock lock = new ReentantLock();//默认非公平锁
Lock lock = new ReentantLock(true);//改为公平锁
是非公平锁,可重入。
可重入锁:同一个线程可以反复获取锁多次,然后需要释放多次。
所有的锁都是可重入锁,也叫递归锁
好比一个院子,拿到了大门的锁就自动获得了里面所有屋子的锁,这就是可重入锁。
package com.kuang.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
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 synchronized void sms(){
lock.lock();// 锁必须配对(也就是一个lock.lock()对应一个lock.unlock()),否则就会死在里面,
try {
System.out.println(Thread.currentThread().getName()+"sms");
call(); //这里也有锁
}finally {
lock.unlock();
}
}
public synchronized void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"call");
}finally {
lock.unlock();
}
}
}
package com.kuang.lock;
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");
}
}
应用实例:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
不断尝试直到成功。
自己实现自旋锁:
package com.kuang.lock;
import sun.security.provider.ConfigFile;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class Demo3{
public static void main(String[] args) throws InterruptedException {
SpinLock lock = new SpinLock(); //自己使用CAS实现的锁
new Thread(()->{
lock.myLock();
try{
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.myUnlock();
}
},"t1").start();
TimeUnit.SECONDS.sleep(1);//主线程休眠一秒,保证线程t1在t2之前先获取到锁
new Thread(()->{
lock.myLock();
try{
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.myUnlock();
}
},"t2").start();
}
//解析:t1线程先执行myLock(),将atomicReference更改了值,此时t2线程也执行myLock(),
// 但是由于t1已经修改了atomicReference,expect不是null,因此线程t2会一直在myLock()的while处因为执行失败而循环,也就是自旋
//直到t1执行unMylock(),修改了atomicReference为null,此时线程t2才可以从while循环处执行成功,继续执行。这就是自旋锁
}
/**
* 自旋锁
*/
class SpinLock {
AtomicReference<Thread> atomicReference = new AtomicReference<>(); //此时atomicRefernece的初始值为null。
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"==>myLock");
//自旋锁
while(!atomicReference.compareAndSet(null,thread)){ //如果这个操作不成功就一直重复尝试
}
}
//解锁
public void myUnlock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"==>myUnlock");
atomicReference.compareAndSet(thread,null);
}
}
是什么
package com.kuang.deadlock;
import java.util.concurrent.TimeUnit;
public class Demo01 {
public static void main(String[] args) {
String la= "locka";
String lb = "lockb";
new Thread(new MyThread(la,lb),"线程A").start();
new Thread(new MyThread(lb,la),"线程B").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){ //锁住lockA对象
System.out.println(Thread.currentThread().getName()+"获得"+lockA);
System.out.println("想获取"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){ //锁住lockB对象
System.out.println(Thread.currentThread().getName()+"获得"+lockB);
}
}
}
}
怎么排查?