目录
1.7 JUC 版的生产者消费者问题 Lock Condition
为什么要用两个if判断呢?用一个if加上synchronized不行吗?下面我们来分析为什么不能用一个if+synchronized。
1:回顾多线程
进程和线程是什么
进程是操作系统分配资源的最小单元,而线程是cpu调度的最小单元。
-
java默认有几个线程
2个,main线程和GC线程(GC垃圾回收机制) -
java可以开启线程么
不能 -
并发和并行
并发,多线程操作同一个资源,cpu单核,模拟多条线程,快速交替
并行,多人一起走,cpu多核,多个线程可以同时执行,线程池
package main;
public class Demo1 {
public static void main(String[] args) {
//获取cpu的核数
//cpu密集型,io密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
示例:
线程有几个状态:
Thread.State
public enum State {
/**
* 新建状态
*/
NEW,
/**
* 运行状态
*/
RUNNABLE,
/**
* 堵塞状态
*/
BLOCKED,
/**
* 等待状态
*/
WAITING,
/**
* 超时等待
*/
TIMED_WAITING,
/**
* 终止状态
*/
TERMINATED;
}
1.1 wait/sleep 的区别
1.来自不同类,wait->Object,sleep->Thread
2.锁的释放,wait->释放锁,sleep->不释放锁
3.使用范围,wait->同步代码块,sleep->任何地方
1.2 synchronized锁
package main;
/*
* 真正的多线程开发,公司中的开发,降低耦合型
* 线程就是一个单独的资源类,没有任何附属的操作!
* 1. 属性 方法
* */
public class TicketSale {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源丢入线程
Ticket ticket = new Ticket();
//@FunctionalInterface 函数式接口,jkd1.8 lambda 表达式(参数)->{代码}
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类OOP
class Ticket{
//属性 方法
private int number = 30;
//卖票的方式
public synchronized void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余"+number);
}
}
}
1.3 Lock 锁
Class ReentrantLock 构造方法 | |
---|---|
public ReentrantLock() | 创建一个ReentrantLock的实例。 这相当于使用ReentrantLock(false) |
public ReentrantLock(boolean fair) | 根据给定的公平政策创建一个 ReentrantLock的实例. fair - true如果此锁应使用合理的订购策略 |
1.3.1 什么是公平锁,非公平锁?
- 非公平锁:可以插队(无参构造方法默认为非公平锁)
- 公平锁:先来后到(有参构造方法传值true时为公平锁)
package main;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 真正的多线程开发,公司中的开发,降低耦合型
* 线程就是一个单独的资源类,没有任何附属的操作!
* 1. 属性 方法
* */
public class TicketSale2 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源丢入线程
Ticket2 ticket = new Ticket2();
//@FunctionalInterface 函数式接口,jkd1.8 lambda 表达式(参数)->{代码}
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 30; 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();//解锁
}
}
}
/*
公平锁
线程1购买了第10张票
线程2购买了第9张票
线程3购买了第8张票
线程1购买了第7张票
线程2购买了第6张票
线程3购买了第5张票
线程1购买了第4张票
线程2购买了第3张票
线程3购买了第2张票
线程1购买了第1张票
非公平锁
线程1购买了第10张票
线程1购买了第9张票
线程1购买了第8张票
线程1购买了第7张票
线程1购买了第6张票
线程1购买了第5张票
线程1购买了第4张票
线程1购买了第3张票
线程1购买了第2张票
线程1购买了第1张票
*/
1.4 synchronized 和 Lock 区别
- lock是一个接口,而synchronized是java的一个关键字。
- synchronized在发生异常时会自动释放占有的锁,因此不会出现死锁;而lock发生异常时,不会主动释放占有的锁,必须手动来释放锁,可能引起死锁的发生。
- Synchronized 可重入锁,不可以中断的,非公平; Lock ,可重入锁,可以判断锁,非公平(可以自己设置);
- Synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码!
- Synchronized 线程1(获得锁,阻塞)、绩程2(等待,傻傻的等) ; Lock锁就不一定会等待下去;
- Synchronized无法判断获取锁的状态,Lock 可以判断是否获取到了锁
synchronized
Lock
1.5 synchronized 版的生产者和消费者
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.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//等待 业务 通知
class Data{//数字 资源类
private int number = 0;
public synchronized void increment() throws InterruptedException {
if (number != 0) {
//等待
this.wait();
}
number++;
//通知其他线程,我+1完毕了
System.out.println(Thread.currentThread().getName()+"-->"+number);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if (number == 0) {
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"-->"+number);
this.notifyAll();
}
}
1.6 防止虚假唤醒问题
四个线程时(两个生产者两个消费者)出现虚假唤醒问题,需要使用while来替换if来判断唤醒后是否可以继续执行
理解 : 拿两个加法线程A、B来说,比如A先执行,执行时调用了wait方法,那它会等待,此时会释放锁,那么线程B获得锁并且也会执行wait方法,两个加线程一起等待被唤醒。此时减线程中的某一个线程执行完毕并且唤醒了这俩加线程,那么这俩加线程不会一起执行,其中A获取了锁并且加1,执行完毕之后B再执行。如果是if的话,那么A修改完num后,B不会再去判断num的值,直接会给num+1。如果是while的话,A执行完之后,B还会去判断num的值,因此就不会执行
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.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//等待 业务 通知
class Data{//数字 资源类
private int number = 0;
public synchronized void increment() throws InterruptedException {
while (number != 0) {
//等待
this.wait();
}
number++;
//通知其他线程,我+1完毕了
System.out.println(Thread.currentThread().getName()+"-->"+number);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while (number == 0) {
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"-->"+number);
this.notifyAll();
}
}
1.7 JUC 版的生产者消费者问题 Lock Condition
await:
signalAll:
package 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 (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//等待 业务 通知
class Data2 {//数字 资源类
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// condition.await();//等待
// condition.signalAll();//唤醒全部
public void increment() throws InterruptedException {
lock.lock();
try {
//业务代码
while (number != 0) {
//等待
condition.await();
}
number++;
//通知其他线程,我+1完毕了
System.out.println(Thread.currentThread().getName() + "-->" + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number == 0) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "-->" + number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
1.8 Condition 实现精准通知唤醒
await:
signal:
package 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();
}
}
class Data3 {// 资源类 Lock
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;
public void printA() {
lock.lock();
try {
//业务,判断->执行->通知
while (number != 1) {
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "--->A");
//唤醒指定的人:B
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
//业务,判断->执行->通知
while (number != 2) {
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "--->B");
//唤醒指定的人:C
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
//业务,判断->执行->通知
while (number!=3){
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"--->C");
number=1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
1.9 八锁现象问题理解
同一把锁:synchronized 锁的对象是方法的调用者(phone)
package lock;
import java.util.concurrent.TimeUnit;
/*
* 1.标准情况下,两个线程先打印发短信还是打电话?1.发短信﹑2.打电话
* 2.sendSms延迟4秒,两个线程先打印 发短信还是 打电话?1.发短信 2.打电话
* */
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
//锁的存在
new Thread(()->{
phone.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者
//两个方法是同一个锁,谁先拿到谁先执行
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
结果:
两个对象不是用一把锁:phone,phone2
package com.example.juc.test;
import java.util.concurrent.TimeUnit;
/*
* 3.增加了一个普通方法,先发短信还是Hello
* 4.两个对象,两个同步方法,先发短信还是先打电话
* */
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
//两个对象,两个调用者,两把锁!
Phone phone = new Phone();
Phone phone2 = new Phone();
//锁的存在
new Thread(()->{
System.out.println(Thread.currentThread().getName());
phone.hello();
phone.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
System.out.println(Thread.currentThread().getName());
phone2.hello();
phone2.call();
},"B").start();
}
}
class Phone{
//synchronized 锁的对象是方法的调用者
//两个方法是同一个锁,谁先拿到谁先执行
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
//这里没有锁,不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
结果:
static静态方法两把锁锁的都是class模板:class phone
Phone唯一的一个Class对象
package com.example.juc.test;
import java.util.concurrent.TimeUnit;
/*
* 5.增加两个静态的同步方法,只有一个对象,先打印 发短信还是打电话
* 6.两个对象!增加两个静态的同步方法, 先打印 发短信还是打电话
* */
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
//两个对象的Class类模板只有一个,static,锁的是Class
Phone phone = new Phone();
Phone phone2 = new Phone();
//锁的存在
new Thread(()->{
phone.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.call();
},"B").start();
}
}
//Phone唯一的一个Class对象
class Phone{
//synchronized 锁的对象是方法的调用者
//static 静态方法
//类一加载就有了!锁的是Class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
结果:
不是同一把锁,A线程的锁是phone类,B线程的锁是phone2
package com.example.juc.test;
import java.util.concurrent.TimeUnit;
/*
* 7.1个静态的同步方法,1个普通的同步方法,1个对象,先打印谁
* 8.1个静态的同步方法,1个普通的同步方法,2个对象,先打印谁
* */
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
//两个对象的Class类模板只有一个,static,锁的是Class
Phone phone = new Phone();
Phone phone2 = new Phone();
//锁的存在
new Thread(() -> {
phone.sendSms();
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
//Phone唯一的一个Class对象
class Phone {
//静态的同步方法 锁的是Class类模板
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通的同步方法 锁的调用者
public synchronized void call() {
System.out.println("打电话");
}
}
结果:
总结:
- new this 具体的一个手机
- static Class 唯一的一个类模板
2:不安全的集合类
2.1 常见的不安全集合类:ArrayList
/**
* .ConcurrentModificationException 并发修改异常
*/
public class test {
public static void main(String[] args) {
//并发情况下 ArrayList是不安全的
List<String> list = new ArrayList<>();
//Vector<String> list = new Vector<>();
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();
}
}
}
在并发情况下报错:
Exception in thread "5" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:907)
at java.util.ArrayList$Itr.next(ArrayList.java:857)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
2.1.1 解决方法:
1:new Vector()
public class test {
public static void main(String[] args) {
Vector<String> list = new Vector<>();
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:Collections.synchronizedList()这个重点
/**
* .ConcurrentModificationException 并发修改异常
*/
public class test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
List<String> list2 = Collections.synchronizedList(list);
for (int i = 1; i <= 10 ; i++) {
new Thread(()->{
list2.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list2);
},String.valueOf(i)).start();
}
}
}
3:CopyOnWriteArrayList<E>
public class test {
public static void main(String[] args) {
List<String> list2 = new CopyOnWriteArrayList();
for (int i = 1; i <= 10 ; i++) {
new Thread(()->{
list2.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list2);
},String.valueOf(i)).start();
}
}
}
为啥这个是安全的?(未完善)
/*
* CopyOnWrite 写入时赋值 COW 计算机程序设计领域的一种优化策略
* 多个线程调用的时候 , list , 读取的时候 , 固定的, 写入(覆盖)
* 在写入的时候避免覆盖 , 造成数据问题!
* 读写分离
*/
1:
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
2:
final void setArray(Object[] a) {
array = a;
}
3:
private transient volatile Object[] array;
为啥不用:Vector 而是选择用 :CopyOnWriteArrayList
1:Vector 被 synchronized 修饰,效率相对低。
2:CopyOnWriteArrayList 的方法 都没被 synchronized 修饰,效率相对较高。
2.2 常见的不安全集合类:HashSet()
public class set集合 {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 1; i <= 30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
报错信息:
Exception in thread "11" Exception in thread "16" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)
at java.util.HashMap$KeyIterator.next(HashMap.java:1466)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
2.2.1 解决方法:
1:Collections.synchronizedSet(new HashSet<>());
public class set集合 {
public static void main(String[] args) {
//Set<String> seobjectst = new HashSet<>();
Set<String> set = Collections.synchronizedSet(new HashSet<>());
for (int i = 1; i <= 30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
1.2 new CopyOnWriteArraySet<>();
public class set集合 {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
HashSet的底层就是HashMap
map的key不允许重复
2.3 常见的不安全集合类:HashMap()
public class map {
public static void main(String[] args) {
Map<String,String> map2 = new HashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
map2.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map2);
},String.valueOf(i)).start();
}
}
}
2.3.1 报错信息
Exception in thread "21" Exception in thread "25" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1442)
at java.util.HashMap$EntryIterator.next(HashMap.java:1476)
at java.util.HashMap$EntryIterator.next(HashMap.java:1474)
at java.util.AbstractMap.toString(AbstractMap.java:554)
at java.lang.String.valueOf(String.java:2994)
2.3.2 解决方法
ConcurrentHashMap<>()
Map<String,String> map2 = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
map2.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map2);
},String.valueOf(i)).start();
}
Collections.synchronizedMap();
Map<String,String> map = new HashMap<>();
Map<String, String> map2 = Collections.synchronizedMap(map);
for (int i = 0; i < 30; i++) {
new Thread(()->{
map2.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map2);
},String.valueOf(i)).start();
}
3:Callable
3.1 怎么启动Callable?
public class test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestA test = new TestA();
FutureTask task = new FutureTask(test); //适配类
new Thread(task,"A").start();
new Thread(task,"B").start(); //结果会被缓存,效率高
/获取callable的返回结果,get方法可能产生阻塞!把他放到最后或者使用异步通信来处理
String o = String.valueOf(task.get()); //获取callable 的返回结果
System.out.println(o);
}
}
class TestA implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("我是好人");
return "100";
}
}
我是好人
100
进程已结束,退出代码0
Runable ----> 实现类:FutureTask---> Callable
4:常用的辅助类
4.1 CountDownLatch
4.1.1 介绍(减法计数器)
允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
4.1.2 简单使用
源码:
/**
* Constructs a {@code CountDownLatch} initialized with the given count.
*
* @param count the number of times {@link #countDown} must be invoked
* before threads can pass through {@link #await}
* @throws IllegalArgumentException if {@code count} is negative
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
//计数器
public class CountDownLatchTestA {
public static void main(String[] args) throws InterruptedException {
//总数是6,必须要执行任务的时候再使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"出去了");
countDownLatch.countDown();//数量-1
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归零,然后再往下执行
System.out.println("人都出去完了,我要关门了");
}
}
打印结果:
0出去了
2出去了
5出去了
1出去了
4出去了
3出去了
人都出去完了,我要关门了
原理:
countDownLatch.countDown();//数量-1
countDownLatch.await();//等待计数器归零,然后再往下执行
每次有线程调用countDown()方法,计数器减1, 假设计算机变为0 ,await()方法就会被唤醒,继续执行!
4.2 CyclicBarrier
4.2.1 介绍(加法计数器)
源码:
//接收一个计数,一个线程参数
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
public class CyclicBarrierTestA {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(7,new Thread(()->{
System.out.println("成功召唤神龙!!!");
}));
for (int i = 1; i <= 7 ; i++) {
final int temp = i;
new Thread(()->{
System.out.println("第"+temp+"颗龙珠收集成功!");
try {
barrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
}
}
}
打印:
第1颗龙珠收集成功!
第7颗龙珠收集成功!
第2颗龙珠收集成功!
第3颗龙珠收集成功!
第6颗龙珠收集成功!
第4颗龙珠收集成功!
第5颗龙珠收集成功!
成功召唤神龙!!!
4.3 Semaphore
4.3.1 介绍(信号量)
源码:
默认是非公平锁,但是可以设置为公平锁
acquire获取资源
release 释放资源
作用:多个资源互斥的使用。并发限流,控制最大的线程数!
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
/**
* Creates a {@code Semaphore} with the given number of
* permits and the given fairness setting.
*
* @param permits the initial number of permits available.
* This value may be negative, in which case releases
* must occur before any acquires will be granted.
* @param fair {@code true} if this semaphore will guarantee
* first-in first-out granting of permits under contention,
* else {@code false}
*/
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
4.3.2 简单使用
public class tWWW {
public static void main(String[] args) {
//线程数量,停车位,限流
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);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();//释放,会将当前的信号量释放+1,然后唤醒等待的线程!
}
},String.valueOf(i)).start();
}
}
}
打印结果如下:
2抢到车位
0抢到车位
1抢到车位
2离开车位
0离开车位
1离开车位
4抢到车位
5抢到车位
3抢到车位
4离开车位
5离开车位
3离开车位
5:读写锁(ReadWriteLock)
5.1 使用
package main;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
* ReadWriteLock
* 读 - 读 可以共存
* 读 - 写 不能共存
* 写 - 写 不能共存
* */
public class ReadWriteLockDemo {
public static void main(String[] args) {
//MyCache myCache =new MyCache();
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 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()+"写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
//取,读 所有人都可以读
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();
//存,写
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入OK");
}
//取,读
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取OK");
}
}
打印结果:
1写入1
1写入OK
2写入2
2写入OK
3写入3
3写入OK
4写入4
4写入OK
5写入5
5写入OK
2读取2
2读取OK
3读取3
3读取OK
5读取5
5读取OK
1读取1
4读取4
4读取OK
1读取OK
6:阻塞队列
6.1:队列
队列(FIFO)先进先出。
写入:如果队列满了,就必须阻塞等待。
读取:如果队列是空的,必须阻塞,等待生产,从而读取消息。
如下图所示:
6.2 :四组API
6.2.1:抛出异常
添加:add()
移除:remove()
判断队首:element()
public class FIFO {
public static void main(String[] args) {
say();
}
public static void say(){
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//Exception in thread "main" java.lang.IllegalStateException: Queue full
//System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
//Exception in thread "main" java.util.NoSuchElementException
//System.out.println(blockingQueue.remove());
}
}
6.2.2:有返回值,不会抛出异常
添加:offer()
移除:poll()
判断队首:peek()
public class FIFO {
public static void main(String[] args) {
say2();
}
public static void say2(){
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.offer("d"));
//移除
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
}
}
打印:
true
true
true
false
a
b
c
null
6.2.3:阻塞等待
取:put()
移除:take()
public class FIFO {
public static void main(String[] args) throws InterruptedException {
say3();
}
public static void say3() 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());
}
}
6.2.4:超时等待
public class FIFO {
public static void main(String[] args) throws InterruptedException {
say4();
}
public static void say4() throws InterruptedException {
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.offer("d",2, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}
}
6.2.5 总结
方法摘要 | |
---|---|
ArrayBlockingQueue(int capacity) | 创建具有给定(固定)容量和默认访问策略的 ArrayBlockingQueue |
boolean add(E e) | 在插入此队列的尾部,如果有可能立即这样做不超过该队列的容量,返回指定的元素 true成功时与抛出 IllegalStateException如果此队列已满。 |
boolean remove(Object o) | 从该队列中删除指定元素的单个实例(如果存在)。 |
boolean offer(E e) | 如果可以在不超过队列容量的情况下立即将其指定的元素插入该队列的尾部,则在成功时 false如果该队列已满,则返回 true |
boolean offer(E e, long timeout, TimeUnit unit) | 在该队列的尾部插入指定的元素,等待指定的等待时间,以使空间在队列已满时变为可用 |
E poll() | 检索并删除此队列的头,如果此队列为空,则返回 null 。 |
E poll(long timeout, TimeUnit unit) | 检索并删除此队列的头,等待指定的等待时间(如有必要)使元素变为可用 |
void put(E e) | 在该队列的尾部插入指定的元素,如果队列已满,则等待空间变为可用 |
E take() | 检索并删除此队列的头,如有必要,等待元素可用 |
E peek() | 检索但不删除此队列的头,如果此队列为空,则返回 null |
6.3: SynchronousQueue(同步队列)
public class 同步队列 {
public static void main(String[] args) {
BlockingQueue<String> queue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"执行了a操作");
queue.put("a");
System.out.println(Thread.currentThread().getName()+"执行了b操作");
queue.put("b");
System.out.println(Thread.currentThread().getName()+"执行了c操作");
queue.put("c");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"A").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"读取了====a操作");
queue.take();
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"读取了====b操作");
queue.take();
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"读取了====c操作");
queue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"B").start();
}
}
打印结果:
A执行了a操作
B读取了====a操作
A执行了b操作
B读取了====b操作
A执行了c操作
B读取了====c操作
7:线程池
三大方法,7大参数,4种拒绝策略
7.1 线程池的好处
1. 降低资源的消耗
2. 提高响应的速度
3. 方便管理
线程复用,可以控制最大并发数,管理线程。
7.1.1 3大方法
public class pool {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();// 缓存线程池(遇强则强)
ExecutorService threadPool2 = Executors.newFixedThreadPool(6);//固定线程池大小
ExecutorService threadPool3 = Executors.newSingleThreadExecutor();//单一线程
try {
for (int i = 0; i < 100; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
threadPool.shutdown();
}
}
}
7.1.2 7大参数
ThreadPoolExecutor
构造方法 :
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
创建一个新的 ThreadPoolExecutor与给定的初始参数。
参数 | |
---|---|
corePoolSize | - (核心线程数)即使空闲时仍保留在池中的线程数,除非设置 allowCoreThreadTimeOut |
maximumPoolSize | - 池中允许的最大线程数 |
keepAliveTime | - 当线程数大于内核时,这是多余的空闲线程在终止前等待新任务的最大时间。 |
unit | - keepAliveTime参数的时间单位 |
workQueue | - 用于在执行任务之前使用的队列。 这个队列将仅保存execute方法提交的Runnable任务。(阻塞队列) |
threadFactory | - 执行程序创建新线程时使用的工厂(线程工厂) |
handler | - 执行被阻止时使用的处理程序,因为达到线程限制和队列容量(拒绝策略) |
7.1.2 四种拒绝策略
RejectedExecutionHandler | |
---|---|
ThreadPoolExecutor.AbortPolicy | 被拒绝的任务的处理程序,抛出一个 RejectedExecutionException 。(银行满了,还有人进来,不处理这个人的,抛出异常) |
ThreadPoolExecutor.CallerRunsPolicy | 一个被拒绝的任务的处理程序,直接在 execute方法的调用线程中运行被拒绝的任务,除非执行程序已被关闭,否则这个任务被丢弃。(哪来回哪去) |
ThreadPoolExecutor.DiscardOldestPolicy | 被拒绝的任务的处理程序,丢弃最旧的未处理请求,然后重试 execute ,除非执行程序被关闭,在这种情况下,任务被丢弃。 |
ThreadPoolExecutor.DiscardPolicy | 被拒绝的任务的处理程序静默地丢弃被拒绝的任务 |
7.1.2.1 代码示例:
/**
* new ThreadPoolExecutor.AbortPolicy() 银行满了,还有人进来,不处理这个人的,抛出异常)
* new ThreadPoolExecutor.CallerRunsPolicy() 哪来的回哪去
* new ThreadPoolExecutor.DiscardOldestPolicy() 队列满了不会抛出异常
* new ThreadPoolExecutor.DiscardOldestPolicy() 队列满了。尝试去跟第一个线程竞争。如果没竞争过,还是回丢弃任务
* 不会抛出异常
*/
public class newPool {
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,5,3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
//最大承载:Queue + max = 5 + 3 = 8
//超过RejectedExecution
for (int i = 0; i < 9 ; i++) {
poolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
poolExecutor.shutdown();
}
}
}
8:了解CPU密集型和IO密集型(池的大小)
package main;
import java.util.concurrent.*;
public class ExecutorServiceDemo {
public static void main(String[] args) {
/*
* 最大线程到底该如何定义
* 1.CPU密集型,几核,最大线程就是几,可以保持CPU的效率最高
* 2.IO密集型 大于你程序中十分耗IO的线程
* */
ExecutorService threadPool = new ThreadPoolExecutor(
2,
Runtime.getRuntime().availableProcessors(),
3,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
try {
//最大承载:Queue + max
//超过RejectedExecution
for (int i = 0; i < 9; i++) {
//使用线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" : ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
}
}
9:四大函数式接口
9.1 Function 函数式接口
注意:传入一个T类型的参数R 返回一个参数R
/*
* 函数式接口:有一个输入参数,有一个输出参数
* */
public class function {
public static void main(String[] args) {
Function<String, String> function = new Function<String, String>() {
@Override
public String apply(String o) {
return o;
}
};
Function<String, String> function2 = (str)->{return str;};
System.out.println(function2.apply("adns"));
}
}
9.2 Predicate 断定型接口
注意:参入一个参数,返回一个布尔类型的 true / false
package fourinterface;
import java.util.function.Predicate;
/*
* 断定型接口:有一个输入参数,返回值只能是 布尔值
* */
public class PredicateDemo {
public static void main(String[] args) {
//判断字符串是否为空
// Predicate<String> predicate = new Predicate<String>() {
// @Override
// public boolean test(String s) {
// return s.isEmpty();
// }
// };
Predicate<String> predicate = (str)->{return str.isEmpty();};
System.out.println(predicate.test(" "));
}
}
9.3 Consumer 消费性接口
package fourinterface;
import java.util.function.Consumer;
/*
* 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("hello");
}
}
9.4 Supplier 供给型接口
package fourinterface;
import java.util.function.Supplier;
/*
* Supplier 供给型接口 :没有参数 , 只有返回值
* */
public class SupplierDemo {
public static void main(String[] args) {
// Supplier supplier =new Supplier() {
// @Override
// public Object get() {
// return 1024;
// }
// };
Supplier supplier = ()->{return 1024;};
System.out.println(supplier.get());
}
}
10 Stream流式计算
方法摘要 | |
---|---|
Stream< T > filter(Predicate< ? super T > predicate) | 返回由与此给定谓词匹配的此流的元素组成的流 |
Stream< T > sorted(Comparator< ? super T > comparator) | 返回由该流的元素组成的流,根据提供的 Comparator进行排序 |
< R > Stream< R > map(Function< ? super T,? extends R > mapper) | 返回由给定函数应用于此流的元素的结果组成的流 |
void forEach(Consumer< ? super T > action) | 对此流的每个元素执行操作 |
Stream< T > limit( long maxSize ) | 返回由此流的元素组成的流,截短长度不能超过 maxSize |
package fourinterface;
import java.util.Arrays;
import java.util.List;
public class StreamDemo {
public static void main(String[] args) {
User u1 = new User(1,"a",21);
User u2 = new User(2,"b",22);
User u3 = new User(3,"c",23);
User u4 = new User(4,"d",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;})
.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);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id ;
private String name;
private int age ;
}
11 ForkJoin(分支合并)
特点:工作窃取
ForkJoinTask
方法摘要 | |
---|---|
ForkJoinTask < V > fork() | 在当前任务正在运行的池中异步执行此任务(如果适用) |
V get() | 等待计算完成,然后检索其结果 |
ForkJoinPool
方法摘要 | |
---|---|
< T > ForkJoinTask< T > submit(ForkJoinTask< T > task) | 提交一个ForkJoinTask来执行 |
LongStream
方法摘要 | |
---|---|
< T > ForkJoinTask< T > submit(ForkJoinTask< T > task) | 提交一个ForkJoinTask来执行 |
LongStream parallel() | 返回平行的等效流 |
static LongStream rangeClosed(long startInclusive, long endInclusive) | 返回有序顺序 LongStream从 startInclusive (含)至 endInclusive通过的递增步长(含) 1 |
long reduce(long identity, LongBinaryOperator op) | 使用提供的身份值和 associative累积功能对此流的元素执行 reduction ,并返回减小的值 |
package forkjoin;
import java.util.concurrent.RecursiveTask;
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;
}
@Override
protected Long compute() {
if ((end-start) < temp ){
Long sum = 0L;
for (Long i = start; i <= end; i++) {
sum += i;
}
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();
}
}
}
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();//5533
//test2();//3709
test3();//222
}
public static void test1(){
Long sum = 0L;
long start = System.currentTimeMillis();
for (Long i = 1L; i <= 10_0000_0000L; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("sum = "+sum+" 时间 : "+(end-start));
}
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L,10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum = "+sum+" 时间 : "+(end-start));
}
public static void test3(){
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 = "+sum+" 时间 : "+(end-start));
}
}
12 异步回调
package main;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class Demo2 {
public static void main(String[] args) throws Exception {
//没有返回值的runAsync 异步回调
// CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"runAsync-->Void");
// });
// System.out.println("1111");
// completableFuture.get();//获得阻塞执行结果
//有返回值的 supplyAsync 异步回调
//分为成功和失败的回调
//失败返回的是错误信息
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync-->Integer");
int i = 10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t,u)->{
System.out.println("t-->"+t);//正常的返回结果
System.out.println("u-->"+u);//错误信息
}).exceptionally((e)->{
System.out.println(e.getMessage());//可获取到错误的返回结果
return 2233;
}).get());
}
}
13 Volatile(可见性)
package main;
import java.util.concurrent.TimeUnit;
public class VolatileDemo {
private static volatile int num = 0;
public static void main(String[] args) throws Exception {
new Thread(()->{
while (num == 0) {//num不加volatile无法跳出循环
}
}).start();
TimeUnit.SECONDS.sleep(1);
num=1;
System.out.println(num);
}
}
不保证原子性
package main;
import java.io.PrintWriter;
//volatile不保证原子性
public class VolatileDemo2 {
private volatile static int num = 0;
public static void add(){
num++;//不是原子性操作,底层分好几步
}
public static void main(String[] args) {
//理论上num结果为2万
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {//main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
不加锁保证原子性
package main;
import java.util.concurrent.atomic.AtomicInteger;
//volatile不保证原子性
public class VolatileDemo2 {
//原子类的Integer
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
//num++;//不是原子性操作,底层分好几步
num.getAndIncrement();//AtomicInteger + 1 方法 ,CAS
}
public static void main(String[] args) {
//理论上num结果为2万
for (int i = 0; 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);
}
}
14 单例模式
饿汉式单例
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 Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式单例
package single;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
//懒汉式单例
//道高一尺魔高一丈
public class LazyMan {
private static boolean code = false;
private LazyMan(){
synchronized (LazyMan.class){
if (code == false){
code = true;
}else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
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 NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// for (int i = 0; i < 10; i++) {
// new Thread(()->{
// LazyMan.getInstance();
// }).start();
// }
//LazyMan instance = LazyMan.getInstance();
Field code = LazyMan.class.getDeclaredField("code");
code.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
code.set(instance2,false);
LazyMan instance3 = declaredConstructor.newInstance();
System.out.println(instance2.hashCode());
System.out.println(instance3.hashCode());
}
}
/*
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
* 123
* 132 A
* B //此时lazyman还没有完成构造
* */
为什么要用两个if判断呢?用一个if加上synchronized不行吗?下面我们来分析为什么不能用一个if+synchronized。
public Singleton getSingleton(){
if(singleton == null){
synchronized(Singleton.class){
singleton = new Singleton();
}
}
return singleton;
}
上面代码中,当多个线程在等待锁的时候,第一个线程抢到锁的线程先执行了 singleton = new Singleton();此时已经创建了一个实例,即singleton !=null。执行完后第一个线程释放了锁,后面的线程抢到了锁,又去执行 singleton = new Singleton(); 又创建了一个实例。这样就破坏了单例的性质,就不是单例模式了。所以抢到锁之后还要判断下singleton是否等于空,为空时才创建对象,不为空时就不创建对象。
所以,DCL懒汉式用了2个if+synchronized来保证线程安全。
静态内部类
package single;
//静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
};
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
详情见:
15 CAS
AS通俗的解释就是:
比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止.
ABA
CAS存在的问题
CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作
ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A - 2B-3A。
public class CASDemo {
//AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
// 正常在业务操作,这里面比较的都是一个个对象
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
// CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("a1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Lock lock = new ReentrantLock(true);
atomicStampedReference.compareAndSet(1, 2,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println("a2=>"+atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3=>"+atomicStampedReference.getStamp());
},"a").start();
// 乐观锁的原理相同!
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("b1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 6,
stamp, stamp + 1));
System.out.println("b2=>"+atomicStampedReference.getStamp());
},"b").start();
}
}