JUC学习大纲
文章目录
- JUC学习大纲
- 前言
- 一、pandas是什么?
- 二、使用步骤
- 总结
- 1、回顾进程线程与并发并行
- 2、Lock锁☆!!!!
- ReentrantLock
- 3、生产者与消费者问题
- 4、8锁现象
- 1、使用两个synchronized锁方法时,先执行哪个方法?
- 2、使用synchronized并且调用普通方法时,是先执行发短信还是hello?
- 3、两个对象,两个同步方法,先执行发短信还是打电话?
- 4、使用static修饰的synchronized方法,一个对象先执行哪个方法?
- 5、使用static修饰的synchronized方法,两个对象先执行哪个方法?
- 6、一个方法使用static synchronized,一个使用普通synchronized,创建一个对象,调用两个方法谁先执行?
- 7、一个方法使用static synchronized,一个使用普通synchronized,创建两个对象,分别调用两个方法谁先执行?
- 8、两个使用static的同步方法,分别在不同的类中,如果同时调用相同的方法,谁先开始执行?
- 9、总结
- 5、集合类不安全
- 6、Callable
- 7、常用的辅助类
- 8、读写锁 ReadWriteLock
- 9、阻塞队列
- 10、线程池☆!!!!
- 11、lambda表达式 ()->
- 13、方法引用 ::
- 14、四大函数式接口(必须掌握)
- 15、stream流式计算
- 16、ForkJoin
- 17、异步回调
- 18 JMM
- 19、Volatile
- 20、彻底玩转单例模式
- 21、深入理解CAS
- 22、各种锁的理解
前言
hi,大家好,此篇笔记是作者通过观看狂神说JUC并且自己查阅一些资料编辑完成的,大家可以观看目录查找自己想要了解的问题,会不定时更新补充,欢迎大家阅读收藏!!
一、pandas是什么?
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
二、使用步骤
1.引入库
代码如下(示例):
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
2.读入数据
代码如下(示例):
data = pd.read_csv(
'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv')
print(data.head())
该处使用的url网络请求的数据。
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。
1、回顾进程线程与并发并行
1、进程与线程
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 */
}
}
}
//native 本地方法,调用底层的c++来操作硬件。java无法直接操作硬件
private native void start0();
2、并发与并行
//获取cpu的核数
System.out.println(Runtime.getRuntime().availableProcessors());
并发编程的本质:充分利用CPU的资源
1、线程有几个状态
public enum State {
//创建态
NEW,
//运行态
RUNNABLE,
//阻塞态
BLOCKED,
//等待态,会一直等下去
WAITING,
//超时等待,等一段时间
TIMED_WAITING,
//终止态
TERMINATED;
}
2、wait/sleep的区别
1、来自不同的类
wait => Object
sleep => Thread
2、关于锁的释放
wait 会释放锁
sleep 不会释放
3、使用的范围是不同的
wait 必须在同步代码块中使用
sleep 可以在任何地方使用
2、Lock锁☆!!!!
1、传统Synchronized
//基本的卖盘例子
/*
* 公司开发中,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作!!!
* 1、属性、方法
* */
public class SaleTicketDemo01 {
public static void main(String[] args) {
Ticket ticket=new Ticket();
new Thread(()->{
for (int i=0;i<60;i++){
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i=0;i<60;i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i=0;i<60;i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类 oop编程
class Ticket{
//属性 方法
private int num=50;
//synchronized 本质:队列,锁
public synchronized void sale(){
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+num--+"张票,剩余:"+num+"张票");
System.out.println();
}
}
}
2、JUC中的lock锁
是一个接口,具有三个实现类
ReentrantLock 可重入锁(最常用的)
ReadLock 读锁
WriteLock 写锁
ReentrantLock
public ReentrantLock() {
//如果无参就为非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
//如果为true,则为公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁:先来先服务
非公平锁:可以抢占资源(默认)
public class SaleTicketDemo02 {
public static void main(String[] args) {
Ticket1 ticket=new Ticket1();
new Thread(()->{
for (int i=0;i<60;i++){
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i=0;i<60;i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i=0;i<60;i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类 oop编程
class Ticket1{
private int num=50;
Lock lock = new ReentrantLock(true);
public void sale(){
try{
lock.lock();
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+num--+"张票,剩余:"+num+"张票");
System.out.println();
}
}finally {
lock.unlock();
}
}
}
3、传统Synchronized与lock锁的区别
1、Synchronized 内置的 java 关键字,Lock锁是一个类
2、Synchronized 无法判断获取锁的状态,Lock 锁可以判断是否获取到了锁
3、Synchronized 会自动释放锁,lock 必须要手动释放锁!!如果不释放锁,会造成死锁问题
4、Synchronized 线程1(获得锁)的话,线程2会一直等待,如果线程1(阻塞),线程2还是会一直等待;lock锁就不一定会等待下去
lock.tryLock();
5、Synchronized 可重入锁,不可中断,非公平的;lock,可重入锁,可以判断锁,可以自己设置公平锁。
Lock lock = new ReentrantLock(true);//公平锁
Lock lock = new ReentrantLock();//非公平锁
6、Synchronized 适合锁少量的代码同步问题,lock 锁适合锁大量的同步代码。
3、生产者与消费者问题
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();
}
}
},"生产者").start();
new Thread(()->{
for (int i=0;i<10;i++){
try {
data.reduce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者1").start();
new Thread(()->{
for (int i=0;i<10;i++){
try {
data.reduce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者2").start();
new Thread(()->{
for (int i=0;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产者2").start();
}
}
class Data{
private int number=0;
public synchronized void increment() throws InterruptedException {
if(number != 0){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
public synchronized void reduce() throws InterruptedException {
if(number == 0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
}
1、虚假唤醒问题解决
上述代码A、B、C、D四个线程,会产生虚假唤醒
为了解决这个问题,我们需要将if判断改为while
class Data{
private int number=0;
public synchronized void increment() throws InterruptedException {
while (number != 0){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
public synchronized void reduce() throws InterruptedException {
while (number == 0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
}
2、JUC版本的生产者与消费者问题
1、synchronized与lock 对线程阻塞与唤醒的不同
synchronized:
wait()//阻塞当前资源
notify()//随机唤醒一个资源
notifyAll()//唤醒所有资源
Lock:
await()//阻塞当前资源
signal()//唤醒当前资源
signalAll()//唤醒所有资源
public class B {
public static void main(String[] args) {
Data1 data=new Data1();
new Thread(()->{
for (int i=0;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产者").start();
new Thread(()->{
for (int i=0;i<10;i++){
try {
data.reduce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者1").start();
new Thread(()->{
for (int i=0;i<10;i++){
try {
data.reduce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者2").start();
new Thread(()->{
for (int i=0;i<10;i++){
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产者2").start();
}
}
class Data1{
private int number=0;
Lock lock=new ReentrantLock();
Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
try {
lock.lock();
while (number != 0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
public void reduce() throws InterruptedException {
try {
lock.lock();
while (number == 0){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
}
上述代码执行出的结果是随机的,如果想要让进程进行有序的执行的话可以使用Condition 进行精准的通知和唤醒线程。
具体操作:
public class C {
public static void main(String[] args) {
//A 执行完调用B,B执行完调用C,C执行完调用A
Data2 data=new Data2();
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 Data2{
private int number=1;
private Lock lock=new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
//业务:判断-> 执行-> 通知
public void printA(){
try {
lock.lock();
while (number!=1){
//等待
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"=>AAAA");
//唤醒指定的人B
number=2;
condition2.signal();
}finally {
lock.unlock();
}
}
public void printB(){
try {
lock.lock();
while (number!=2){
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"=>BBBB");
number=3;
condition3.signal();
}finally {
lock.unlock();
}
}
public void printC(){
try {
lock.lock();
while (number!=3){
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"=>CCCC");
number=1;
condition1.signal();
}finally {
lock.unlock();
}
}
}
运行结果:
4、8锁现象
如何判断锁的是谁!!知道什么是锁,锁的是谁。
通过以下问题了解到什么是锁。
1、使用两个synchronized锁方法时,先执行哪个方法?
public class A {
/* 这个代码运行结果是 1、发短信 2、打电话 原因并不是因为因为 A先执行的,而是因为锁的存在 */
public static void main(String[] args) {
Phone phone=new Phone();// 由于两个方法用的同一个锁,所以谁先拿到谁先执行
new Thread(()->{ phone.senSms(); },"A").start();
try
{
TimeUnit.SECONDS.sleep(1);//阻塞一秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ phone.call(); },"B").start();
}
}
class Phone{
// synchronized 锁的对象是方法的调用者
public synchronized void senSms(){
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
2、使用synchronized并且调用普通方法时,是先执行发短信还是hello?
public class A {
/*
这个代码运行结果是 1、hello 2、发短信
原因:hello方法不是同步方法,不受锁的影响
*/
public static void main(String[] args) {
Phone phone=new Phone();
new Thread(()->{ phone.senSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ phone.hello(); },"B").start();
}
}
class Phone{
// synchronized 锁的对象是方法的调用者
public synchronized void senSms(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
//没有锁,不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
3、两个对象,两个同步方法,先执行发短信还是打电话?
public class A {
/*
这个代码运行结果是 1、打电话 2、发短信
原因:由于两个方法用的不是同一个锁
*/
public static void main(String[] args) {
//两个对象,两个调用者,两把锁
Phone phone1=new Phone();
Phone phone2=new Phone();
new Thread(()->{ phone1.senSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ phone2.call(); },"B").start();
}
}
class Phone{
// synchronized 锁的对象是方法的调用者
public synchronized void senSms(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发短信");
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
//没有锁,不是同步方法,不受锁的影响
public void hello(){
System.out.println(Thread.currentThread().getName()+"hello");
}
}
4、使用static修饰的synchronized方法,一个对象先执行哪个方法?
public class B { public static void main(String[] args) { //一个对象
Phone1 phone1=new Phone1();
new Thread(()->{ phone1.senSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ phone1.call(); },"B").start(); }
}
class Phone1{
//static 类一加载就有了,锁的是Class模板
//因为两个方法都被static修饰了,所以使用的是同一个锁
public static synchronized void senSms(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发短信");
}
public static synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
}
5、使用static修饰的synchronized方法,两个对象先执行哪个方法?
public class B {
public static void main(String[] args) {
//两个对象,两个调用者,两把锁,
//因为是使用的static方法,所以使用的都是一个Class对象,所以先执行A,在B
Phone1 phone1=new Phone1();
Phone1 phone2=new Phone1();
new Thread(()->{ phone1.senSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ phone2.call(); },"B").start();
}
}
class Phone1{
//static 类一加载就有了,锁的是Class模板
//因为两个方法都被static修饰了,所以使用的是同一个锁
public static synchronized void senSms(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发短信");
}
public static synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
}
6、一个方法使用static synchronized,一个使用普通synchronized,创建一个对象,调用两个方法谁先执行?
public class C {
public static void main(String[] args) {
//此方法类似于创建两个对象,使用不同的锁,所以是b先运行,a最后
Phone2 phone1=new Phone2();
new Thread(()->{ phone1.senSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ phone1.call(); },"B").start();
}
}
class Phone2{
//static 类一加载就有了,锁的是Class模板
//一个静态synchronized锁,一个普通的synchronized锁
public static synchronized void senSms(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发短信");
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
}
7、一个方法使用static synchronized,一个使用普通synchronized,创建两个对象,分别调用两个方法谁先执行?
public class C {
public static void main(String[] args) {
//因为是使用的static方法,所以使用的都是一个Class对象
//而因为call方法没有使用static,所以锁所属对象不是Class,所以B先执行
Phone2 phone1=new Phone2();
Phone2 phone2=new Phone2();
new Thread(()->{ phone1.senSms(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ phone2.call(); },"B").start();
}
}
class Phone2{
//static 类一加载就有了,锁的是Class模板
//一个静态synchronized锁,一个普通的synchronized锁
public static synchronized void senSms(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"发短信");
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
}
8、两个使用static的同步方法,分别在不同的类中,如果同时调用相同的方法,谁先开始执行?
public class C {
public static void main(String[] args) {
//因为是使用的static方法,所以使用的都是一个Class对象,所以先执行A,在B
Phone2 phone1=new Phone2();
Phone3 phone2=new Phone3();
new Thread(()->{ phone1.call(); },"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ phone2.call(); },"B").start();
}
}
class Phone2{
public synchronized void call(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"打电话");
}
}
class Phone3{
public static synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
}
9、总结
new this 具体的一个类,也就是说你创建多个对象的话,每个对象的锁互不相关,对象在调用同步方法时,并不会阻塞其他对象调用其他方法。
static Class 唯一的一个模板,如果在同步方法上添加static字段,这个静态同步方法在被调用时,会自己通过反射调用一个Class模板,生成一个当前类static同步方法通用的锁。
5、集合类不安全
1、List 不安全
public class List1 {
//java.util.ConcurrentModificationException 并发修改异常!!
//当我们直接使用ArrayList进行并发操作时,会产生并发修改异常。
public static void main(String[] args) {
//在并发下,ArrayList是不安全的
List<Object> list = new ArrayList<>();
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();
}
}
}
为了解决 ArrayList 的线程不安全问题,我们有以下解决方法:
public class List1 {
//java.util.ConcurrentModificationException 并发修改异常!!
public static void main(String[] args) {
//在并发下,ArrayList是不安全的
/*
* 解决方案:
* 1、使用Vector 去解决并发安全问题
* List<Object> list = new Vector<>();
* 2、通过使用Collections工具类,使ArrayList变的安全
* List<Object> list = Collections.synchronizedList(new ArrayList<>());
* 3、使用JUC包下的CopyOnWriteArrayList<>();
* List<Object> list = new CopyOnWriteArrayList<>();
* CopyOnWrite 写入时复刻 COW 计算机程序设计领域的一种优化
* 原理:
* 多个线程调用的时候,list在读取的时候是固定的,在写入的时候会覆盖
* 而COW在写入的时候避免覆盖造成数据问题,在写入的时候复制一份,
* 复制后给调用者,调用者写完之后再给数据放回去
*
* 为什么不使用vector而使用CopyOnWriteArrayList?
* 1、vector 使用的是synchronized方法,而使用synchronized方法效率很低
* 2、CopyOnWriteArrayList使用的是lock锁
*
* */
// List<Object> list = new Vector<>();
// List<Object> list = new ArrayList<>();
// List<Object> list = Collections.synchronizedList(new ArrayList<>());
List<Object> 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();
}
}
}
2、set 不安全
public class Set1 {
//ConcurrentModificationException 并发修改异常!!
public static void main(String[] args) {
Set<String> set=new HashSet<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
为了使set变为安全的,我们可以使用以下方法:
public class Set1 {
//ConcurrentModificationException 并发修改异常!!
public static void main(String[] args) {
// Set<String> set=new HashSet<>();
// Set<String> set= Collections.synchronizedSet(new HashSet<>());
// Set<String> set=new CopyOnWriteArraySet<>();
//方法原理与list相同
Set<String> set=new CopyOnWriteArraySet<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
3、Map不安全
public class Map1 {
//ConcurrentModificationException 并发修改异常!!
public static void main(String[] args) {
//map 是这样用吗? 不是,工作中不使用HashMap
//默认等价于什么?
// Map<Object, Object> map = new HashMap<>(16,0.75f);//加载因子,初始化容量
Map<Object, Object> map = new HashMap<>();
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();
}
}
}
为了使Map变的更安全,我们需要使用以下方法。
public class Map1 {
//ConcurrentModificationException 并发修改异常!!
public static void main(String[] args) {
//map 是这样用吗? 不是,工作中不使用HashMap
//默认等价于什么?
// Map<Object, Object> map = new HashMap<>(16,0.75f);//加载因子,初始化容量
// Map<Object, Object> map = new HashMap<>();
// 方法:
// Collections.synchronizedMap(new HashMap<>());
// new ConcurrentHashMap<>();//使用并发的hashmap
Map<Object, Object> 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();
}
}
}
6、Callable
1、可以抛出异常
2、可以有返回值
3、方法不同,run()/call()
public class Callable1 {
public static void main(String[] args) {
// new Thread(new Runnable()).start();
// new Thread(new FutureTask<V>()).start();
// new Thread(new FutureTask<V>(Callable)).start();
MyTh myTh=new MyTh();
FutureTask futureTask=new FutureTask(myTh);//适配类
new Thread(futureTask,"A").start();
try {
Integer o = (Integer) futureTask.get();//获取callable的call方法的返回值
//这个get方法会产生阻塞!!!,把他放到最后,或使用异步通信解决
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyTh implements Callable<Integer>{
//这里继承什么属性,call方法的返回值就必须是什么属性
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"call");
return 121;
}
}
如果创建多个多线程同时调用同一个callable
public class Callable1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new Runnable()).start();
// new Thread(new FutureTask<V>()).start();
// new Thread(new FutureTask<V>(Callable)).start();
MyTh myTh=new MyTh();
FutureTask<Integer> futureTask=new FutureTask<>(myTh);
FutureTask<Integer> task[] = new FutureTask[10];
for (int i = 0; i < 10; i++) {
task[i]=new FutureTask<Integer>(myTh);
new Thread(task[i],String.valueOf(i)).start();
}
// new Thread(futureTask,"A").start();
// new Thread(futureTask,"B").start();//结果会被缓存提高效率
int num = 0;
while(num<10){
try {
System.out.println(task[num].get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
num++;
}
//这个get方法会产生阻塞!!!,把他放到最后
}
}
class MyTh implements Callable<Integer>{
//这里继承什么属性,call方法的返回值就必须是什么属性
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"call");
return 121;
}
}
main线程里,有三个线程,三个线程独立,task.get() 只有等待对应线程完成后才会执行、所以最后可以打印出10个结果。而且,效率明显比串行执行10次循环效率高。
7、常用的辅助类
1、CountDownLatch 减法计数器
闭锁,减法计数器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6,必须要去执行任务的时候,再使用
CountDownLatch countDownLatch=new CountDownLatch(6);
for (int i = 1; i < 7; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"go");
countDownLatch.countDown();
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归零,然后再向下执行
// countDownLatch.countDown();//-1
System.out.println("关门");
}
}
原理:
countDownLatch.countDown();//计数器数量-1
countDownLatch.await();//等待计数器归零,然后再执行下面的代码
每次有线程调用countDown();数量-1,假设计数器数量变为0,await();就会被唤醒,然后继续执行
2、CyclicBarrier 加法计数器
java中关于线程的计数器,加法计数器
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;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"完成第"+temp+"个线程");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
上述CyclicBarrier需要产生出7个线程后才会执行其中runnable的方法,如果设置为8个,如果只运行了7个线程,则会一直陷入阻塞状态,等待线程达到8个才会运行runnable的方法。
3、Semaphore
信号量
public class SemaphoreDemo {
public static void main(String[] args) {
//参数: 默认线程数量-->能有的线程数量
//在限流的时候可以使用,每次访问的数量只能有这么多,不能超过,类似停车位。
Semaphore semaphore=new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"得到了线程位置");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//释放资源
}
// acquire() 得到
// release() 释放
},String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire();//拿到通行证/信号量,如果信号量已经被拿空了,则等待,直到释放其他线程信号量
semaphore.release();//释放通行证/信号量
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数。保证服务器的安全与高可用
8、读写锁 ReadWriteLock
读可以被多线程同时读,写的时候只能有一个线程去写
/*
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
* */
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache2 myCache = new MyCache2();
for (int i = 0; i < 5; i++) {
final int temp=i;
new Thread(()->{
myCache.put(temp+"",Thread.currentThread().getName());
},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 MyCache2{
private volatile Map<String,Object> map=new HashMap<>();
private ReadWriteLock lock=new ReentrantReadWriteLock();
//存,在写入时,只希望同时只有一个线程可以写
public void put(String key,Object o){
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,o);
System.out.println(Thread.currentThread().getName()+"写入完毕");
} catch (Exception e){
e.printStackTrace();
} finally {
lock.writeLock().unlock();
}
}
//取,在读取时,所有人都可以读
public void get(String key){
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完毕");
} catch (Exception exception) {
exception.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
}
结果:在写入时,并没有任何插队,而在读取时,有插队现象
9、阻塞队列
1、队列的认识
队列采用先进先出的方式存取数据
阻塞队列:BlockingQueue
阻塞队列与List、Set是同级关系
什么时候我们需要使用阻塞队列?
多线程并发处理,线程池!
2、BlockingQueue阻塞队列的使用
学会使用队列
添加、移除
四组API
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时退出等待 |
---|---|---|---|---|
添加 | add() | off() | put() | off() |
移除 | remove() | poll() | take() | poll() |
检测队首元素 | element() | peek() | 空 | 空 |
方式一:抛出异常
public class BlockingQueueDemo {
public static void main(String[] args) {
/*
Collection:
List
Set
BlockingQueue
*/
test1();
}
/*
抛出异常
*/
public static void test1(){
//队列需要参数,参数是当前队列的大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
// 队列满的时候再添加会抛出异常: Queue full
// System.out.println(arrayBlockingQueue.add("d"));
//element():返回并查看队首的元素
System.out.println(arrayBlockingQueue.element());
//当队列为空时,再弹出队列内容,会报错: NoSuchElementException
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
// System.out.println(arrayBlockingQueue.remove());
//如果队列元素为空,则出现异常:NoSuchElementException
System.out.println(arrayBlockingQueue.element());
}
}
方式二:有返回值,不抛出异常
public class BlockingQueueDemo {
public static void main(String[] args) {
test2();
}
public static void test2(){
//队列需要参数,参数是当前队列的大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
//如果队列满的时候再添加,会返回false并且添加失败
// System.out.println(arrayBlockingQueue.offer("d"));
//peek():返回并查看队首的元素
System.out.println(arrayBlockingQueue.peek());
//poll方法:空参为按队列从头部依次弹出
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
//如果队列空的时候再取出返回null,不会报错
System.out.println(arrayBlockingQueue.poll());
//如果队列空的时候再查看队首返回null,不会报错
System.out.println(arrayBlockingQueue.peek());
}
}
方式三:阻塞等待
/*
* 等待,阻塞(一直等待)
* */
public static void test3() throws InterruptedException {
//队列需要参数,参数是当前队列的大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.put("a");
arrayBlockingQueue.put("b");
arrayBlockingQueue.put("c");
//队列没有位置了,就会一直阻塞
// arrayBlockingQueue.put("d");
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
//队列空了,会一直阻塞,直到队列中有元素
System.out.println(arrayBlockingQueue.take());
}
方式四: 超时退出等待
/*
* 等待,阻塞(等待超时)
* */
public static void test4() throws InterruptedException {
//队列需要参数,参数是当前队列的大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
//阻塞2秒钟之后结束
System.out.println(arrayBlockingQueue.offer("d", 2, TimeUnit.SECONDS));
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
//等待2秒后结束
System.out.println(arrayBlockingQueue.poll(2, TimeUnit.SECONDS));
}
3、同步队列SynchronizedQueue
同步队列没有容量
进去一个元素,必须等待取出来之后,才能再往里面放一个元素!!
public class SynchronizedQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();//同步队列
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+"put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+"put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+": "+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+": "+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+": "+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
输出结果:
T1put 1
T2: 1
T1put 2
T2: 2
T1put 3
T2: 3
10、线程池☆!!!!
池化技术
程序的运行,本质:占用系统的资源!优化CPU资源的使用!
线程池、连接池、内存池、对象池… 创建、销毁线程池十分浪费资源
池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后归还资源。
线程池的好处:
1、降低资源的消耗
2、提高响应的速度
3、方便管理
线程复用、可以控制最大并发数、管理线程
线程池具有:三大方法、7大参数、4种拒绝策略
1、线程池的三大方法
Executors.newSingleThreadExecutor();//单个线程
Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
Executors.newCachedThreadPool();//可伸缩的
//Executors 工具类,有三大方法
//使用线程池之后,要使用线程池来创建线程
public class Demo1 {
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();
}
}
}
2、线程池的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() :7大参数
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;
}
银行实例演示线程池:
手动创建一个线程池
public class Demo1 {
public static void main(String[] args) {
ExecutorService threadPool =new ThreadPoolExecutor(
2,//核心线程池大小
5,//最大线程池大小
3,//存活的时间,超时了没有人调用就会释放
TimeUnit.SECONDS,//超时的单位
new LinkedBlockingDeque<>(3),//阻塞队列,3个队列
Executors.defaultThreadFactory(),//线程工程:创建线程的,一般不用动
new ThreadPoolExecutor.AbortPolicy());
//默认的拒绝策略: 队列满了,如果还有线程进入,则抛出异常
try {
//最大承载Deque+max,如果超出最大承载,会报异常
for (int i = 0; i < 8; i++) {
//使用线程池之后,要使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" OK");
});
}
} finally {
//线程池用完需要关闭线程池
threadPool.shutdown();
}
}
}
3、4种拒绝策略
new ThreadPoolExecutor.AbortPolicy(): 默认的拒绝策略,队列满了,如果还有线程进入,则抛出异常
new ThreadPoolExecutor.CallerRunsPolicy():哪来的回哪去 会让main线程执行
new ThreadPoolExecutor.DiscardPolicy():队列满了,如果还有线程进入,会丢掉任务,不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy():队列满了,会尝试竞争第一个线程,看是否还在运行,如果失败了就没了,如果成功就执行,也不会抛出异常。
4、小结
任务的分类:
池的最大的大小如何去设置
任务的分类:
1、CPU 密集型
既然名字里带有CPU了,说明其消耗的主要资源就是 CPU 了。
电脑是8核的,最多支持8条线程同时执行,所以我们在定义线程池参数时,定义maximumPoolSize时,我们有几核,最多也就能定义几个。
几核就选几这样效率最高
设置线程数时,针对单台机器,最好就是有几个 CPU ,就创建几个线程,然后每个线程都在执行这种任务,永不停歇。
Runtime.getRuntime().availableProcessors();//获取核数
2、IO 密集型
既然名字里带有IO了,说明其消耗的主要资源就是 IO 了。
判断你的程序中十分耗IO的线程的数量,我们需要设置的数量必须大于消耗资源的数量
我们所接触到的 IO ,大致可以分成两种:磁盘 IO和网络 IO。
磁盘 IO ,大多都是一些针对磁盘的读写操作,最常见的就是文件的读写,假如你的数据库、 Redis 也是在本地的话,那么这个也属于磁盘 IO。
网络 IO ,这个应该是大家更加熟悉的,我们会遇到各种网络请求,比如 http 请求、远程数据库读写、远程 Redis 读写等等。
IO 操作的特点就是需要等待,我们请求一些数据,由对方将数据写入缓冲区,在这段时间中,需要读取数据的线程根本无事可做,因此可以把 CPU 时间片让出去,直到缓冲区写满。
11、lambda表达式 ()->
lambda表达式的使用举例:
/*
Lambda表达式的使用
1.举例:(o1,o2) -> Integer.compare(o1,o2);
2.格式:
-> : Lambda操作符 或 箭头操作符
->左边:Lambda形参列表 (其实就是接口中的抽象方法中的形参列表)
->右边:Lambda体 (其实就是重写的抽象方法的方法体)
3.Lambda表达式的使用:(分为六种情况)
总结:
->左边:Lambda形参列表的参数类型可以省略(类型推断);
如果Lambda形参列表只有一个参数,其一对()也可以省略
->右边:Lambda体应该使用一对{}包裹;
如果Lambda体只有一条执行语句(可能是return语句),可以省略这一对{}和 return关键字
4.Lambda表达式的本质:作为函数式接口的实现
5.如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口
接口上面声明注解@FunctionalInterface
*/
public void test1(){
//使用lambda表达式之前
Runnable r1=new Runnable() {
@Override
public void run() {
System.out.println("我爱北京");
}
};
r1.run();
System.out.println("**********************************");
//使用lambda表达式之后
Runnable r2=() -> {System.out.println("我爱故宫");}
//使用lambda表达式之后
Runnable r2=() -> System.out.println("我爱故宫");
r2.run();
}
13、方法引用 ::
/*
方法引用的使用
1.使用情景:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
2.方法引用,本质上就是Lambd表达式,而Lambda表达式作为函数式接口的实例。
使用方法引用,也是函数式接口的实例
3.使用格式: 类(或对象) :: 方法名
4.具体分为如下三种情况:
对象 :: 非静态方法
类 :: 静态方法
类 :: 非静态方法
5.方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与
方法引用的方法的形参列表和返回值类型相同!(针对于情况1与情况2)
*/
情况一:对象::实例方法名
//情况一:对象::实例方法名
//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
@Test
public void test1(){
Consumer<String> con=(s)-> System.out.println(s);
con.accept("北京");
System.out.println("*************************");
PrintStream ps = System.out;
Consumer<String> con1=ps :: println;
con1.accept("beijing");
}
//Supplier中的T get()
//Employee中的String getName()
@Test
public void test2(){
Supplier<String> sup=new Supplier<String>() {
@Override
public String get() {
return null;
}
};
Employee emp=new Employee(1001,"Tom",23,5600);
Supplier<String> sup1=() -> emp.getName();
String s = sup1.get();
System.out.println(s);
Supplier<String> sup2= emp::getName;
String s1 = sup2.get();
System.out.println(s1);
}
情况二:类 :: 静态方法:
//情况二:类 :: 静态方法
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test3(){
Comparator comparator=new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return 0;
}
};
Comparator<Integer> com1=(t1,t2) -> Integer.compare(t1,t2);
System.out.println(com1.compare(12, 21));
Comparator<Integer> com2=Integer::compare;
System.out.println(com2.compare(21, 2));
}
//Function中的R apply(T t)
//Math中的Long round(Double d)
@Test
public void test4(){
Function<Double,Long> function=new Function<Double, Long>() {
@Override
public Long apply(Double o) {
return Math.round(o);
}
};
System.out.println(function.apply(12.1));
Function<Double,Long> f1=d ->Math.round(d);
System.out.println(f1.apply(13.9));
Function<Double,Long> f2=Math::round;
System.out.println(f2.apply(13.2));
}
情况三:类 :: 实例方法:
//情况三:类 :: 实例方法
//Comparator中的int compare(T t1,T t2)
//String中的int t1.compareTo(t2)
@Test
public void test5(){
Comparator<String> comparator=new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
};
Comparator<String> com1=(s1,s2) -> s1.compareTo(s2);
System.out.println(com1.compare("abc", "abd"));
Comparator<String> com2=String::compareTo;
System.out.println(com2.compare("acs", "sadasd"));
}
//BiPredicate中的boolean test(T t1,T t2);
//String中的boolean t1.equals(t2)
@Test
public void test6(){
BiPredicate biPredicate=new BiPredicate() {
@Override
public boolean test(Object o, Object o2) {
return o.equals(o2);
}
};
System.out.println(biPredicate.test("s", "s"));
BiPredicate b1=(o1,o2)-> o1.equals(o2);
System.out.println(b1.test("a", "c"));
BiPredicate b2=Object::equals;
System.out.println(b2.test("a", "a"));
}
//Functiono中的R apply(T t)
//Employee中的String getName();
@Test
public void test7(){
Employee employee=new Employee(123,"Tom",12,123);
Function<Employee,String> function=new Function<Employee, String>() {
@Override
public String apply(Employee employee) {
return employee.getName();
}
};
Function<Employee,String> f1=s -> s.getName();
System.out.println(f1.apply(employee));
Function<Employee,String> f2=Employee::getName;
System.out.println(f2.apply(employee));
}
14、四大函数式接口(必须掌握)
1、什么是函数式接口
我们需要掌握的技术:lambda表达式、链式编程、函数式接口、Stream流式计算
只有一个方法的接口被称为函数式接口@FunctionalInterface
//java具有超级多的@FunctionalInterface应用
//简化编程模型,在新版本的框架底层大量应用!!!
//例如:foreach(消费者类型的函数式接口为参数);
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
/*
java内置的4大核心函数式接口
消费型接口 Consumer<T> void accept(T t)
供给型接口 Supplier<T> T get()
函数型接口 Function<T,R> R apply(T t)
断定型接口 Predicate<T> boolean test(T t)
*/
@Test
public void test1(){
happyTime(500, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
System.out.println("学习太累了,去天上人间消费了"+aDouble);
}
});
happyTime(500, money -> System.out.println("学习太累了,去天上人间喝了口水消费了"+money));
}
public void happyTime(double money, Consumer<Double> con){
con.accept(money);
}
@Test
public void test2(){
List<String> list= Arrays.asList("北京","天津","南京","河南");
List<String> list1 = filterString(list, new Predicate<String>() {
@Override
public boolean test(String s) {
return s.contains("京");
}
});
System.out.println(list1);
List<String> list2 = filterString(list, s -> s.contains("京"));
System.out.println(list2);
}
//根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定
public List<String> filterString(List<String> list, Predicate<String> pre){
ArrayList<String> filterList=new ArrayList<>();
for(String s:list){
if(pre.test(s)){
filterList.add(s);
}
}
return filterList;
}
2、函数型接口 Function
/*
* Function 函数型接口,有一个输入参数,有一个输出
* 只要是 函数式接口 可以用lambda表达式简化
* */
public class Demo1 {
public static void main(String[] args) {
Function<String,Object> function1= new Function<String,Object>() {
@Override
public Object apply(String o) {
return o;
}
};
System.out.println(function1.apply("hhh"));
//使用lambda表达式简化
Function function2 = (str)->{
return str;
};
//使用lambda表达式简化
Function function3 = str-> str;
}
}
3、断定型接口 Predicate
/*
* 断定型接口:有一个输入参数,返回值只能是 布尔值!!
* 只要是 函数式接口 可以用lambda表达式简化
*/
public class Demo2 {
public static void main(String[] args) {
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String str) {
return str.isEmpty();
}
};
System.out.println(predicate.test("asd"));
//使用lambda表达式简化
Predicate<String> predicate1 = (str)->{
return str.isEmpty();
};
//使用lambda表达式简化
Predicate<String> predicate2 = str-> str.isEmpty();
}
}
4、消费型接口 Consumer
/*
* Consumer 消费型接口只有输入,没有返回值
* */
public class Demo3 {
public static void main(String[] args) {
Consumer<String> consumer1 = new Consumer<String>() {
@Override
public void accept(String str) {
System.out.println(str);
}
};
consumer1.accept("hehe");
//使用lambda表达式简化
Consumer<String> consumer2 =(str)->{
System.out.println(str);
};
//使用lambda表达式简化
Consumer<String> consumer3 =str-> System.out.println(str);
}
}
5、供给型接口 Supplier
/*
* 供给型接口 没有参数,只有返回值
* */
public class Demo4 {
public static void main(String[] args) {
Supplier<String> supplier1 = new Supplier<String>() {
@Override
public String get() {
return "hhh";
}
};
System.out.println(supplier1.get());
//使用lambda表达式
Supplier<String> supplier2 =()->{
return "hehe";
};
}
}
15、stream流式计算
/*
1.Stream关注的是对数据的运算,与CPU打交道
集合关注的是数据的存储,是与内存打交道的
2.
stream自己不会存储元素。
stream不会改变源对象。相反,他们会返回一个持有结果的新Stream。
Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
3.Stream 执行流程
① Stream的实例化
② 一系列操作(过滤,映射、...)
③ 终止操作
4.说明:
4.1 一个中间操作链,对数据源的数据进行处理
4.2 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
*/
stream使用案例
/*
* 现在有五个用户!筛选:
* 1、ID 必须是偶数
* 2、年龄必须大于23岁
* 3、用户名转为大写字母
* 4、用户名字母倒着排序
* 5、只输入一个用户
* */
public class Demo1 {
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(5,"e",25);
User u6=new User(6,"f",26);
//集合就是存储
List<User> users = Arrays.asList(u1, u2, u3, u4, u5,u6);
//计算交给stream流
//使用技术:lambda表达式、链式编程、函数式接口、stream流式计算
users.stream().filter(user -> {return user.getId()%2==0;})
//返回由与此给定谓词匹配的此流的元素组成的流。
.filter(user-> user.getAge()>23)
//map返回由给定函数应用于此流的元素的结果组成的流。
.map(user->{return user.getName().toUpperCase(Locale.ROOT);})
.sorted((user1,user2)->{return user2.compareTo(user1);})
//返回由此流的元素组成的流,截短长度不能超过 maxSize 。
.limit(1)
.forEach(System.out::println);
}
}
1、Stream中间操作
1、筛选与切片
@Test
public void test1(){
List<Employee> list=EmployeeData.getEmployees();
// filter(Predicate p):接收Lambda ,从流中排除某些元素
Stream<Employee> stream = list.stream();
System.out.println();
//练习:查询员工表中薪资大于4000的员工信息
stream.filter(e -> e.getSalary() >4000).forEach(System.out::println);
System.out.println();
// limit(long maxsize):截断流,使其元素不超过给定数量
list.stream().limit(3).forEach(System.out::println);
System.out.println();
// skip(long n):跳过元素,返回—个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。
// 与limit(n)互补
System.out.println();
list.stream().skip(3).forEach(System.out::println);
// distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素
System.out.println();
list.add(new Employee(1010,"刘强东",40,8000));
list.add(new Employee(1010,"刘强东",40,8000));
list.add(new Employee(1010,"刘强东",40,8000));
list.stream().distinct().forEach(System.out::println);
}
2、映射
@Test
public void test2(){
//map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
System.out.println();
//练习:获取员工姓名长度大于3的员工的姓名
List<Employee> employees = EmployeeData.getEmployees();
Stream<String> stringStream = employees.stream().map(Employee::getName);
stringStream.filter(name->name.length()>3).forEach(System.out::println);
System.out.println();
//练习2:
Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest1::fromStringToStream);
streamStream.forEach(s ->{
s.forEach(System.out::println);
});
System.out.println();
//flatMap(Function f)
//接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
Stream<Character> characterStream = list.stream().flatMap(StreamAPITest1::fromStringToStream);
characterStream.forEach(System.out::println);
}
//将字符串中的多个字符构成的集合转换成为对应的stream的实例
public static Stream<Character> fromStringToStream(String str){
ArrayList<Character> list=new ArrayList<>();
for(Character c:str.toCharArray()){
list.add(c);
}
return list.stream();
}
3、排序
//排序
@Test
public void test4(){
//sorted()---自然排序
List<Integer> list = Arrays.asList(12, 13, 45, 6454, 21, 42);
list.stream().sorted().forEach(System.out::println);
//抛异常,原因:Employee没有实现Comparable接口
// List<Employee> employees = EmployeeData.getEmployees();
// employees.stream().sorted().forEach(System.out::println);
//sorted(Comparatoe com)--定制排序
List<Employee> employees = EmployeeData.getEmployees();
employees.stream().sorted((e1,e2)-> {
int value= Integer.compare(e1.getAge(),e2.getAge());
if(value!=0){
return value;
}else {
return Double.compare(e1.getSalary(),e2.getSalary());
}
}
).forEach(System.out::println);
}
2、Stream的终止操作
1、匹配与查找
//匹配与查找
@Test
public void test1(){
List<Employee> employees = EmployeeData.getEmployees();
// allMatch(Predicate p)——检查是否匹配所有元素。
//练习:是否所有员工的年龄都大于18
boolean b = employees.stream().allMatch(e -> e.getAge() > 18);
System.out.println(b);
//anyMatch(Predicate P)——检查是否至少匹配一个元素。
// 练习:是否存在员工的工资大于6000
boolean b1 = employees.stream().anyMatch(e -> e.getSalary() > 6000);
System.out.println(b1);
//noneMatch(Predicate p)——检查是否没有匹配的元素。
// 练习:是否存在员工姓“叶”
boolean b2 = employees.stream().noneMatch(e -> e.getName().startsWith("叶"));
System.out.println(b2);
//findFirst——返回第一个元素
Optional<Employee> first = employees.stream().findFirst();
System.out.println(first);
//findAny——返回当前流中的任意元素
Optional<Employee> any = employees.parallelStream().findAny();
System.out.println(any);
//count——返回流中元素的总个数
long count = employees.stream().filter(e -> e.getSalary()>5000).count();
System.out.println(count);
}
@Test
public void test2(){
List<Employee> employees = EmployeeData.getEmployees();
//max(Comparator c)——返回流中的最大值
//练习:返回最高的工资
Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
Optional<Double> max = salaryStream.max(Double::compare);
System.out.println(max);
//min(Comparator c)——返回流中的最小值
//练习:返回最低的工资
Stream<Double> salaryStream1 = employees.stream().map(e -> e.getSalary());
Optional<Double> min = salaryStream1.min(Double::compare);
System.out.println(min);
Optional<Employee> min1 = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(min1);
//forEach(Consumer c)——内部迭代
employees.stream().forEach(System.out::println);
//使用集合的遍历操作
employees.forEach(System.out::println);
}
2、归约
//归约
@Test
public void test3(){
//reduce(T iden, BinaryOperator b)
//可以将流中元素反复结合起来,得到一个值。返回T
//练习1:计算1-10的自然数的和
List<Integer> list= Arrays.asList(1,2,3,4,5,6,7,8);
Integer reduce = list.stream().reduce(0, Integer::sum);
System.out.println(reduce);
//reduce(BinaryOperator b)
//可以将流中元素反复结合起来,得到一个值。返回OptionalT>
//练习2:计算公司所有员工工资的总和
List<Employee> employees = EmployeeData.getEmployees();
Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
// Optional<Double> reduce1 = salaryStream.reduce(Double::sum);
Optional<Double> reduce1 = salaryStream.reduce((d1,d2)->d1+d2);
System.out.println(reduce1.get());
}
3、收集 ☆!!!!
//收集
@Test
public void test4(){
//collect(Collector c)
//将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
// Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。
// 另外,Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
//练习1:查找工资大于6000的员工,结果返回为一个List或Set
List<Employee> employees = EmployeeData.getEmployees();
List<Employee> list = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());
list.forEach(System.out::println);
System.out.println();
List<Employee> employees1 = EmployeeData.getEmployees();
Set<Employee> collect = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
collect.forEach(System.out::println);
}
16、ForkJoin
Fork Join 在JDK1.7,并行执行任务!提高效率。大量数据!
大数据:Map Reduce (把大任务拆分成小任务)
Fork Join特点:工作窃取
在b线程执行完之后,会将a线程中的任务拿过来执行,提高工作效率!
实际使用:
public class Test1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// test1();//Sum=500000000500000000 时间:5879
// test2();//Sum= 500000000500000000 时间:4190
test3();//Sum= 500000000500000000 时间:201
}
//普通求和方法
public static void test1(){
long start = System.currentTimeMillis();
Long sum=0L;
for (Long i = 1L; i <= 10_0000_0000L; i++) {
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("Sum="+sum+" 时间:"+(end-start));
}
//使用ForkJoin调优
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool=new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo1(1L,10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("Sum= "+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= "+sum+" 时间:"+(end-start));
}
}
ForkJoin锁创建的类
/*
* 求和计算的任务
*
* 数据量大的话可以使用ForkJoin/Stream并行流
*
* 如何使用ForkJoin
* 1、ForkJoin通过ForkJoinPool来执行
* 2、计算任务通过 ForkJoinPool.execute(ForkJoinTask<?> task)
* 3、计算类要继承RecursiveTask<?>
*
* */
public class ForkJoinDemo1 extends RecursiveTask<Long> {
private Long start;
private Long end;
//临界值
private Long temp=10000L;
public ForkJoinDemo1(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
Long sum=0L;
if((end-start)<temp){
for (Long i = start; i <= end; i++) {
sum+=i;
}
}else {
//分支合并计算
Long middle = (end + start) / 2;
ForkJoinDemo1 task1 = new ForkJoinDemo1(start, middle);
task1.fork();//拆分任务,把任务压入队列
ForkJoinDemo1 task2 = new ForkJoinDemo1(middle+1,end);
task2.fork();
sum = task1.join() + task2.join();
}
return sum;
}
}
ForkJoin原理是通过递归不断调用自身进行计算,节省时间。
17、异步回调
Future 设计的初衷:对将来的某个事件的结果进行建模
使用CompletableFuture来进行异步回调
没有返回值的runAsync异步回调
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//因为类似于ajax,所以可以有返回值,也可以没有返回值
//没有返回值的话,泛型类就选Void
//发起一个请求
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
System.out.println("11");
completableFuture.get();//获取执行结果
//由于请求在执行时被阻塞,所以11先执行
}
}
有返回值的异步回调
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//有返回值的异步回调
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"completableFuture");
return 111;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("T->" + t);//T 正常运行时的返回结果
System.out.println("U->" + u);//U 是一个错误的信息
//java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
}).exceptionally((e) -> {
e.getMessage();
return 404;
}).get());
}
}
18 JMM
volatile是 java 虚拟机提供的轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
什么是JMM?
JMM:java内存模型,不存在的东西,是一个概念,一种约定!!
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷会主存。
2、线程加锁前,必须读取主存中的最新值到工作内存中!
3、加锁和解锁,必须是同一把锁!!!
JMM规定了内存主要划分为主内存和工作内存两种。此处的主内存和工作内存跟JVM内存划分(堆、栈、方法区)是在不同的层次上进行的,如果非要对应起来,主内存对应的是Java堆中的对象实例部分,工作内存对应的是栈中的部分区域,从更底层的来说,主内存对应的是硬件的物理内存,工作内存对应的是寄存器和高速缓存。
JVM在设计时候考虑到,如果JAVA线程每次读取和写入变量都直接操作主内存,对性能影响比较大,所以每条线程拥有各自的工作内存,工作内存中的变量是主内存中的一份拷贝,线程对变量的读取和写入,直接在工作内存中操作,而不能直接去操作主内存中的变量。但是这样就会出现一个问题,当一个线程修改了自己工作内存中变量,对其他线程是不可见的,会导致线程不安全的问题。因为JMM制定了一套标准来保证开发者在编写多线程程序的时候,能够控制什么时候内存会被同步给其他线程。
内存交互操作
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
-
- lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
-
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
JMM对这八种操作规则和对volatile的一些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安全的了。但是这些规则实在复杂,很难在实践中直接分析。所以一般我们也不会通过上述规则进行分析。更多的时候,使用java的happen-before规则来进行分析
问题:当我们在main线程修改过主内存中的值后,但是由于JMM规范,导致运行中的线程并不知道主线程的值已经被main线程修改,还会继续使用运行时的值,就会产生阻塞问题。
public class JMMDemo {
private static int num = 1;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{ //此线程对主内存的变化是不知道的
while (num==1){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num=0;
System.out.println(num);
}
}
19、Volatile
volatile是 java 虚拟机提供的轻量级的同步机制
1、保证可见性
public class VolatileDemo {
private volatile static int num = 1;
//不加volatile程序就会死循环
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (num==1){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num=0;
System.out.println(num);
}
}
2、不保证原子性
//不保证原子性
public class VDemo2 {
private volatile static int num = 0;
public static void add(){
num++;
}
//理论上,num结果应该为2w,实际结果达不到2w
public static void main(String[] args) {
for (int i = 1; i <= 20 ; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
add();
}
},String.valueOf(i)).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
}
如果不加 Lock锁 和 synchronized,怎么样保证原子性?
通过指令:javap -c 当前文件名.class 进行反编译查看底层代码
我们可以使用原子类解决原子性问题,非常高效,底层都和操作系统挂钩,直接再内存中修改值!!Unsafe类是一个很特殊的存在
//使用原子类解决原子性问题
public class VDemo2 {
//原子类的int
private volatile static AtomicInteger num = new AtomicInteger(0);
public static void add(){
// num++;
num.getAndIncrement();//+1 方法,使用的底层的CAS,效率极高
}
//num结果为2w
public static void main(String[] args) {
for (int i = 1; i <= 20 ; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
add();
}
},String.valueOf(i)).start();
}
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
}
3、禁止指令重排
什么是指令重排?
你写的程序,计算机并不是按照你写的那样去执行的。
源代码–>编译器优化的重排–>指令并行也可以会重排–>内存系统也会重排–>执行
int x = 1;//1
int y = 2;//2
x = x + 5;//3
y = x * x;//4
//我们所期望的执行步骤是 1、2、3、4、5
处理器在进行指令重排的时候,会考虑数据之间的依赖性!!!
volatile可以避免指令重排:
内存屏障。CPU指令。作用:
1、保证特定的操作的执行顺序!
2、可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
20、彻底玩转单例模式
1、饿汉式
2、懒汉式
上述存在线程不安全的问题,我们可以使用DCL懒汉式
public class SingleTest {
Order order=Order.getInstance();
}
class Order{
private Order() {
}
private static Order instance=null;
public static Order getInstance(){
// 方式二:效率比较高 双重检测锁模式的 懒汉式单例 DCL懒汉式
if(instance==null){
synchronized (Order.class) {
if(instance==null){
instance=new Order();
/**
1、分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
我们期待的是123这样顺序执行,
但是有可能执行的是132
如果是132的话,
当a线程进入并且执行到3的时候,会将对象指向这个空间,突然来了一个b线程,
b线程会发现instance!=null,这样就直接return b线程中的结果,
但是此时instance还未进行构造,会产生问题。
为了解决上述问题,我们必须在声明对象时,加上volatile属性。
*/
}
}
}
return instance;
}
}
但是DCL懒汉式还是存在着原子性问题。
使用volatile解决DCL懒汉式中存在的原子性问题
public class SingleTest {
Order order=Order.getInstance();
}
class Order{
private Order() {
}
private volatile static Order instance=null;
public static Order getInstance(){
// 方式二:效率比较高 双重检测锁模式的 懒汉式单例 DCL懒汉式
if(instance==null){
synchronized (Order.class) {
if(instance==null){
instance=new Order();
}
}
}
return instance;
}
}
静态内部类实现懒汉式
//静态内部类实现
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER=new Holder();
}
}
但是我们会发现,我们可以通过反射来破坏这种单例模式,为了解决这种异常,我们可以通过以下方法
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class){
if (lazyMan!=null){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
}
}
}
return lazyMan;
}
}
通过设置字段方式,防止反射破坏单例结构,但是还是不够,如果被获取到自定义的字段名,反射还能继续进行破坏
public class LazyMan {
//红绿灯
private static boolean zhangsan = false;
private LazyMan(){
//三重反射
synchronized (LazyMan.class){
if(zhangsan==false){
zhangsan=true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
private volatile static LazyMan lazyMan;
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 {
// LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan lazyMan = declaredConstructor.newInstance();
LazyMan lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan);
System.out.println(lazyMan2);
}
}
为了防止反射破坏单例模式,我们最终只能使用枚举来组织反射。
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance();
System.out.println(enumSingle);
}
}
我们通过探究源码发现,反射中说明枚举不可以通过反射进行创建对象
Cannot reflectively create enum objects
我们发现在通过idea或者javap进行反编译后中,enum也是一个继承了Enum类的java类,并且拥有空参构造器,但是实际使用时,却发现,空参构造器无法获取,于是我们使用jad进行反编译,发现实际使用的是继承Enum中父类的构造器参数,string与int,于是通过反射进行获取,发现无法获取对象。
3、单例模式的应用场景
单例模式感觉不怎么用到,实际的应用场景有哪些呢?以下,我将列出一些就在咱们周边和很有意义的单例应用场景。
-
Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
-
windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
-
网站的计数器,一般也是采用单例模式实现,否则难以同步。
-
应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
-
Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
-
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
-
多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
-
操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
-
HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
总结以上,不难看出:
单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。
21、深入理解CAS
1、什么是CAS
public class CasDemo1 {
//CAS 就是这个缩写compareAndSet 比较并且交换当前工作内存中的值和主内存中的值,
//如果这个值是期望的,那么则执行!如果不就一直循环
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(2021);
//compareAndSet(int expect, int update) 期望 更新
//如果我期望的值达到了就更新,否则就不更新 CAS 是CPU的并发原语
System.out.println(atomicInteger.compareAndSet(2021, 2022));
System.out.println(atomicInteger.get());
atomicInteger.getAndIncrement();
System.out.println(atomicInteger.compareAndSet(2021, 2022));
System.out.println(atomicInteger.get());
}
}
缺点:
1、循环会耗时间
2、一次只能保证一个共享变量的原子性
3、会产生ABA问题
2、unsafe类
java无法操作内存,但是java可以通过调用C++ 使用native字段,C++可以操作内存,使用unsafe就相当于java的后门,可以通过这个类操作内存。
3、CAS:ABA问题(类似狸猫换太子问题)
public class CasDemo1 {
//CAS 就是这个缩写compareAndSet 比较并且交换
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(2021);
//对于我们我们平时写的sql来说,我们使用的是乐观锁
//compareAndSet(int expect, int update) 期望 更新
//如果我期望的值达到了就更新,否则就不更新 CAS 是CPU的并发原语
//===========捣乱的线程================
System.out.println(atomicInteger.compareAndSet(2021, 2022));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2022, 2021));
System.out.println(atomicInteger.get());
//=============期望的线程==================
System.out.println(atomicInteger.compareAndSet(2021, 2022));
System.out.println(atomicInteger.get());
}
}
为了解决这个ABA问题,我们需要使用原子引用,与乐观锁类似
4、原子引用
解决这个ABA问题,我们需要使用原子引用,与乐观锁类似
public class CasDemo1 {
//CAS 就是这个缩写compareAndSet 比较并且交换
public static void main(String[] args) {
// AtomicInteger atomicInteger=new AtomicInteger(2021);
//对于我们我们平时写的sql来说,我们使用的是乐观锁
//AtomicStampedReference 注意,如果泛型是一个包装类,要注意对象的引用问题
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
//在正常业务操作中,这里面比较的都是一个个对象
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("a1->"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(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,
2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("b2->"+atomicStampedReference.getStamp());
},"b").start();
}
}
结果:
a1->1
b1->1
true
a2->2
true
a3->3
true
b2->4
Integer使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间;
22、各种锁的理解
1、公平锁、非公平锁
Lock lock1 = new ReentrantLock();//非公平锁
Lock lock2 = new ReentrantLock(true);//公平锁
公平锁:顾名思义非常公平,采用先来先服务策略,
//如果参数为true,则为公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
非公平锁:可以允许插队执行,类似短作业优先策略
//非公平锁:
public ReentrantLock() {
sync = new NonfairSync();
}
2、可重入锁
可重入锁(递归锁)
synchronized版本的可重用锁
public class Lock2 {
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()+": 发信息");
call();//这里会获得当前的锁,也就是可重用锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+": 打电话");
}
}
Lock版本的可重用锁:具有细节问题
public class Lock3 {
public static void main(String[] args) {
MobilePhone mobilePhone=new MobilePhone();
new Thread(()->{
mobilePhone.sms();
},"A").start();
new Thread(()->{
mobilePhone.sms();
},"B").start();
}
}
class MobilePhone{
Lock lock =new ReentrantLock();
public void sms(){
lock.lock();
//锁必须配对,否则容易死锁,必须 死锁 配对 开锁
try {
System.out.println(Thread.currentThread().getName()+": 发信息");
call();//这里会获得当前的锁,也就是可重用锁
}finally {
lock.unlock();
}
}
public void call(){
lock.lock();//在这里又锁了一次,实际两把锁,而synchronized只有一把锁
try {
System.out.println(Thread.currentThread().getName()+": 打电话");
}finally {
lock.unlock();
}
}
}
3、自旋锁
SpinLock
底层的标准的自旋锁
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
//自旋锁
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
他会不断的尝试,直到成功位置
自定义自旋锁
//自旋锁
public class Lock4 {
//int 默认为0
//Thread 默认为null
AtomicReference<Thread> atomicReference=new AtomicReference<>();
//加锁
public void myLock(){
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==> mylock");
//自旋锁
while (!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void myUnlock(){
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==> myUnlock");
atomicReference.compareAndSet(thread,null);
}
}
测试:
public class LockTest {
public static void main(String[] args) throws InterruptedException {
// ReentrantLock reentrantLock = new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
//底层使用的自旋锁CAS
Lock4 lock=new Lock4();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception a) {
a.getMessage();
} finally {
lock.myUnlock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception a) {
a.getMessage();
} finally {
lock.myUnlock();
}
},"T2").start();
}
}
t2会等t1解锁后,才能解锁
4、死锁
public class DeathDemo {
public static void main(String[] args) {
String locka="LockA";
String lockb="LockB";
new Thread(new A(locka,lockb),"T1").start();
new Thread(new A(lockb,locka),"T2").start();
}
}
class A implements Runnable{
private String lockA;
private String lockB;
public A(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"lock"+lockA+"get->"+lockB);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"lock"+lockB+"get->"+lockA);
}
}
}
} } } }}
死锁测试,怎么排除死锁?
1、使用 jps -l 定位进程号
2、使用 jstack 进程号 找到死锁问题