JUC包并发编程学习笔记
JUC并发编程
java.util.concurrent
1、线程和进程
进程:一个运行程序的执行实体,例如xxx.exe就会开启一个或若干个进程,一个进程往往可以包含一个或多个线程
线程:操作系统进行运算调度的最小单位,包含在进程中;一个进程中可能包含多条线程,每条线程并行执行不同的任务;
java默认有几个线程? 2个:main线程和GC线程
java有权开启线程吗? 没有,实际上是调用了本地的native方法(C++实现)
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
//调用了本地方法,C++实现,java无法直接操作硬件
private native void start0();
并发和并行
并发:多线程操作同一资源,交替执行
- CPU单核,线程快速交替执行
- 并发编程的本质
并行:“并排行走”或“同时实行或实施”
- CPU多核,满足多线程同时执行
//获取程序运行时最大可用处理器数量
System.out.println(Runtime.getRuntime().availableProcessors());
线程的生命周期(六种状态)
public enum State {
// 新建
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,死等直到被唤醒
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}
①初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
②运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的成为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得cpu 时间片后变为运行中状态(running)。
③阻塞(BLOCKED):表示线程阻塞于锁。
④等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
⑤超时等待(TIME_WAITING):该状态不同于WAITING,它可以在指定的时间内自行返回。
⑥终止(TERMINATED):表示该线程已经执行完毕。
Thread的状态切换
多线程并发环境下数据的安全问题
-
什么时候数据在多线程并发环境下存在安全问题?
三个条件:
1. 多线程并发 2. 有共享数据 3. 共享数据有修改的行为
-
怎么解决线程安全问题?
线程排队执行(不能并发)
用排队执行解决线程安全问题
这种机制被称为:线程同步机制
线程同步机制的语法是:
synchronized(){
//线程同步代码块
}
synchronized小括号中的”数据“是相当关键的。
这个数据必须是多线程共享的数据,才能达到多线程排队
例如对一个银行账户取款的方法:
public void withdroaw(double money){
synchronized(this){ //这里的this就是指当前Account对象,即多个线程需要共享操作的对象
double before = this.getBalance();
double after = before - money;
try{
Thread.sleep(1000);
} catch (InterruptedException){
e.printStackTrace();
}
this.setBalance(after);
}
}
当一个线程t1进行到对这个账户的取款方法时,遇到synchronized(this),然后会找到this这个对象的锁并占用(Java语言中任何一个对象都有一个标记,我们称为“锁”),然后执行同步代码块中的程序,直到同步代码块执行结束,才会释放这把锁;
当线程t1占有这把锁时,线程t2也同时访问到了同一个对象的synchronized,再找到这个对象的锁发现已经被占用,则会等待到t1执行完并释放这把锁时,再将锁占有并进行代码块中的程序。
这样就达到了线程排队执行。
需要注意的是:这里的共享对象一定是需要排队的这些线程对象所共享的。
在开发中应该怎么解决线程安全问题?
第一:
尽量使用局部变量代替实例变量和静态变量;
第二:
如果必须使用实例变量,考虑多线程时创建多个对象,一个线程对于一个对象,这样实例变量的内存就不共享,也就没有数据安全问题了;
第三:
如果不能使用局部变量,也不能创建多个对象的情况下,再考虑使用synchronized线程同步机制;
因为synchronized线程同步机制使程序执行效率降低,系统的用户吞吐量下降,使用户体验变差。
死锁
synchronized的嵌套容易产生死锁:
package lock;
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new MyThread1(o1,o2);
Thread t2 = new MyThread2(o1,o2);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
private Object o1;
private Object o2;
public MyThread1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"->"+"o1");
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"->"+"o2");
}
}
}
}
class MyThread2 extends Thread{
private Object o1;
private Object o2;
public MyThread2(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"->"+"o2");
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"->"+"o1");
}
}
}
}
线程t1占有了实例变量o1的锁,线程t2占有了实例变量o2的锁,再接下来运行t1需等待t2释放o2的锁,而线程t2等待t1释放o1的锁,等待时t1和t2都不会释放自己已经占有的锁,因为第一个synchronized中的代码块还未执行完,这样于是就发生了死锁。因为发生死锁是既不报错,程序也不会停止,所以在开发中应重点避免死锁的产生。
2、Lock锁(重点)
传统的 Synchronized
/** 一个多线程售票的案例
* 线程就是单一的资源类,不应该有任何附属操作,降低耦合性
*/
public class TicketSale {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//并发 多线程操作同一资源,把资源类丢入线程
new Thread(()-> { //jdk1.8以后 支持lambda表达式创建函数式接口的对象 (参数...) -> {代码...}
while (ticket.num > 0)
ticket.sale();
},"A").start();
new Thread(()-> {
while (ticket.num > 0)
ticket.sale();
},"B").start();
new Thread(()-> {
while (ticket.num > 0)
ticket.sale();
},"C").start();
}
}
//资源类 OOP
class Ticket{
//属性
public int num = 50;
//买票方法
public synchronized void sale(){
if (num > 0)
System.out.println(Thread.currentThread().getName()+"->卖出了第"+num--+"张票,剩余"+num+"张");
}
}
Lock接口
公平锁:线程先来后到,获取锁
非公平锁:可以插队,根据优先级(默认)
lock锁实现并发
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketSale2 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
//并发 多线程操作同一资源,把资源类丢入线程
new Thread(()-> { //jdk1.8以后 支持lambda表达式创建函数式接口的对象 (参数...) -> {代码...}
while (ticket.num > 0)
ticket.sale();
},"A").start();
new Thread(()-> {
while (ticket.num > 0)
ticket.sale();
},"B").start();
new Thread(()-> {
while (ticket.num > 0)
ticket.sale();
},"C").start();
}
}
//资源类 OOP
class Ticket2{
//属性
public int num = 50;
// 锁
Lock lock = new ReentrantLock();
//买票方法
public void sale(){
// 加锁
lock.lock();
try {
// 业务代码
if (num > 0) {
System.out.println(Thread.currentThread().getName()+"->卖出了第"+num--+"张票,剩余"+num+"张");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); // 解锁
}
}
}
synchronized 和 lock 的区别
1、Synchronized是Java内置的关键字,Lock是一个java接口
2、Synchronized会自动释放锁,而lock必须要手动释放lock.unlock(),不释放则会发生死锁
3、Synchronized无法判断获取锁的状态,而lock可以判断是否获取了锁(Thread.holdlock(obj))
4、Synchronized 线程1(获取到锁,阻塞),线程2(死等);lock锁可以设定等待时间 lock.tryLock(long time, TimeUnit unit)
5、Synchronized 可重入锁,不可中断,非公平锁;Lock 可重入锁,可判断锁状态,默认非公平(可设置)
6、Synchronized 适合锁少量的代码同步问题,Lock适合大量锁的同步代码
3、生产者消费者问题
传统的Synchronized实现
public class ProducerCustomer {
public static void main(String[] args) {
Product product = new Product();
new Thread(()->{
// 设定累计生产消费10次
for (int i = 0; i < 10; i++) {
try {
product.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
product.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
// 判断等待,业务,唤醒
class Product{
private Integer i = 0;
public synchronized void increment() throws InterruptedException {
if (i != 0){ //资源不为零时,等待消费,不用生产
this.wait();
}
i++; //生产+1
System.out.println(Thread.currentThread().getName()+"->"+i);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if (i == 0){ //资源为零,等待生产,无法消费
this.wait();
}
i--; //消费-1
System.out.println(Thread.currentThread().getName()+"->"+i);
this.notifyAll();
}
}
if判断等待条件存在问题:
当不止两个线程,假设存在A、B、C、D四个线程时,会存在虚假唤醒的情况
例如A和C程同时判一次i != 0 后等待被唤醒,唤醒时A和C同时唤醒并不再判断i != 0 而同时i++ 出现了i等于2甚至3的情况
因此用while循环替代if判断,使其唤醒后仍然需要判断条件,不满足继续等待
// 判断等待,业务,唤醒
class Product{
private Integer i = 0;
public synchronized void increment() throws InterruptedException {
while (i != 0){ //资源不为零时,等待消费,不用生产
this.wait();
}
i++; //生产+1
System.out.println(Thread.currentThread().getName()+"->"+i);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while (i == 0){ //资源为零,等待生产,无法消费
this.wait();
}
i--; //消费-1
System.out.println(Thread.currentThread().getName()+"->"+i);
this.notifyAll();
}
}
JUC下的Lock锁和Condition实现
public class ProducerCustomer {
public static void main(String[] args) {
Product product = new Product();
new Thread(()->{
// 设定累计生产及消费10次
for (int i = 0; i < 10; i++) {
product.increment();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
product.decrement();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
product.decrement();
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
product.increment();
}
},"D").start();
}
}
// 判断等待,业务,唤醒
class Product{
private Integer i = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//condition.await(); 等待
//condition.signal(); 唤醒
public void increment(){
lock.lock();
try {
while (i != 0){ //资源不为零时,等待消费,不用生产
condition.await();
}
i++; //生产+1
System.out.println(Thread.currentThread().getName()+"->"+i);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement(){
lock.lock();
try {
while (i == 0){ //资源为零,等待生产,无法消费
condition.await();
}
i--; //消费-1
System.out.println(Thread.currentThread().getName()+"->"+i);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Condition 精准的通知指定线程的唤醒
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class AwakeAppointedCondition {
public static void main(String[] args) {
Data1 data = new Data1();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printC();
}
},"C").start();
}
}
class Data1{
private Integer i = 0; // 标识位 0唤醒A 1唤醒B 2唤醒C
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void printA(){
lock.lock();
try {
while (i != 0){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"->执行");
i = 1;
condition2.signal(); //唤醒线程B
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (i != 1){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"->执行");
i = 2;
condition3.signal(); //唤醒线程C
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (i != 2){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"->执行");
i = 0;
condition1.signal(); //唤醒线程A
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
4、集合类 List、Set、Map
1、List集合
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
// java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
public static void main(String[] args) {
/**
* 并发下的ArrayList不是安全的
* 解决方案:
* 1、List<String> list = new Vector<>(); //效率较低
* 2、List<String> list = Collections.synchronizedList(new ArrayList<>());
* //util包下的Collections工具类将ArrayList转为线程安全的
* 3、List<String> list = new CopyOnWriteArrayList<>();
* //CopyOnWrite 写入时复制, COW 计算机程序设计领域的一种优化策略
* 他的实现就是写时复制,在往集合中添加数据的时候,先拷贝存储的数组,
* 然后添加元素到拷贝的新数组中,然后用新的数组去替换原先的数组
*
*/
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(Thread.currentThread().getName()+list);
},String.valueOf(i)).start();
}
}
}
CopyOnWriteArrayList的add方法:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//他的实现就是写时复制,在往集合中添加数据的时候,先拷贝存储的数组,
//然后添加元素到拷贝的新数组中,然后用新的数组去替换原先的数组
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
2、Set集合
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class SetTest {
public static void main(String[] args) {
/**
* 同理,并发下的Set也会产生 ConcurrentModificationException
* 解决方案:
* 1、Set<String> set = Collections.synchronizedSet(new HashSet<>());
* 2、Set<String> set = new CopyOnWriteArraySet<>();
*/
Set<String> set = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < 10; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(Thread.currentThread().getName()+set);
},String.valueOf(i)).start();
}
}
}
HashSet底层是什么?
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(); 这是一个常量
}
3、Map集合
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
// java.util.ConcurrentModificationException
public class MapTest {
public static void main(String[] args) {
/**
* 同理,并发下的HashMap也会产生 ConcurrentModificationException
* HashMap 默认初始容量16,加载因子0.75
* static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
* static final float DEFAULT_LOAD_FACTOR = 0.75f;
* 解决方案:
* 1、Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
* 2、Map<String,String> map = new ConcurrentHashMap<>(); 常用
* 理解ConcurrentHashMap原理!
*/
Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
5、Callable
相比于Runnable
1、可以有返回值
2、可以抛出异常
3、方法不同 run() / call()
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) {
//启用Callable步骤:
//new Thread( new Runnable() ).start();
//new Thread( new FutureTask<V>() ).start();
//new Thread( new FutureTask<V>( new Callable<V>() ) ).start();
MyCallable callable = new MyCallable();
FutureTask futureTask = new FutureTask(callable); //适配类
new Thread(futureTask,"A").start(); //打印call()方法执行
new Thread(futureTask,"B").start(); //依旧只打印了一个call()方法执行
try {
String s = (String) futureTask.get(); //需要捕获异常,且可能产生阻塞!
//一般将其放在最后执行;或者使用异步操作来处理
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
/**
* Callable<V> 中的泛型V即为call()方法的返回类型
*/
class MyCallable implements Callable<String>{
@Override
public String call(){
System.out.println("call()方法执行");
return "1024";
}
}
result:
call()方法执行
1024
注意点:
1、为什么FutureTask并发访问只会执行一次run()方法?
public void run() {
// FutureTask中包含一个state属性,如果state==new 说明任务没有被执行或者正在被执行还没有执行到set(result)方法。
// 此时通过CAS操作将runner设置为当前线程,这样如果线程正在执行(此时state仍然为new)其他线程进来后CAS设置失败(因为期 望值已经不再是null),直接return;或者State已经被修改不是New,也直接return。这就是为啥并发下FutureTask只会 执行一次run。
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
2、FutureTask的get()方法返回结果可能需要等待,会产生阻塞!为什么产生阻塞?
public V get() throws InterruptedException, ExecutionException {
int s = state;
//首先判断state状态,没有完成就会进入awaitDone方法。
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
/**
* 如果run方法没有运行完毕,这时线程会自旋,
* 首先被封装成WaitNode节点然后加入到链表如果还没有执行完毕就会wait,
* 如果run方法执行完毕最后会调用finishCompletion叫醒wait的线程。
*/
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
6、常用的三个辅助类(必会)
1、CountDownLatch
手动调用计数减一
示例用法
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) {
// 减法计数器
CountDownLatch countDownLatch = new CountDownLatch(5); //参数为设定的计数值
for (int i = 0; i < 5; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"completed");
countDownLatch.countDown(); //完成一条线程,计数减一
},String.valueOf(i)).start();
}
try {
countDownLatch.await(); // 使当前线程等待,直到计数为零(或直接被中断)才继续向下执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("close");
}
}
result:
1completed
2completed
3completed
0completed
4completed
close
2、CyclicBarrier
相当于一个计数器,累计到一个屏障(设定值)后执行(自动累计)
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 = 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();
}
}
}
result:
Thread-0收集到第0颗龙珠
Thread-4收集到第4颗龙珠
Thread-6收集到第6颗龙珠
Thread-2收集到第2颗龙珠
Thread-1收集到第1颗龙珠
Thread-3收集到第3颗龙珠
Thread-5收集到第5颗龙珠
集齐七颗龙珠,召唤神龙!
原理:
cyclicBarrier.await();
// 每次这个dowait()方法中,又会对设定的屏障值-1
// 直到计算为零时,调用传入Runnable实例的run方法
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L); //调用dowait方法
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count; // count减一
if (index == 0) { // tripped //count为零时调用runnable的run方法
boolean ranAction = false;
try {
final Runnable command = barrierCommand; //这是传入的Runnable实例
if (command != null)
command.run(); //调用run()方法
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
3、Semaphore
示例使用
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
// 示例:抢车位,3个车位,6辆车。 限流时也会用到!
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(()->{
try {
semaphore.acquire(); //获得一个资源位,如果资源已被占满则等待,直到资源释放
System.out.println(Thread.currentThread().getName()+"抢到了车位");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName()+"离开车位");
semaphore.release(); //释放占有的资源
}
}).start();
}
}
}
result:
Thread-1抢到了车位
Thread-2抢到了车位
Thread-0抢到了车位
// 间隔两秒
Thread-2离开车位
Thread-0离开车位
Thread-1离开车位
Thread-4抢到了车位
Thread-3抢到了车位
Thread-5抢到了车位
// 间隔两秒
Thread-5离开车位
Thread-3离开车位
Thread-4离开车位
原理:
semaphore.acquire();
获得一个资源位,如果资源已被占满则等待,直到资源释放
semaphore.release();
//释放占有的资源
作用:多个共享资源互斥的使用;并发限流,控制最大线程数
7、ReadWriteLock 读写锁
示例使用:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReadWriteLock(读写锁)
* 读-读 可以同时
* 读-写 不能同时
* 写-写 不能同时
* 写锁(独占锁、排它锁、互斥锁) 一次只能被一个线程占有
* 读锁 (共享锁) 可以多个线程同时占有
*/
public class ReadWriteDemo {
public static void main(String[] args) {
MyCacheLock myCacheLock = new MyCacheLock();
// 多线程写入
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(()->{
myCacheLock.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
// 多线程读取
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(()->{
myCacheLock.get(temp+"");
},String.valueOf(i)).start();
}
}
}
// 使用读写锁模拟实现一个可读写的缓存
// 可同时读,不可同时写
class MyCacheLock{
private volatile Map<String,String> map = new HashMap<>();
//读写锁,更加细粒度的控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 存,写入 希望同时只能有一个线程写入,使用写锁
public void put(String key,String 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);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
// 结果可见写入操作独占,读取操作可共存
result:
1写入1
1写入完成
2写入2
2写入完成
3写入3
3写入完成
4写入4
4写入完成
5写入5
5写入完成
5读取5
3读取3
3读取完成
2读取2
2读取完成
4读取4
4读取完成
5读取完成
1读取1
1读取完成
8、BlockingQueue
阻塞队列:FIFO
- 写入:如果队列已满则无法写入,阻塞
- 读取:如果队列为空无法读取,阻塞
什么情况下使用阻塞队列:多线程并发处理、线程池
队列四组API:
方式 | 抛出异常 | 有返回值,不抛异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add( ) | offer( ) | put( ) | offer( , , ) |
移除 | remove() | poll() | take() | poll( , ) |
检测队首元素 | element() | peek() | - | - |
/**
* 1、会抛出异常
*/
static void test1(){
BlockingQueue blockingQueue = new ArrayBlockingQueue(3); //参数为队列容量
blockingQueue.add("a");
blockingQueue.add("b");
blockingQueue.add("c");
// blockingQueue.add("d"); // 队列满抛异常 java.lang.IllegalStateException: Queue full
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.element()); // 检测队首元素,队列空抛异常 java.util.NoSuchElementException
System.out.println(blockingQueue.remove()); // 队列空抛异常 java.util.NoSuchElementException
}
/**
* 2、不抛异常,有返回值
*/
static void test2(){
BlockingQueue blockingQueue = new ArrayBlockingQueue(3); //参数为队列容量
System.out.println(blockingQueue.offer("A"));
System.out.println(blockingQueue.offer("B"));
System.out.println(blockingQueue.offer("C"));
// System.out.println(blockingQueue.offer("D")); // 队列满返回false 不抛出异常
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.peek()); // 检测队首元素 队列空返回null,不跑异常
System.out.println(blockingQueue.poll()); // 队列空返回null,不抛异常
}
/**
* 3、阻塞等待
*/
static void test3() throws InterruptedException {
BlockingQueue 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、超时等待
*/
static void test4() throws InterruptedException {
BlockingQueue blockingQueue = new ArrayBlockingQueue(3); //参数为队列容量
System.out.println(blockingQueue.offer("A"));
System.out.println(blockingQueue.offer("B"));
System.out.println(blockingQueue.offer("C"));
// System.out.println(blockingQueue.offer("D", 2, TimeUnit.SECONDS)); //队列满则等待两秒,若还满则返回false
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS)); // 队列空则等待两秒,若仍为空则返回null
}
SynchronousQueue 同步队列
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class SynchronousQueueDemo {
public static void main(String[] args) {
SynchronousQueue<Object> synchronousQueue = new SynchronousQueue<>(); //同步队列
new Thread(()->{
for (int i = 0; i < 3; i++) {
try {
synchronousQueue.put(i); //put存值
System.out.println(Thread.currentThread().getName()+"put"+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"T1").start();
new Thread(()->{
for (int i = 0; i < 3; i++) {
try {
TimeUnit.SECONDS.sleep(2);
Object take = synchronousQueue.take(); //take取值
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"take"+take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"T2").start();
}
}
result:
T1put0
T2take0
// 等待两秒
T1put1
T2take1
// 等待两秒
T1put2
T2take2
总结:
正如文档所说,synchronousQueue的put存值和take取值方法必然是成对出现才会执行,
put进去的同时又take将值取出,所以可以说它甚至是没有容量的,因为无法存放元素,
单一的put或是take都会产生阻塞。
9、线程池(重点)
线程池:三大方法、七大参数、四种拒绝策略
池化技术
程序运行需要占用系统的资源,池化技术目的在优化资源的使用!
线程池、连接池、对象池、缓存池...
池化技术:事先准备好一些资源放入一个池,需要用时从池中取,用完后放回池,即减少了创建以及销毁时的资源开销
线程池的优点:
- 线程复用
- 可控制最大并发数
- 方便线程的管理
线程池:三大方法
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
// Executors 工具类,创建线程池,三大方法
// Executors.newSingleThreadExecutor(); //创建单线程池
// Executors.newFixedThreadPool(5); //创建固定容量的线程池
// Executors.newCachedThreadPool(); //缓存线程池,弹性容量,可伸缩
public class Demo1 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool(); //缓存线程池,弹性容量,可伸缩
// 1.线程池开启线程 2.线程运行 3.关闭线程还给线程池
try {
for (int i = 0; i < 100; i++) {
// 使用线程池来创建线程
threadPool.execute(()->{ // Runnable,重新run方法
System.out.println(Thread.currentThread().getName()+"run");
});
}
} 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>());
}
// 本质:调用了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;
}
阿里编程规范中规定:
使用 ThreadPoolExecutor自定义线程池
import java.util.concurrent.*;
public class Demo1 {
public static void main(String[] args) {
//自定义线程池 ThreadPoolExecutor()
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2, //核心线程池大小
5, //最大线程池大小
3, //除核心线程外的线程保留时间,超时没人调用则会释放
TimeUnit.SECONDS, //保留时间单位
new ArrayBlockingQueue<>(3), //阻塞队列,存放等待的线程
Executors.defaultThreadFactory(), //线程工厂,创建线程的,一般用默认线程工厂
new ThreadPoolExecutor.AbortPolicy()); //拒绝策略,线程池和阻塞队列都满时对再申请的线程的处理策略
// 1.线程池开启线程 2.线程运行 3.关闭线程还给线程池
try {
for (int i = 0; i < 8; i++) {
// 使用线程池来创建线程
threadPool.execute(()->{ // Runnable,重新run方法
System.out.println(Thread.currentThread().getName()+"run");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); //关闭线程池
}
}
}
四种拒绝策略
拒绝策略,线程池已分配完且阻塞队列也满时,对再申请的线程的处理策略
// new ThreadPoolExecutor.AbortPolicy(); //不处理,然后抛出异常
// new ThreadPoolExecutor.CallerRunsPolicy(); //哪来的回哪去:哪个线程申请的哪个线程自己新建
// new ThreadPoolExecutor.DiscardPolicy(); //丢弃该申请的线程,且不抛出异常
// new ThreadPoolExecutor.DiscardOldestPolicy(); //抛弃最早进入队列的任务,并添加新任务到队列,失败也不抛出异常
小结和拓展
线程池的最大线程数如何设置合理?
// 了解:CPU密集型、IO密集型——调优
// 1、CPU 密集型 根据CPU逻辑处理器设置,是多少就设置多少,保证CPU效率最高
// 2、IO 密集型 根据程序中IO十分耗费时间的线程数设置,一般设置为它的两倍
//获取程序运行时最大可用处理器数量
System.out.println(Runtime.getRuntime().availableProcessors());
10、四大函数式接口(必须掌握)
新时代的编程:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// 简化编程模型,在新版本的框架底层大量应用
// foreach(消费者类型函数式接口)
.
代码测试:
Function 函数式接口
。
import java.util.function.Function;
/**
* Function 函数式接口,有一个输入参数,一个返回值
* 只要是函数式接口 就可以用lambda表达式简化 ()->{}
*/
public class FunctionDemo {
public static void main(String[] args) {
// Function<String, String> 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("abc"));
}
}
Predicate 断定型接口
import java.util.function.Predicate;
/**
* 断定型接口,有一个输入参数,返回一个boolean值
*/
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(""));
}
}
consumer 消费型接口
.
/**
* 消费型接口,只有一个输入,没有返回值
*/
public class ConsumerDemo {
public static void main(String[] args) {
// Consumer<String> consumer = new Consumer<String>() {
// @Override
// public void accept(String s) {
// System.out.println(s);
// }
// };
Consumer<String> consumer = (str)->{
System.out.println(str);
};
consumer.accept("abc");
}
}
supplier 供给型接口
.
/**
* 供给型接口,没有参数,有一个自定义类型返回值
*/
public class SupplierDemo {
public static void main(String[] args) {
// Supplier<String> supplier = new Supplier<String>() {
// @Override
// public String get() {
// return "1024";
// }
// };
Supplier<String> supplier = ()->{ return "1024";};
System.out.println(supplier.get());
}
}
11、Stream流式计算
数据:存储+计算
集合、MySQL等本质就是存储;
计算都应该交给Stream流处理!
代码示例
/**
* 题目要求:只用一行代码实现
* 现有五个用户,进行筛选:
* 1、ID是偶数
* 2、年龄大于23
* 3、用户名转换为大写
* 4、按用户名字母倒着排序
* 5、只输出一个用户
*/
public class StreamDemo {
public static void main(String[] args) {
User u1 = new User(1,"a",21);
User u2 = new User(2,"c",22);
User u3 = new User(3,"d",23);
User u4 = new User(4,"b",24);
User u5 = new User(6,"e",25);
// 集合用于存储
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
// 计算交给Stream流
// lambda表达式、链式编程、函数式接口、Stream流式计算
list.stream()
.filter((u)->{return u.getId() % 2 == 0;}) //限制id为偶数
.filter((u)->{return u.getAge() > 23;}) //限制年龄大于23
.map((u)->{
u.setName(u.getName().toUpperCase());
return u;}) //将name转换为大写
.sorted((uu1,uu2)->{return -uu1.getName().compareTo(uu2.getName());}) //按名字字母顺序倒着排序
.limit(1) //限制记录为一条
.forEach(System.out::println); //遍历输出
}
}
result:
User{id=6, name='E', age=25}
12、ForkJoin
什么是forkjoin
ForkJoin在1.7后出现,能够并行执行多任务,在大数据量下能有效提高效率!
.
ForkJoin特点:工作窃取
其中实现的是双向队列,当b执行完,而A还未执行完,B会从A队列的另一端获取任务执行。
.
.
代码示例 斐波那契数列求和
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
/**
* 如何使用ForkJoin
* 1、ForkJoinPoll 通过它来执行
* 2、计算任务 ForkJoinPool.execute(ForkJoinTask task)
* 3、计算类要继承RecursiveTask<>
*/
public class ForkJoinDemo {
public static void main(String[] args){
ForkJoinPool forkJoinPool = new ForkJoinPool(); //ForkJoinPool 用于执行ForkJoinTask
Fibonacci fibonacci = new Fibonacci(10); // 新建ForkJoinTask
Integer sum = forkJoinPool.invoke(fibonacci); //invoke() 执行给定的任务,返回完成后的结果。
System.out.println(sum); //55
}
}
// 递归计算斐波那契数列求和
class Fibonacci extends RecursiveTask<Integer> {
final int n;
Fibonacci(int n) {
this.n = n;
}
@Override
protected Integer compute() { //f(n) = f(n-1)+f(n-2)
if (n <= 1) return n;
Fibonacci f1 = new Fibonacci(n - 1);
f1.fork(); //拆分任务,把任务压入线程队列
Fibonacci f2 = new Fibonacci(n - 2);
return f2.compute() + f1.join(); //f1.join()将任务的计算结果返回
}
}
13、异步回调
Future 设计的初衷,对将来的某个事件进行建模
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* 异步回调 CompletableFuture
* 异步执行
* 成功回调
* 失败回调
*/
public class CompletableFutureDemo {
public static void main(String[] args) {
// 发起一个异步请求
// void的包装类Void 返回值为空
// 无返回值的异步回调
// CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{ // Runnable()
// System.out.println(Thread.currentThread().getName()+"runAsync");
// try {
// TimeUnit.SECONDS.sleep(2); // 延时2秒
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// });
//
// System.out.println(Thread.currentThread().getName()+"run");
//
// try {
// System.out.println(completableFuture.get()); // 获取异步处理的结果 null
// } catch (InterruptedException e) {
// e.printStackTrace();
// } catch (ExecutionException e) {
// e.printStackTrace();
// }
// 有返回值的异步回调
// 成功和失败对应不同的返回结果
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(()->{ //Supplier 供给型接口
System.out.println(Thread.currentThread().getName()+"supplyAsync");
int i = 10/0;
try {
TimeUnit.SECONDS.sleep(2); // 延时2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
return "supplyAsync success";
});
System.out.println(Thread.currentThread().getName()+" run");
try {
String result = completableFuture.whenCompleteAsync((t,v)->{
System.out.println(t); //正常的返回结果 即上面return的 "supplyAsync success"
System.out.println(v); //出现的错误信息 异常信息 java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
}).exceptionally((e)->{
e.printStackTrace();
return "500";
}).get(); //得到返回结果 成功为t,失败为"500"
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
14、JMM
对volatile的理解
Volatile是Java虚拟机提供的轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
什么是JMM
JMM:Java内存模型,是一种概念、规范!
关于JMM的一些同步规定:
1、线程加锁前,必须读取主存中的最新值到工作内存中!
2、线程解锁前,必须把共享变量立刻刷新回主存。
3、加锁和解锁是同一把锁。
JMM的八种原子操作:
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
八种操作的规则:
1、不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
2、不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
3、不允许一个线程将没有assign的数据从工作内存同步回主内存
4、一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
5、一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
6、如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
7、如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
8、对一个变量进行unlock操作之前,必须把此变量同步回主内存
15、Volatile
1、保证可见性
/**
* 1、volatile保证可见性
*/
public class VolatileDemo1 {
// volatile保证可见性
// 如果不加volatile,下面Thread1线程中获取的i检测不到主内存中i值的变化
// 会陷入死循环
private static volatile int i = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (i == 0){
}
}).start();
TimeUnit.SECONDS.sleep(1);
i++;
System.out.println("main");
}
}
2、不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功要么同时失败。
/**
* 2、volatile不保证原子性
*/
public class VolatileDemo2 {
// volatile不保证原子性
private static volatile int num = 0;
public static void main(String[] args) {
// 理论上num的值应该为 20000, 而结果总是小于20000的
// 因为volatile不保证原子性,在自增的过程中出现并发问题
for (int i = 0; i < 20; i++ ) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
num++;
}
}).start();
}
while (Thread.activeCount() > 2){ // 至少main、gc两个线程
Thread.yield(); // 保证自增的线程运行完
}
System.out.println("num="+num);
}
}
++不是一个原子性操作:
不使用lock和synchronized怎么保证原子性?
使用java.util.concurrent.atomic包下的原子类
.
import java.util.concurrent.atomic.AtomicInteger;
/**
* 2、volatile不保证原子性
*/
public class VolatileDemo2 {
// volatile不保证原子性
// 使用java.util.concurrent.atomic下原子类保证原子性
private static AtomicInteger num = new AtomicInteger(0);
public static void main(String[] args) {
// 理论上num的值应该为 20000, 而结果总是小于20000的
// 因为volatile不保证原子性,在自增的过程中出现并发问题
for (int i = 0; i < 20; i++ ) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
num.getAndIncrement(); // 原子递增方法 底层实现:CAS
}
}).start();
}
while (Thread.activeCount() > 2){ // 至少main、gc两个线程
Thread.yield(); // 保证自增的线程运行完
}
System.out.println("num="+num);
}
}
3、禁止指令重排
指令重排:一个写完的程序,并不完全按照代码的逻辑执行,计算机可能会对一些指令进行重新排序;
源代码-->编译器优化的重排-->指令并行也可能会重排-->内存系统也会重排-->执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
volatile可以避免指令重排:
内存屏障,CPU指令。 作用:
1、保证特定的操作的执行顺序
2、可以保证某些变量的内存可见性
.
16、单例模式
饿汉式单例
/**
* 饿汉式单例:类一加载就创建了单例实例对象,分配了内存空间
* 存在的问题: 造成空间的浪费
*/
public class HungryMan {
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private HungryMan(){ // 单例模式将构造器私有化
}
private static HungryMan Hungry = new HungryMan();
public static HungryMan getInstance(){ //只能通过getInstance去获取实例
return Hungry;
}
}
DCL 懒汉式
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* 懒汉式单例模式:只有在实际使用时采取实例单例对象
*/
public class LazyMan {
private static boolean flag = false; //判断单例是否创建的标志
private volatile static LazyMan lazyMan; // volatile防止初始化时指令重排
private LazyMan(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (flag == false){
flag = true;
} else {
throw new RuntimeException("不要试图用反射破坏单例");
}
}
}
}
// 双重检测锁模式 DCL懒汉式单例 (double check lock)
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan(); //不是一个原子性操作
/**
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
* 因此可能出现指令重排,
* 在多线程下可能就会出现问题
*/
}
}
}
return lazyMan;
}
}
/**
* 单线程下单例确实没问题
* 多线程并发情况下会出问题,解决:加锁
*
* 反射机制能破解这种普通的单例
*/
class Main{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Constructor<LazyMan> lazyManConstructor = LazyMan.class.getDeclaredConstructor(); //反射获取LazyMan的构造器
lazyManConstructor.setAccessible(true); //将构造器私有性改变
// LazyMan lazyMan1 = lazyManConstructor.newInstance();
// LazyMan lazyMan2 = lazyManConstructor.newInstance();
// System.out.println(lazyMan1);
// System.out.println(lazyMan2); // 此时单例模式就被破坏 可以尝试加一个标志位来判断单例是否已被创建
//但标志位若被发现,仍然可以被破坏
Field flag = LazyMan.class.getDeclaredField("flag");
flag.setAccessible(true); //破坏私有性
LazyMan lazyMan3 = lazyManConstructor.newInstance();
flag.set(lazyMan3,false); //修改标志位的值
LazyMan lazyMan4 = lazyManConstructor.newInstance();
System.out.println(lazyMan3); //JUC.singlemodel.LazyMan@6d6f6e28
System.out.println(lazyMan4); //JUC.singlemodel.LazyMan@135fbaa4
}
}
使用Enum枚举类防止反射破坏单例
由上我们发现普通的单例类能够被反射破解,那么如何防止单例被破坏?
进入反射类的newInstance方法看到如下说明:
因此我们尝试验证用反射去获取一个Enum类的单例
.
通过idea生成的.class文件和javap命令反编译的代码都显示enum声明的类中只包含一个无参构造
Constructor<EnumSingle> enumSingleConstructor = EnumSingle.class.getDeclaredConstructor(null);
enumSingleConstructor.setAccessible(true);
EnumSingle enumSingle1 = enumSingleConstructor.newInstance();
尝试使用反射获取的无参构造方法生成实例对象,却出现如下异常:
说明idea和javap给我们编译的代码是错误的!!!
于是我们使用jad工具反编译EnumSingle.class文件 jad -sjava EnumSingle.class
package JUC.singlemodel;
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(JUC/singlemodel/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i); //这里调用了父类的构造函数
}
public static EnumSingle getInstance()
{
return ENUM_SINGLE;
}
public static final EnumSingle ENUM_SINGLE;
private static final EnumSingle $VALUES[];
static
{
ENUM_SINGLE = new EnumSingle("ENUM_SINGLE", 0);
$VALUES = (new EnumSingle[] {
ENUM_SINGLE
});
}
}
发现实际上在这个所谓的“无参”构造中调用了super的构造方法,即Enum类的构造方法:
.
于是我们尝试使用有参构造去生成一个实例
Constructor<EnumSingle> enumSingleConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
enumSingleConstructor.setAccessible(true);
EnumSingle enumSingle1 = enumSingleConstructor.newInstance();
结果爆出异常如下:
说明了确实不能使用反射去获取Enum类的实例,验证成功!
总结:使用Enum类创建的单例类才是最安全可靠的!
17、深入理解CAS
自旋锁的简单实现
import java.util.concurrent.atomic.AtomicReference;
//自旋锁实现
public class SpinLockDemo {
// Thread null
AtomicReference<Thread> atomicReference = new AtomicReference<>();
// 加锁
public void myLock(){
Thread thread = Thread.currentThread();
//自旋锁
//第一个获取锁的线程能直接获取,无需自旋,成功修改锁值
//锁被占有期间再有一个线程申请锁,锁值不为null,进入自旋,等待锁释放
while (!atomicReference.compareAndSet(null, thread)){
System.out.println(Thread.currentThread().getName()+"=>spin");
}
}
// 解锁
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"=>unLock");
atomicReference.compareAndSet(thread,null);
}
}
import java.util.concurrent.TimeUnit;
public class SpinLockTest {
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock(); //第一次获取锁能默认值为null,能直接获取,并将锁值设为当前线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinLockDemo.myUnLock(); //锁值为当前线程,能成功解锁
}
},"A").start();
new Thread(()->{
spinLockDemo.myLock(); //此时锁仍被线程A占有,锁值不为null,进入自旋,需等待线程A解锁
try {
} catch (Exception e) {
e.printStackTrace();
} finally {
spinLockDemo.myUnLock();
}
},"B").start();
}
}
juc下cas的代码示例
import java.util.concurrent.atomic.AtomicInteger;
public class CasDemo {
// CAS compareAndSwap 比较并交换
public static void main(String[] args) {
// 原子类 底层就是用CAS实现的
AtomicInteger atomicInteger = new AtomicInteger(233); //初始值
// 如果期望值是正确的,那么更新,否则就不更新 CAS是CPU的并发原语
atomicInteger.compareAndSet(233,666); //true
// return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 这个方法是native方法,是C++实现的
System.out.println(atomicInteger.get()); //666
atomicInteger.compareAndSet(233,2333); //此时期望值已不是233,所以更新失败,返回false
}
}
Unsafe类
Atomic类实现原子性的本质就是调用了Unsafe类的native方法,这些native方法都是基于CAS实现的
例如AtomicInteger.getAdnAddInt方法如下
原子性的自增操作,底层使用CAS实现:
标准的CAS自旋锁
CAS缺点:
1、自旋(循环)浪费时间
2、一次性只能保证一个共享变量的原子性
3、ABA问题
什么是ABA问题
什么意思呢?就是说一个线程把数据A变为了B,然后又重新变成了A。此时另外一个线程读取的时候,发现A没有变化,就误以为是原来的那个A。
18、原子引用 解决ABA问题
带版本号的原子引用:AtomicStampedReferenceTest
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceTest {
public static void main(String[] args) {
// 带版本号的原子引用 参数为初始值以及版本号
// 如果引用的泛型是个包装类,注意对象的引用问题
AtomicStampedReference<String> stampedRef = new AtomicStampedReference<>("AAA",1);
new Thread(()->{
int stamp = stampedRef.getStamp();
System.out.println(Thread.currentThread().getName()+"获取版本号:"+ stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
stampedRef.compareAndSet("AAA","BBB",stampedRef.getStamp()
,stampedRef.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"第一次修改后版本号:"+stampedRef.getStamp());
stampedRef.compareAndSet("BBB","AAA",stampedRef.getStamp()
,stampedRef.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"第二次修改后版本号:"+ stampedRef.getStamp());
},"Thread-A").start();
new Thread(()->{
int stamp = stampedRef.getStamp();
System.out.println(Thread.currentThread().getName()+"获取版本号:"+ stamp);
try {
TimeUnit.SECONDS.sleep(2); // 休眠1秒,保证线程A完成ABA操作
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean res = stampedRef.compareAndSet("AAA","BBB",stamp
,stamp+1); // 这里的版本号期望是初始获取的版本号
System.out.println(Thread.currentThread().getName()+"是否修改成功:"+ res);
System.out.println(Thread.currentThread().getName()+"当前实际值:"+ stampedRef.getReference());
},"Thread-B").start();
}
}
这里使用的是AtomicStampedReference的compareAndSet函数,这里面有四个参数:
compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)
(1)第一个参数expectedReference:表示预期值。
(2)第二个参数newReference:表示要更新的值。
(3)第三个参数expectedStamp:表示预期的时间戳。
(4)第四个参数newStamp:表示要更新的时间戳。
输出结果情况:
.
分析:
线程A和B一开始获取的的版本号都为1,之后可以看到线程A先进行了两次修改,值从“AAA”=>“BBB”=>“AAA”,之后线程B进行CAS操作,虽然期望值确实是“AAA”,但是发现版本号已经不是期望的版本号了,说明值已经被修改过,因此更新失败;
注意:
CAS仍旧是compareAndSwap
这里的compareAndSet实质上还是调用了Unsafe类中的CAS native方法
.
.
可能出现的坑:
如果期望值为Integer引用,而值正好比较大时,会出现期望值不正确而修改失败的情况
原因: