大数据学习之路 JUC篇

大数据学习之路 JUC篇(1)

前提说明

本人是一名学生,茫茫it行业的一名卑微的小白,这是我第一次写博客。其原因是学着学着知识发现回顾的时候差不多全忘记了!!为了总结、复习自己以往学到过的有关大数据方面的知识,以便在未来回想自己当初都学过什么的时候,不会那么无助。同时也希望这些知识能够帮助那些在寻求技术之路上的朋友们。也欢迎各位大佬前来纠错指正!

正文

JUC:
java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks

什么是 进程、线程?
参考文章
什么是 并发、并行?
参考文章

线程的几种状态
Thread.State 枚举类查看线程的各种状态
NEW(新建);
RUNNABLE(就绪);
BLOCKED(阻塞);
WAITING(等待)一直等待,不见不散;
TIMED_WAITING(超时等待)等待一定的时候后,不再等待;
TERMINATED(终止)。

wait/sleep 都会导致线程的阻塞,有什么区别呢?
wait放开手去睡,放开手里的锁。
sleep握紧手去睡,醒了手里还有锁

多线程编程的企业级套路+模板

在高内聚低耦合的情况下, 线程 操作(对外暴露的调用方法) 资源类

  1. 高内聚低耦合的前提下,线程操作资源类
  2. 判断+干活+通知
  3. 防止线程的虚假唤醒,只要有wait/await需要用while判断

题目1:三个售票员 卖出 30张票

一言不合 先定义一个资源类。而 高内聚的内涵是: 将对资源类的操作封装在资源类本身中(高内聚)

class Ticket2 //资源类=实例变量+实例方法
{
private int number = 30;

public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "\t 卖出第:" + number-- + "票\t还剩下:"+number+"张票");}
}
}

main()线程中定义三个售票员进行买票
public class SaleTicketDemo01 {
public static void main(String[] args) //main线程,一切程序的入口
{
//创建资源类对象
Ticket2 ticket = new Ticket2();
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 40; j++) {
ticket.sale();
}
}
}, “Thread”+i).start();
}
}
}

换成lambda表达式:
public static void main(String[] args) //main线程,一切程序的入口
{
//创建资源类对象
Ticket2 ticket = new Ticket2();
//使用lambda表达式
new Thread(()->{ for(int i=0;i<40;i++) ticket.sale(); }, “A”).start();
new Thread(()->{ for(int i=0;i<40;i++) ticket.sale(); }, “B”).start();
new Thread(()->{ for(int i=0;i<40;i++) ticket.sale(); }, “C”).start();
}
}

lambda 表达式 口诀(阳哥的口诀就是爽!)

口诀:拷贝小括号;写死右箭头;落地大括号。(参数只有一个的时候可以省略类型和小括号)
lambda 适用于函数式接口(接口中只有一个未实现的抽象方法!)(@FunctionalInterface 函数式接口用此注解进行修饰)
Java8 中 函数式接口允许 有部分方法的实现(使用default 修饰词),也允许静态方法的实现(类名.调用)

@FunctionalInterface
interface Foo {
void sayHello();

default int mul(int x, int y) {
return x * y;
}

default int mul1(int x, int y) {
return x * y;
}

static float div(int a, int b) {
return a / b;
}

static float div2(int a, int b) {
return a / b;
}
}

interface Foo2 {
int add(int x, int y);
}

public class lambda {

public static void main(String[] args) {
Foo foo = new Foo() {
@Override
public void sayHello() {
System.out.println("sayHello方法");
}
};
foo.sayHello();

//转换成lambda表达式
//TODO 口诀:拷贝小括号;写死右箭头;落地大括号
Foo foo1 = () -> {
System.out.println("sayHello方法");
};
foo1.sayHello();
System.out.println(foo1.mul(5, 5));
System.out.println(Foo.div(8, 2));

Foo2 foo2 = new Foo2() {
@Override
public int add(int x, int y) {
return x + y;
}
};
System.out.println(foo2.add(5, 7));
foo2 = (int x, int y) -> {
return x + y;
};
System.out.println(foo2.add(5, 7));

}
}

JAVA四大函数式接口
JAVA四大函数式接口

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class FunctionInterDemo {
    public static void main(String[] args) {
        //TODO 1.消费型接口,有一个输入参数,但是没有返回值,使用accept接受参数
        Consumer<String> consumer = s -> {
            System.out.println(s);
        };
        consumer.accept("消费型接口");
        //TODO 2.供给型接口,有没有输入参数,但是有返回值,使用get获取返回值
        Supplier<String> supplier = () -> { return "供给型接口";};
        System.out.println(supplier.get());
        //TODO 3.断定型接口,有输入参数,有返回值,但是返回值类型必须是boolean,使用test获取返回值
        Predicate<String> predicate = s -> {return s.isEmpty();};
        System.out.println(predicate.test("断定型接口"));
        //TODO 4.函数型接口,有输入参数,有返回值,使用apply获取返回值
        Function<String,Integer> function = s -> {return 1024;};
        System.out.println(function.apply("函数型接口"));
    }
}
结果:
消费型接口
供给型接口
false
1024

资源类中使用JUC中的Lock进行加锁
public void sale() {
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + “\t 卖出第:” + number-- + “票\t还剩下:”+number+“张票”);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

结果
A 卖出第:30票 还剩下:29张票
A 卖出第:29票 还剩下:28张票
A 卖出第:28票 还剩下:27张票
A 卖出第:27票 还剩下:26张票
A 卖出第:26票 还剩下:25张票
A 卖出第:25票 还剩下:24张票
A 卖出第:24票 还剩下:23张票
A 卖出第:23票 还剩下:22张票
A 卖出第:22票 还剩下:21张票
A 卖出第:21票 还剩下:20张票
A 卖出第:20票 还剩下:19张票
A 卖出第:19票 还剩下:18张票
A 卖出第:18票 还剩下:17张票
A 卖出第:17票 还剩下:16张票
A 卖出第:16票 还剩下:15张票
A 卖出第:15票 还剩下:14张票
A 卖出第:14票 还剩下:13张票
A 卖出第:13票 还剩下:12张票
A 卖出第:12票 还剩下:11张票
B 卖出第:11票 还剩下:10张票
B 卖出第:10票 还剩下:9张票
B 卖出第:9票 还剩下:8张票
B 卖出第:8票 还剩下:7张票
B 卖出第:7票 还剩下:6张票
B 卖出第:6票 还剩下:5张票
B 卖出第:5票 还剩下:4张票
B 卖出第:4票 还剩下:3张票
B 卖出第:3票 还剩下:2张票
B 卖出第:2票 还剩下:1张票
B 卖出第:1票 还剩下:0张票

使用synchronize(隐式锁,同步方法或者同步代码块)关键字进行 锁 ,会锁定整个方法体下的所有代码。这时我们使用更加灵活的锁 Lock(显式锁)
JUC中的Lock:ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock
ReentrantLock(可重入锁) 现实生活中的例子:小明上厕所,给门上了一把锁。同时小刚也想上厕所,这时就要等待这个锁解锁。等小明解锁 开门后,小刚也要给门上的锁加锁,出来时也要解锁。(可重入锁)

ReentrantLock构造函数中提供两种锁:创建公平锁和非公平锁(默认)
ReentrantLock有三个内部类 Sync、NonfairSync和FairSync类。
Sync继承AbstractQueuedSynchronized抽象类
NonfairSync(非公平锁)继承Sync抽象类。
FairSync(公平锁)继承Sync抽象类。
公平锁,线程按照发出的请求的顺序获取锁;非公平锁,允许插队。
公平锁上,若有另外一个线程持有锁或有其他线程在等待队列中等待这个锁,那么新发出的请求的线程将被放入到队列中。

非公平锁性能高于公平锁性能的原因
在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟
假设线程 A 持有一个锁,并且线程 B 请求这个锁。由于锁被 A 持有,因此 B 将被挂起。当 A 释放锁时,B 将被唤醒,因此 B 会再次尝试获取这个锁。与此同时,如果线程 C 也请求这个锁,那么 C 很可能会在 B 被完全唤醒之前获得、使用以及释放这个锁。这样就是一种双赢的局面,B 获得锁的时刻并没有推迟,C 更早的获得了锁,并且吞吐量也提高了。

当持有锁的时间相对较长或者请求锁的平均时间间隔较长,应该使用公平锁
公平锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平锁虽然可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量。

**ReadWriteLock(读写锁)**维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有writer,读取锁可以由多个reader线程同时保持。写入锁是独占的。
ReadWriteLock读取操作通常不会改变共享资源,但是执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构。ReadWriteLock能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变形可以完全不需要考虑加锁操作。
写写/读写 需要“互斥(不能共存)
读读 不需要互斥(可以共存)

private ReadWriteLock lock = new ReentrantReadWriteLock();
//读
public void get() {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " : " + number);
} finally {
lock.readLock().unlock();
}
}

//写
public void set(int number) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName());
this.number = number;
} finally {
lock.writeLock().unlock();
}
}

举个例子:资源类MyCache中有一个HashMap,有两个方法:读get方法,写put方法。主线程中有6个写线程,6个读线程。代码如下:

没有加读写锁时代码:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

class MyCache
{
    private volatile Map<String, String> map = new HashMap<>();

    public void put(String key, String value)
    {
        System.out.println(Thread.currentThread().getName()+"写入数据");
        try {
            TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
        map.put(key, value);
        System.out.println(Thread.currentThread().getName()+"写入数据成功!");
    }
    public void get(String key)
    {
        System.out.println(Thread.currentThread().getName()+"读取数据");
        try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
        String s = map.get(""+key);
        System.out.println(Thread.currentThread().getName()+"读取数据成功!"+s);
    }
}

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        // 定义资源类
        MyCache myCache = new MyCache();
        for (int i = 0; i < 6; i++) {
            final int tmpi = i;
            new Thread(()->{
                myCache.put(""+tmpi, String.valueOf(tmpi));
            }, "put"+String.valueOf(i)).start();
        }
        for (int i = 0; i < 6; i++) {
            final int tmpi = i;
            new Thread(()->{
                myCache.get(""+tmpi);
            }, "get"+String.valueOf(i)).start();
        }
    }
}


输出结果:
put0写入数据
put1写入数据
put2写入数据
put3写入数据
put4写入数据
put5写入数据
get0读取数据
get1读取数据
get2读取数据
get3读取数据
get4读取数据
get5读取数据
put0写入数据成功!
put1写入数据成功!
put2写入数据成功!
put4写入数据成功!
get0读取数据成功!0
put5写入数据成功!
put3写入数据成功!
get1读取数据成功!1
get3读取数据成功!3
get2读取数据成功!2
get5读取数据成功!5
get4读取数据成功!4

Process finished with exit code 0

0线程还没有写完成,其他写线程就开始了对这个资源类进行写操作。这样就没法保证这个map的准确性。(类似于数据库中事务的 原子性)

使用读写锁后的代码以及结果:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyCache {
    private volatile Map<String, String> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void put(String key, String value) {
        readWriteLock.writeLock().lock();
        System.out.println(Thread.currentThread().getName() + "写入数据");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入数据成功!");
        readWriteLock.writeLock().unlock();
    }

    public void get(String key) {
        readWriteLock.readLock().lock();
        System.out.println(Thread.currentThread().getName() + "读取数据");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String s = map.get("" + key);
        System.out.println(Thread.currentThread().getName() + "读取数据成功!" + s);
        readWriteLock.readLock().unlock();
    }
}

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        // 定义资源类
        MyCache myCache = new MyCache();
        for (int i = 0; i < 6; i++) {
            final int tmpi = i;
            new Thread(() -> {
                myCache.put("" + tmpi, String.valueOf(tmpi));
            }, "put" + String.valueOf(i)).start();
        }
        for (int i = 0; i < 6; i++) {
            final int tmpi = i;
            new Thread(() -> {
                myCache.get("" + tmpi);
            }, "get" + String.valueOf(i)).start();
        }
    }
}
结果:
put0写入数据
put0写入数据成功!
put1写入数据
put1写入数据成功!
put2写入数据
put2写入数据成功!
put3写入数据
put3写入数据成功!
put4写入数据
put4写入数据成功!
put5写入数据
put5写入数据成功!
get0读取数据
get1读取数据
get2读取数据
get3读取数据
get4读取数据
get5读取数据
get0读取数据成功!0
get1读取数据成功!1
get2读取数据成功!2
get3读取数据成功!3
get4读取数据成功!4
get5读取数据成功!5

结果显示: 读读不互斥(可以同时存在);读写/写写互斥(不能同时存在)

Lock 接口中的 抽象方法
void lock(); //获取锁
void lockInterruptibly() throws InterruptedException; //如果当前线程未被中断,则获取锁
boolean tryLock(); //只有锁在空闲状态时才能获取该锁。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; //锁在给定时间内空闲,且当前线程未被中断,则获取锁
void unlock(); //释放锁
Condition newCondition(); //返回绑定到此Lock实例的新 Condition 实例。

线程间的通信

题目2:现在两个线程,可以操纵初始值为0的一个变量,实现一个线程对该变量加1,一个线程对该变量减1,实现交替,来10轮,变量初始值为0;
题目3:4个线程, 两个加,两个减 ,生产一个消费一个,最终变量初始值为0
针对于题目3,如果使用if来控制wait或者await,可能会产生虚假唤醒的可能
产生虚假唤醒的原因分析

//1. 判断
   if (number != 0)
        {
            condition.await();
        }
        //2. 干活
        number++;
        System.out.println(Thread.currentThread().getName()+"\t"+number);
        //3. 通知
        condition.signalAll();

上述代码想要实现的是 number 加1、减1 循环10轮。但是使用if来进行条件判断时,number的数值可能超过1。其原因如下图
在这里插入图片描述

所以在多线程环境,如果想要防止线程的虚假唤醒,在遇到wait,await的时候,进行条件判断时,使用while循环,每次唤醒后将当前线程重新拉回后再进行标志位判断(类似于重新安检),进而防止上述线程的虚假唤醒。

完整代码如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//资源类
class Aircondition {
    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void increament1() throws Exception {
        lock.lock();
        try {
            //1. 判断
            while (number != 0)
            {
                condition.await();
            }
            //2. 干活
            number++;
            System.out.println(Thread.currentThread().getName()+"\t"+number);
            //3. 通知
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void decrement1() throws Exception
    {
        lock.lock();
        try
        {
            // 判断
            while (number == 0)
            {
                condition.await();
            }
            // 干活
            number--;
            System.out.println(Thread.currentThread().getName()+"\t"+number);
            // 通知
            condition.signalAll();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }


    public synchronized void increment() throws Exception {
        //1 判断
//        if(number >=2)
//        {
//            this.wait();  //等待
//        }
        //TODO 多个线程同一个操作时,可能导致虚假唤醒, 可通过while解决
        while (number > 0) {
            this.wait();
        }
        //2 干活
        this.number++;
        //干完了吆喝一声
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        //3 通知
        this.notifyAll();  //唤醒其他所有线程(通知)

    }

    public synchronized void decrement() throws Exception {
        //1 判断
        while (number == 0) {
            this.wait();
        }
        //2 干活
        this.number--;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        //3 通知
        this.notifyAll();
    }
}


public class CommunicationBetweenThread {
    public static void main(String[] args) throws Exception {
        Aircondition aircondition = new Aircondition();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
//                    aircondition.increment();
                    aircondition.increament1();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "Producer1").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
//                    aircondition.increment();
                    aircondition.increament1();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "Producer2").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
//                    aircondition.decrement();
                    aircondition.decrement1();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "Consumer1").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
//                    aircondition.decrement();
                    aircondition.decrement1();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "Consumer2").start();
    }
}
上述代码运行结果如下:
Producer1	1
Consumer1	0
Producer1	1
Consumer1	0
Producer1	1
Consumer1	0
Producer1	1
Consumer1	0
Producer1	1
Consumer1	0
Producer1	1
Consumer1	0
Producer1	1
Consumer1	0
Producer1	1
Consumer1	0
Producer1	1
Consumer1	0
Producer1	1
Consumer1	0
Producer2	1
Consumer2	0
Producer2	1
Consumer2	0
Producer2	1
Consumer2	0
Producer2	1
Consumer2	0
Producer2	1
Consumer2	0
Producer2	1
Consumer2	0
Producer2	1
Consumer2	0
Producer2	1
Consumer2	0
Producer2	1
Consumer2	0
Producer2	1
Consumer2	0

Process finished with exit code 0

synchronize与lock(Condition)在线程通信(等待和唤醒)中,写法的区别
在这里插入图片描述
synchronized 与 wait() nofity() notifyAll() 配套
Lock 与Condition对象与 await() signal() signalAll()配套

为什么会出现老版本和新版本的写法(wait–await;notify–signal)?

Condition可以实现唤醒特定的线程(精确打击;精确控制:实现线程之间的按顺序调用)

condition 类似于备用钥匙,一把锁可以有多个备用钥匙。

题目:多线程之间按顺序调用, 实现 A线程->B线程->C线程。
三个线程启动,要求如下:
AA打印5次, BB打印10次,CC打印15次
接着
AA打印5次, BB打印10次,CC打印15次
来10轮
在这里插入图片描述

完整代码如下:


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ShareData {
    private int number = 1; //A:1,B:2, C:3
    private Lock lock = new ReentrantLock();
    // 一把锁开多份备用钥匙
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print_c(int count) {
        lock.lock();
        switch (count) {
            case 5: {
                try {
                    //1. 判断
                    while (number != 1) {
                        c1.await();
                    }
                    //2. 干活
                    for (int i = 0; i < count; i++) {
                        System.out.println(Thread.currentThread().getName() + "\t" + i);
                    }
                    //3. 先修改标志位再通知
                    number = 2;
                    c2.signal();  //TODO 精确打击,只唤醒 c2
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
                break;
            }
            case 10: {
                try {
                    //1. 判断
                    while (number != 2) {
                        c2.await();
                    }
                    //2. 干活
                    for (int i = 0; i < count; i++) {
                        System.out.println(Thread.currentThread().getName() + "\t" + i);
                    }
                    //3. 先修改标志位再通知
                    number = 3;
                    c3.signal();  //TODO 精确打击,只唤醒 c3
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
                break;
            }
            case 15: {
                try {
                    //1. 判断
                    while (number != 3) {
                        c3.await();
                    }
                    //2. 干活
                    for (int i = 0; i < count; i++) {
                        System.out.println(Thread.currentThread().getName() + "\t" + i);
                    }
                    //3. 先修改标志位再通知
                    number = 1;
                    c1.signal();  //TODO 精确打击,只唤醒 c1
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
                break;
            }
        }


    }
}

public class ConditionDemo {
    public static void main(String[] args) {
        ShareData sd=new ShareData();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{sd.print_c(5);}, "AA").start();
            new Thread(()->{sd.print_c(10);}, "BB").start();
            new Thread(()->{sd.print_c(15);}, "CC").start();
        }
    }
}

lock 配备了三个condition:c1,c2,c3;分别用来控制AA,BB,CC线程的执行顺序。
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();

number为标志位(判断等待(await)的条件):1–AA;2–BB;3–CC
number!=1时,AA线程通过c1 condition实现等待
while (number != 1) {
c1.await();
}
number!=2时,BB线程通过c2 condition实现等待
while (number != 2) {
c2.await();
}
number!=3时,CC线程通过c3 condition实现等待
while (number != 3) {
c3.await();
}
当number=1时,AA线程打印5次,然后修改标志位number=2,通过c2 condition唤醒BB线程。
当number=2时,AA线程打印10次,然后修改标志位number=3,通过c3 condition唤醒CC线程。
当number=3时,AA线程打印15次,然后修改标志位number=1,通过c1 condition唤醒AA线程。



两轮结果如下:
AA	0
AA	1
AA	2
AA	3
AA	4
BB	0
BB	1
BB	2
BB	3
BB	4
BB	5
BB	6
BB	7
BB	8
BB	9
CC	0
CC	1
CC	2
CC	3
CC	4
CC	5
CC	6
CC	7
CC	8
CC	9
CC	10
CC	11
CC	12
CC	13
CC	14
AA	0
AA	1
AA	2
AA	3
AA	4
BB	0
BB	1
BB	2
BB	3
BB	4
BB	5
BB	6
BB	7
BB	8
BB	9
CC	0
CC	1
CC	2
CC	3
CC	4
CC	5
CC	6
CC	7
CC	8
CC	9
CC	10
CC	11
CC	12
CC	13
CC	14

多线程的 8锁现象

假设目前有一个 手机phone 资源类。这个资源类 可以 发短信 和 发邮件

第一种锁的情况:标准访问(先打印Email还是SMS)

package org.JVM_JUC;
class Phone1{
    public synchronized void sendEmail(){
        System.out.println("----------sendEmail----------");
    }
    public synchronized void sendSMS(){
        System.out.println("----------sendSMS----------");
    }
}
public class New8Lock {

    public static void main(String[] args) {
        Phone1 phone1 = new Phone1();
        for (int i = 0; i < 10; i++) {

            new Thread(()->{
                phone1.sendEmail();
            }, "A").start();
            new Thread(()->{
                phone1.sendSMS();
            }, "B").start();
        }
    }
}

请问先调用哪个方法:是先发Email还是先发SMS?
答:;这个由 操作系统来控制

结果如下:
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendSMS----------

Process finished with exit code 0

为了保证能让Email先执行,SMS后执行,中间sleep一下

public class New8Lock {

    public static void main(String[] args) throws InterruptedException {
        Phone1 phone1 = new Phone1();
        for (int i = 0; i < 10; i++) {

            new Thread(()->{
                phone1.sendEmail();
            }, "A").start();

            Thread.sleep(100);


            new Thread(()->{
                phone1.sendSMS();
            }, "B").start();
        }
    }
}

第二种锁的情况:sendEmail方法中sleep 4秒,这时谁先打印,谁后打印?

资源类代码
class Phone2{
    public synchronized void sendEmail(){
        // 暂停一会儿线程
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("----------sendEmail----------");
    }
    public synchronized void sendSMS(){
        System.out.println("----------sendSMS----------");
    }
}

main方法核心代码
Phone2 phone2 = new Phone2();
for (int i = 0; i < 10; i++) {

            new Thread(()->{
                phone2.sendEmail();
            }, "A").start();

            Thread.sleep(100);


            new Thread(()->{
                phone2.sendSMS();
            }, "B").start();
        }

分析:由于sleep不释放对象的锁,所以依然按顺序执行

运行结果
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------

第三种锁的情况:新增一个普通方法sayHello方法,sendEmail方法依然睡眠4秒,这时先打印那个方法中的内容?

资源类代码如下:
class Phone3{
    public synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("----------sendEmail----------");
    }
    public synchronized void sendSMS(){
        System.out.println("----------sendSMS----------");
    }
    public void sayHello(){
        System.out.println("----------sayHello----------");
    }
}
mian方法核心代码
Phone3 phone3 = new Phone3();
        for (int i = 0; i < 10; i++) {

            new Thread(()->{
                phone3.sendEmail();
            }, "A").start();

            Thread.sleep(100);


            new Thread(()->{
                phone3.sayHello();
            }, "B").start();
        }

资源类非同步方法先打印,睡眠的sendEmail后打印

----------sayHello----------
----------sayHello----------
----------sayHello----------
----------sayHello----------
----------sayHello----------
----------sayHello----------
----------sayHello----------
----------sayHello----------
----------sayHello----------
----------sayHello----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------

Process finished with exit code 0

第四种锁的情况:两部手机,一个线程操作一部手机发邮件,另一个线程操作另一部手机发短信,这时哪个先打印,哪个后打印?

main核心代码:
 Phone1 p1 = new Phone1();
        Phone1 p2 = new Phone1();
        for (int i = 0; i < 10; i++) {

            new Thread(()->{
                p1.sendEmail();
            }, "A").start();

            Thread.sleep(100);


            new Thread(()->{
                p2.sendSMS();
            }, "B").start();
        }
    }
由于不存在资源竞争,所以按顺序执行
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------

Process finished with exit code 0

第五种锁的情况:同一部手机,两个静态同步方法,请问先打印邮件还是先打印短信?

class Phone4{
    public static synchronized void sendEmail(){
        System.out.println("----------sendEmail----------");
    }
    public static synchronized void sendSMS(){
        System.out.println("----------sendSMS----------");
    }
}
main核心代码
Phone4 p4 = new Phone4();
        for (int i = 0; i < 10; i++) {

            new Thread(()->{
                p4.sendEmail();
            }, "A").start();

            Thread.sleep(100);


            new Thread(()->{
                p4.sendSMS();
            }, "B").start();
        }

静态方法加锁,锁的是整个类,由于两个线程之间有sleep,强制使得两个线程按顺序执行。

----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------

Process finished with exit code 0

第六种锁的情况:两部手机,两个静态同步方法,请问先打印邮件还是先打印短信?

Phone4 p4 = new Phone4();
        Phone4 p41 = new Phone4();
        for (int i = 0; i < 10; i++) {

            new Thread(()->{
                p41.sendEmail();
            }, "A").start();

            Thread.sleep(100);


            new Thread(()->{
                p4.sendSMS();
            }, "B").start();
        }
结果:
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------
----------sendEmail----------
----------sendSMS----------

Process finished with exit code 0
与第五种相同,因为静态方法加锁锁的是整个类,而不是单独的对象。

第七种锁的情况:一个普通同步方法,一个静态同步方法,一部手机,请问先打印邮件还是短信?

class Phone5{
    public static synchronized void sendEmail(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"睡眠"+4+"秒后");
        System.out.println("----------sendEmail----------");
    }
    public synchronized void sendSMS(){
        System.out.println("----------sendSMS----------");
    }
}

main核心方法:
Phone5 phone5 = new Phone5();
        for (int i = 0; i < 10; i++) {

            new Thread(()->{
                phone5.sendEmail();
            }, "A").start();

            Thread.sleep(100);


            new Thread(()->{
                phone5.sendSMS();
            }, "B").start();
        }
结果:
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------

Process finished with exit code 0

第八种锁的情况:一个普通同步方法,一个静态同步方法,两部手机,请问先打印邮件还是短信?

Phone5 phone5 = new Phone5();
        Phone5 phone51 = new Phone5();
        for (int i = 0; i < 10; i++) {

            new Thread(()->{
                phone5.sendEmail();
            }, "A").start();

            Thread.sleep(100);


            new Thread(()->{
                phone51.sendSMS();
            }, "B").start();
        }
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
----------sendSMS----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------
A睡眠4秒后
----------sendEmail----------

Process finished with exit code 0

八锁 结果分析

对于 第一种和第二种锁:
一个对象里面如果有多个synchronize方法,某个时刻内,只要一个线程去调用其中的一个synchronize方法了,
其他的线程都只能等待,换句话说,某个时刻内,只能有唯一一个线程去访问这些synchronize方法
锁的是当前对象this,被锁定后,其他的线程都不能进入到当前对象的其他的synchronize方法。

对于 第三种锁:
生活中的例子: 班长要用手机发邮件, 学委只要手机壳, 所以不参加资源竞争。
添加的普通方法与同步锁无关

对于 第四种锁
换成两个对象后,不是同一把锁了,没有资源竞争

对于 第五种和第六种锁
static 锁的是全局锁,而不是对象锁(对于同一个类的不同对象,static空间 只有一份)
synchronize实现同步的基础:Java中的每一个对象都可以作为锁。
具体表现为一下3中形式:
1.对于普通的同步方法,锁是当前实例对象(对象锁)
2.对于同步方法块,锁是synchronize括号中配置的对象
3.对于静态同步方法,锁的是整个类(全局锁), 该类的所有实例对象都会受到该锁的限制

对于 第七种和第八种锁
静态同步方法与非静态同步方法之间是不会有竞争条件的(锁不一样)
但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁

Java 多线程的实现(操作类有几种)?

老版本中有2种;新版本中有4种。

  1. 继承Thread类,实现run方法
  2. 实现Runnable接口,实现run方法
  3. 实现Callable接口,实现call方法。注意:新建Thread的时候,Thread的构造方法中没有接收Callable的。(中间商赚差价!!!)所有我们需要找到一个既可以联系Runnable接口又联系Callable接口的类(FutureTask))
  4. 线程池 ExecutorService(ThreadPoolExcutor类)

Callbale接口 实现多线程

class MyThread implements Runnable
{
    @Override
    public void run() {
        System.out.println("Runnable 的 run方法");
    }
}

class MyThread2 implements Callable<Integer>
{
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable 的 call方法正在执行。。。。");
        return 1024;
    }
}

Callable接口与Runnable接口的区别:

  1. Callable接口的call方法有返回值;而Runnable的run方法没有返回值。
  2. call会抛出异常;而run方法不会抛出异常。
  3. 实现方法不同。一个是call方法,一个是run方法。
完整代码:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyThread implements Runnable
{
    @Override
    public void run() {
        System.out.println("Runnable 的 run方法");
    }
}
class MyThread2 implements Callable<Integer>
{
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable 的 call方法正在执行。。。。");
        return 1024;
    }
}



public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new MyThread2());
        new Thread(futureTask, "CallableThread").start();
        Integer result = (Integer) futureTask.get(); //获取线程的call方法的返回值
        System.out.println(result);
        new Thread(new MyThread(), "RunnableThread").start();
    }
}

结果:
Callable 的 call方法正在执行。。。。
1024
Runnable 的 run方法

futureTask.get()方法可以获取call方法的返回值
为什么要使用Callable接口?
在这里插入图片描述

Callable细节

get() 方法获取线程的返回值时,一般放在最后。因为调用了get()方法,则会阻塞线程。一直等待线程计算完成后才能执行下面的后续代码。

futureTask.get() 方法放到最后的结果
class MyThread2 implements Callable<Integer>
{
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable 的 call方法正在执行。。。。");
        TimeUnit.SECONDS.sleep(4);
        System.out.println(Thread.currentThread().getName()+"睡了"+"4s后"+"返回数值");
        return 1024;
    }
}



public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new MyThread2());
        new Thread(futureTask, "CallableThread").start();

        System.out.println("main线程:futureTask线程已经start");

        Integer result = (Integer) futureTask.get(); //获取线程的call方法的返回值
    }
}

结果:main线程会先打印"main线程:futureTask线程已经start"
main线程:futureTask线程已经start
Callable 的 call方法正在执行。。。。
CallableThread睡了4s后返回数值

Process finished with exit code 0
futureTask.get()方法放到前面的结果:
class MyThread2 implements Callable<Integer>
{
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable 的 call方法正在执行。。。。");
        TimeUnit.SECONDS.sleep(4);
        System.out.println(Thread.currentThread().getName()+"睡了"+"4s后"+"返回数值");
        return 1024;
    }
}



public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new MyThread2());
        new Thread(futureTask, "CallableThread").start();
        Integer result = (Integer) futureTask.get(); //获取线程的call方法的返回值
        System.out.println("main线程:futureTask线程已经start");
    }
}

Callable 的 call方法正在执行。。。。
CallableThread睡了4s后返回数值
main线程:futureTask线程已经start
Process finished with exit code 0

同一个futureTask对象只能被线程调用一次,当有新的线程调用了已经被调用过的futureTask对象时,这次只会复用上次的结果。(只会执行一次)

举个例子:老师上课途中,让班长去买水。班长买水的过程中,老师继续讲课,两个线程同时进行(第一次让班长买水,合情合理)。上课途中:学委又让班长第二次买水(有点过分了,班长也要听课!),这时班长不会再次买水。(JAVA中充满着人生的哲学

class MyThread2 implements Callable<Integer>
{
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable 的 call方法正在执行。。。。");
        TimeUnit.SECONDS.sleep(4);
        System.out.println(Thread.currentThread().getName()+"睡了"+"4s后"+"返回数值");
        return 1024;
    }
}



public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new MyThread2());
        new Thread(futureTask, "AA").start();
        new Thread(futureTask, "BB").start();
        System.out.println("main线程:futureTask线程已经start");
        Integer result = (Integer) futureTask.get(); //获取线程的call方法的返回值
    }
}
结果:
main线程:futureTask线程已经start
Callable 的 call方法正在执行。。。。
AA睡了4s后返回数值

Process finished with exit code 0

JUC强大的辅助类

countDownLatch类

countDownLatch类:使一个线程等待其他线程各自执行完毕后再执行。
它是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
CountDownLatch 类的原理
CountDownlatch类主要有两个方法:当一个线程或多个线程调用await方法时,这些线程会被阻塞。
其他线程调用countDown方法时将计数器减一(线程不会被阻塞)
当计数器为0时,因await方法阻塞的线程会被唤醒,继续执行。

举个例子:班级里面有7名学生,包括一名班长;等待其他6名学生上完自习离开教室后,班长负责锁门。

正常不使用countDownLatch工具类时:
public class CountDownLatchDemo {
    public static void main(String[] args) {
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"上完自习,离开教室");
            }, String.valueOf(i)).start();
        }
        System.out.println("班长离开教室并锁上了门");
    }
}
结果:
1上完自习,离开教室
班长离开教室并锁上了门
0上完自习,离开教室
2上完自习,离开教室
3上完自习,离开教室
4上完自习,离开教室
5上完自习,离开教室
班长提前离开了教室并锁上了门,剩余的5名同学只能被困在教室中做多人运动!!
使用了CountDownLatch辅助类之后:
import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6); //设置需要等待的线程数
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"上完自习,离开教室");
                countDownLatch.countDown(); //每个线程执行完毕后,计数器减一
            }, String.valueOf(i)).start();
        }
        countDownLatch.await(); //当计数器数值不为0的时候会一直的等待(阻塞),另一个线程无法执行,直至计数器为0;
        System.out.println("班长离开教室并锁上了门");
    }
}

结果:
1上完自习,离开教室
0上完自习,离开教室
3上完自习,离开教室
2上完自习,离开教室
4上完自习,离开教室
5上完自习,离开教室
班长离开教室并锁上了门(防止了多人运动的发生!!!)
CyclicBarrier类

一句话概括CyclicBarrier类的作用:集齐七颗龙珠—方可召唤神龙
CountDownLatch类是 等所有的线程都执行完成后再执行最后的线程。而CyclicBarrier类实现的是 只有一定量的线程启动了才执行后续的线程。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("七颗龙珠已经集齐,召唤神龙");
        });
        for (int i = 1; i <= 7; i++) {
            final int tmpi = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"集齐"+tmpi+"星龙珠!");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, "线程"+String.valueOf(i)).start();
        }
    }
}
结果:
线程1集齐1星龙珠!
线程2集齐2星龙珠!
线程5集齐5星龙珠!
线程7集齐7星龙珠!
线程4集齐4星龙珠!
线程3集齐3星龙珠!
线程6集齐6星龙珠!
七颗龙珠已经集齐,召唤神龙

CyclicBarrier类与CountDownLatch类的区别

CountDownLatchCyclicBarrier
减计数方式加计数方式
计算为0时释放所有等待的线程计数达到指定值时释放所有等待线程
计数为0时,无法重置计数达到指定值时,计数置为0重新开始
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞
不可重复利用可重复利用

现实生活中的例子:王者荣耀,LOL中,加载时,双方10人,都要等他们加载到100%的时候才能开始游戏

更具体的使用可以参考这位大神的文章:CyclicBarrier 使用详解

Semaphore类(信号量:类似于pv操作)

现实生活中的例子: 有6辆车,争抢3个车位;先抢到的停车(处理业务逻辑),没有抢到的车子等待。抢到车位的车子离开时,没有抢到的要进行下一轮的争抢
先上代码:

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3); //模拟资源类, 有3个车位
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();  // 请求资源
                    System.out.println(Thread.currentThread().getName()+"\t抢占到车位,停车");
                    try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
                    System.out.println(Thread.currentThread().getName()+"\t3s后离开了车位");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();  //释放资源
                }
            }, String.valueOf(i)).start();
        }
    }
}
结果::
1	抢占到车位,停车
0	抢占到车位,停车
2	抢占到车位,停车
0	3s后离开了车位
2	3s后离开了车位
1	3s后离开了车位
3	抢占到车位,停车
4	抢占到车位,停车
5	抢占到车位,停车
3	3s后离开了车位
4	3s后离开了车位
5	3s后离开了车位

Process finished with exit code 0

0	抢占到车位,停车
1	抢占到车位,停车
2	抢占到车位,停车
2	3s后离开了车位
3	抢占到车位,停车
1	3s后离开了车位
5	抢占到车位,停车
0	3s后离开了车位
4	抢占到车位,停车
3	3s后离开了车位
5	3s后离开了车位
4	3s后离开了车位

Process finished with exit code 0

Semaphore类的原理
acquire(获取):当一个线程调用acquire操作时,它要么通过成功,获取信号量(信号量减1);要么一直等待下去,直至有线程释放了信号量,或者超时
release(释放):实际上会将信号量的值加1,然后唤醒等待的线程。
信号量主要用于两个作用
一个是用于对多个共享资源的互斥使用
另一个是用于并发线程数的控制。

Semaphore semaphore = new Semaphore(1);当只有一份资源时,相当于synchronized;
当如果有一个需求,多个线程争抢一份资源,抢到的线程持有这份资源20s钟。可以使用semaphore类(信号量)轻松实现。

BlockingQueue(阻塞队列)

现实生活中,类似于火锅店顾客吃饭时没有位置,需要等待。这些顾客就会被安排到BlockingQueue中。
在这里插入图片描述

线程1往阻塞队列里面添加元素(生产);线程2从阻塞队列中获取元素(消费);
当队列为空的时候,从队列中获取元素的操作将会被阻塞;
当队列是满的时候,往队列中添加元素的操作将会被阻塞。
试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往这个空的队列中插入元素。
试图向已经满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变的空闲起来并后续新增。

阻塞队列的用处

在多线程领域:所谓阻塞,在某些情况下会 挂起 线程(即阻塞),一旦满足条件,被挂起的线程又会被自动唤醒。
为什么需要BlockingQueue,好处是我们不需要关心什么时候 需要阻塞线程,什么时候需要唤醒线程因为这一切BlockingQueue都给你一手包办了

BlockingQueue UML图
在这里插入图片描述

实现类说明
ArrayBlockingQueue 由数组结构组成的有界阻塞队列
LinkedBlockingQueue 由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列
SynchrousQueue 不存储元素的阻塞队列,也即单个元素的阻塞队列
PriorityBlockingQueue支持优先级排序的无界阻塞队列
DelayQueue使用优先级队列实现的延迟无界阻塞队列
LinkedTransferQueue由链表组成的无界阻塞队列
LinkedBlockingDeque由链表组成的双向阻塞队列

BlockingQueue核心方法
在这里插入图片描述
抛出异常代码演示
add 代码说明:

public class BlockingQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        System.out.println(blockingQueue.add("d"));
    }
}
结果:
true
true
true
Exception in thread "main" java.lang.IllegalStateException: Queue full
	at java.util.AbstractQueue.add(AbstractQueue.java:98)
	at java.util.concurrent.ArrayBlockingQueue.add(ArrayBlockingQueue.java:312)
	at org.JVM_JUC.BlockingQueue.BlockingQueueDemo.main(BlockingQueueDemo.java:15)

Process finished with exit code 1

remove 代码说明:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
//        System.out.println(blockingQueue.add("d"));
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
    }
}
结果:
true
true
true
a
b
c
Exception in thread "main" java.util.NoSuchElementException
	at java.util.AbstractQueue.remove(AbstractQueue.java:117)
	at org.JVM_JUC.BlockingQueue.BlockingQueueDemo.main(BlockingQueueDemo.java:19)

Process finished with exit code 1

element方法 返回 队首元素。队列为空的时候会抛出异常。

特殊值代码演示

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> 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

阻塞 说明(put,take)

队列满的时候,再次put,线程会一直阻塞;
队列空的时候,再次take,线程会一直阻塞。

超时 说明(offer,poll)

队列满的时候,再次offer,线程会阻塞指定的时间后,如果成功写入返回true,如果写入失败返回false;
队列空的时候,再次take时,原理同上。

大数据学习之路 JUC篇(2) 集合类的不安全性

ArrayList 线程不安全

回顾一下集合类
ArrayList集合中 底层的实现是 数组 数组的类型是Object。
数组的内存开辟空间初始值是多少:java8中是空引用:类似于懒加载, 当添加第一个元素时,初始空间是10。
当添加的元素超过初始值时, ArrayList会进行扩容, 第一次扩容的时候先扩原始值的一半(15),
然后通过Arrays.copyOf(r, i); 将数据复制到扩容的ArrayList中;第二次扩容扩容到(15 + 15/2 =22)
16:是HashMap的初始值, HashMap扩容为上一次原始值的一倍

线程不安全举例:
代码说明:

只有一个main主线程时,添加数据不会出错。
public class ArrayListDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.stream().forEach(System.out::println);
    }
}
结果:
1
2
3

Process finished with exit code 0

多个线程操作ArrayList时:

public class ArrayListDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,10));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

第一次结果:
[6542b20c-0]
[6542b20c-0, d33da8c7-2]
[6542b20c-0, d33da8c7-2, 4a749f39-4]

Process finished with exit code 0

第二次结果:
[null, 727955a9-9]
[null, 727955a9-9]
[null, 727955a9-9]

第三次结果:
[ae981e3f-b, 408f08a8-a]
[ae981e3f-b, 408f08a8-a, 3d704549-0]
[ae981e3f-b, 408f08a8-a]

Process finished with exit code 0

从上述三次结果可以看出,三次输出的结果都不一致!!!

随着线程数的增加,可能会报java.util.ConcurrentModificationException

public class ArrayListDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 300; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,10));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}
结果:
[bf19612a-8, d6b0e43d-3, d39f752d-3, 5ff66553-1, 8569d610-2, a022c98c-9, 43e3ea26-5, a775bc96-1, 4a9e738e-2, 94f6bcfd-6, 4bcd5e3c-7, ca3a4849-5, b1cc8c8d-e, 05a6f9de-b, 49832dbe-5, 9ae3d6cf-7, 0cba4d3b-1, 1b3966a8-f, 36ac8060-0, 3cfa92c9-4, 79733374-3, 885dad74-2, c3c07e28-0, af50c524-c, 3ad899a0-7, 9fd474fe-1, bacb4c71-7, 592184c8-3, 41becbc3-b, 31f00a15-9, a7e5e7e2-e, 61aa2479-e, aa8e8aba-c, 8db4841b-7, 5c5eff3f-4, ec2c33eb-7, 124524ae-a, 22a1a157-f, ed721b7d-8, b5aa5dfa-b, 45c9cbfd-c, e1a5babe-9, 7931f844-4, 3b039c15-7, 0bf158e0-e, 4010fd6e-3, 91564ee2-5, 7381165f-e, 4fe4ea89-c, 5a6083cc-1, da8beceb-9, 93d78246-7, 4617fcda-8, 147e1576-a, 171b554f-b, 8b5f8cd8-d, e0151427-8, e90ed3a6-8, e2015a1d-6, f5ba66b3-4, 481ca580-2, 3c4ad842-d, be7e6412-f, 1d8514d4-2, fa4e1138-3, b02b0d56-d, fae95fe2-d, a883bedc-4, aee3b444-d]
Exception in thread "10" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at org.JVM_JUC.Collection_Safe.ArrayListDemo.lambda$main$0(ArrayListDemo.java:13)
	at java.lang.Thread.run(Thread.java:745)

导致原因
ArrayList 中的 boolean add(E e);没有加锁,所以多个线程争抢资源时就会发生Java.util.ConcurrentModificationException(俗称:并发修改异常!!)

解决方案
List list = new ArrayList<>(); // 效率高,不支持并发
List list = new Vector<>(); // 线程安全,但是效率低
List list = Collections.synchronizedList(new ArrayList<>()); // 线程安全,小数量完全可以
List list = new CopyOnWriteArrayList<>(); // 线程安全,JUC包下的类(写时复制),适用于多线程环境

HashSet 线程不安全

Set 与 ArrayList类似,也是线程不安全的

HashSet 底层实现是 HashMap, key=e(键值是你要添加的值,而value值是一个常量PRESENT=new Object())

解决方案
Set set = new HashSet<>(); // 效率高,但是线程不安全 
Set set = Collections.synchronizedSet(new HashSet<>()); // 线程安全
Set set = new CopyOnWriteArraySet<>(); // 线程安全,JUC包下的类

同理HashMap也是线程不安全的

HashMap 默认的初始空间为16,负载因子为:0.75。当hashmap里面的元素达到了16*0.75时,进行扩容,HashMap扩容为上一次原始值的一倍。使用HashMap的时候尽量初始空间设置大一些,避免频繁的扩容。

HashMap解决线程安全问题与ArrayList和HashSet有一点不同
Map<String, String> map = new HashMap<>(); // 效率高,多线程不安全
Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
Map<String, String> map = new ConcurrentHashMap<>(); //多线程安全,JUC包下的类

写时复制(原理)

写时复制:(读写分离)
CopyOnWrite容器即写时复制的容器,往一个容器里面添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后新的容器为Object[] newElements,然后新的容器Object[] newElements里面添加元素,添加完元素之后,再将原容器的应用指向新的容器:setArray(newElements);
这样做的好处是可对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

写时复制的源码

public boolean add(E e)
			{
				final ReentrantLock lock = this.lock;
				try
				{
					Object[] elements = getArray();
					int len = elements.length;
					Object[] newElements = Arrays.copyOf(elements, len + 1);
					newElements[len] = e;
					setArray(newElements);
					return true;
				}finally{
					lock.unlock();
				}
			}

从源码里可以看出,每次扩容一个空间,然后将原来的数据通过 Arrays.copyOf 复制到新的容器中。在写的时候和复制的时候 加上了锁。防止了java.util.ConcurrentModificationException异常。

本质上就是 读的容器 和写的容器是分离开的,允许多个线程同时读。但写被加上了锁,同一时刻只允许一个线程写。

JUC(3)线程池(JUC的半壁江山)

线程池的优势:

线程池做的工作是要控制运行的线程数量,处理过程中将任务放入队里, 然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等待,等其他线程执行完毕,再从队列中取出任务来执行。

线程池的主要特点:

线程复用
控制最大并发数
管理线程

  1. 降低资源消耗。通过重复利用已经创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以进行统一的分配,调优和监控。
线程池的使用(ExecutorService 接口)

ExecutorService 接口 通过线程池的工具类 Executors来实例化(类似于集合的工具类:Arrays,Collections)。
线程池的架构
在这里插入图片描述

线程池真真正正要玩的就是ThreadPoolExecutor

Java通过Executors提供了四种线程池:

  1. newSingleThreadExecutor:单线程化线程池。创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
  2. newFixedThreadPool:固定线程数的线程池。创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool:周期性线程池。线程池支持定时以及周期性执行任务。
  4. newCachedThreadPool:可缓存线程池。用来创建一个可以无限扩大的线程池。执行很多短期的异步任务,线程池根据需要创建新线程,但在先前构建的线程可用时将重用他们。可扩容,遇强则强(机器性能越强悍,缓存线程池越强)

三种经典的线程池的代码示例

Executors.newFixedThreadPool()代码示例

需求:模拟银行业务窗口。一共10个用户,5个业务窗口。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ExecutorServiceDemo {
    public static void main(String[] args) {
        ExecutorService pools = Executors.newFixedThreadPool(5); //线程池有5个受理线程,类似于一个银行有5个受理窗口
        try {
            for (int i = 0; i < 10; i++) {
                final int tmpi = i;
                pools.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t" + "号窗口给顾客" + tmpi + "办理业务");
                    try {
                        TimeUnit.MILLISECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pools.shutdown();
        }
    }
}
结果:
pool-1-thread-2	号窗口给顾客1办理业务
pool-1-thread-1	号窗口给顾客0办理业务
pool-1-thread-3	号窗口给顾客2办理业务
pool-1-thread-5	号窗口给顾客4办理业务
pool-1-thread-4	号窗口给顾客3办理业务
pool-1-thread-4	号窗口给顾客5办理业务
pool-1-thread-5	号窗口给顾客6办理业务
pool-1-thread-3	号窗口给顾客7办理业务
pool-1-thread-1	号窗口给顾客8办理业务
pool-1-thread-2	号窗口给顾客9办理业务

Process finished with exit code 0

从结果可以看出,newFixedThreadPool线程池中有5个线程,每个线程可以处理不同的业务。线程池中的每个线程都可以复用。(使用线程池,相对于直接单一使用线程,节省了资源申请和资源释放的响应时间。)

Executors.newSingleThreadExecutor()代码示例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ExecutorServiceDemo {
    public static void main(String[] args) {
//        ExecutorService pools = Executors.newFixedThreadPool(5); //线程池有5个受理线程,类似于一个银行有5个受理窗口
        ExecutorService pools = Executors.newSingleThreadExecutor(); //线程池只有单独一个线程可以处理业务
        try {
            for (int i = 0; i < 10; i++) {
                final int tmpi = i;
                pools.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t" + "号窗口给顾客" + tmpi + "办理业务");
                    try {
                        TimeUnit.MILLISECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pools.shutdown();
        }
    }
}
结果:
pool-1-thread-1	号窗口给顾客0办理业务
pool-1-thread-1	号窗口给顾客1办理业务
pool-1-thread-1	号窗口给顾客2办理业务
pool-1-thread-1	号窗口给顾客3办理业务
pool-1-thread-1	号窗口给顾客4办理业务
pool-1-thread-1	号窗口给顾客5办理业务
pool-1-thread-1	号窗口给顾客6办理业务
pool-1-thread-1	号窗口给顾客7办理业务
pool-1-thread-1	号窗口给顾客8办理业务
pool-1-thread-1	号窗口给顾客9办理业务

Process finished with exit code 0

Executors.newCachedThreadPool()代码示例
代码类似于上述代码,这里直接附上结果(模拟1000个用户办理业务)

pool-1-thread-1	号窗口给顾客0办理业务
pool-1-thread-2	号窗口给顾客1办理业务
pool-1-thread-3	号窗口给顾客2办理业务
pool-1-thread-4	号窗口给顾客3办理业务
pool-1-thread-5	号窗口给顾客4办理业务
pool-1-thread-6	号窗口给顾客5办理业务
pool-1-thread-7	号窗口给顾客6办理业务
pool-1-thread-8	号窗口给顾客7办理业务
pool-1-thread-9	号窗口给顾客8办理业务
pool-1-thread-10	号窗口给顾客9办理业务
pool-1-thread-11	号窗口给顾客10办理业务
pool-1-thread-12	号窗口给顾客11办理业务
pool-1-thread-13	号窗口给顾客12办理业务
pool-1-thread-14	号窗口给顾客13办理业务
pool-1-thread-15	号窗口给顾客14办理业务
pool-1-thread-16	号窗口给顾客15办理业务
pool-1-thread-17	号窗口给顾客16办理业务
pool-1-thread-18	号窗口给顾客17办理业务
pool-1-thread-19	号窗口给顾客18办理业务
pool-1-thread-20	号窗口给顾客19办理业务
pool-1-thread-21	号窗口给顾客20办理业务
pool-1-thread-22	号窗口给顾客21办理业务
pool-1-thread-23	号窗口给顾客22办理业务
pool-1-thread-24	号窗口给顾客23办理业务
pool-1-thread-25	号窗口给顾客24办理业务
pool-1-thread-26	号窗口给顾客25办理业务
pool-1-thread-27	号窗口给顾客26办理业务
pool-1-thread-227	号窗口给顾客226办理业务
pool-1-thread-228	号窗口给顾客227办理业务
pool-1-thread-226	号窗口给顾客225办理业务
pool-1-thread-225	号窗口给顾客224办理业务
pool-1-thread-224	号窗口给顾客223办理业务
pool-1-thread-223	号窗口给顾客222办理业务
pool-1-thread-222	号窗口给顾客221办理业务
pool-1-thread-6	号窗口给顾客305办理业务
pool-1-thread-221	号窗口给顾客220办理业务
pool-1-thread-220	号窗口给顾客219办理业务
pool-1-thread-219	号窗口给顾客218办理业务
pool-1-thread-218	号窗口给顾客217办理业务
pool-1-thread-217	号窗口给顾客216办理业务
pool-1-thread-216	号窗口给顾客215办理业务
pool-1-thread-215	号窗口给顾客214办理业务
pool-1-thread-214	号窗口给顾客213办理业务
pool-1-thread-5	号窗口给顾客354办理业务

从上述结果可以看出,已经创建的线程可以被复用(而不是一直创建新线程)

newScheduledThreadPool:可以 延时启动 、定时启动 、可以自定义最大线程池数量

关于newScheduledThreadPool线程池的使用,可以参考java线程池之newScheduledThreadPool

线程池(ThreadPoolExecutor源码)本质
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

从源码上可以看出,Executors.newFixedThreadPool()、Executors.newSingleThreadExecutor()和Executors.newCachedThreadPool()这三个Executors提供的线程池实际上就是一个线程池:ThreadPoolExecutor

线程池(ThreadPoolExecutor)的七大参数
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        //7大参数
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }


 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

线程池七大参数说明

  1. corePoolSize :线程池中的常驻核心线程数。(比如银行窗口,最多有5个窗口可办理业务,但是由于今天客流量少,只开2个窗口,所以今天的常驻核心线程数就是2
  2. maximumPoolSize :线程池中能够容纳同时执行的最大线程数,这个值必须大于等于1。(常驻线程数为2工作一段时间,突然客流量剧增,这时就需要 扩容,maximumPoolSize 表示最多有几个窗口
  3. keepAliveTime:多余的空闲线程的存活时间。当前线程池中线程数量超过corePoolSize 时,当空闲时间达到,对于线程会被销毁直到只剩下corePoolSize个线程为止。(当前线程池中的线程数多于常驻线程数,但是客流量较少,常驻线程数就可以完成,当多于的线程空闲时间达到这个值的时候就会被销毁,这时就需要 缩容
  4. unit:keepAliveTime的单位
  5. workQueue:队伍队列,被提交但尚未被执行的任务(类似于银行的候客区:用户需要排队等待,等轮到时候,窗口会提示:请 5号顾客到1号窗口办理业务
  6. threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程。一般默认的即可
  7. handler:拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒接请求执行的Runnable的策略。(候客区满了,即阻塞队列满了。银行就会通知:目前该网点正处于业务高峰期,没有进入候客区的顾客需要至少等待2个小时,请您到其他网点办理业务
线程池的底层工作原理

线程池的工作原理

下面这个图是结合现实生活中,银行办理业务,来通俗的说明线程池的原理
在这里插入图片描述

线程池的工作流程
在这里插入图片描述
流程文字版说明.特别重要、特别重要、特别重要

  1. 在创建了线程池后,开始等待请求。
  2. 当调用execute() 方法添加一个请求任务时,线程池会做出如下判断
    2.1 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务。
    2.2 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务 放入阻塞队列
    2.3 如果这个时候阻塞队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
    2.4 如果队列满了且正在运行的线程数量大于或者等于maximumPoolSize,那么线程池会 启动饱和拒绝策略来执行
  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。
  4. 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize的大小。
线程池用哪个?生产中如何设置合理参数?
线程池的拒接策略

线程池的拒绝策略是什么?
等待队列已经排满了,再也塞不下新任务了。同时,线程池中的max线程也达到了,无法继续为新任务服务。这个时候我们就需要 拒绝策略机制合理的处理这个问题。
JDK内置的拒绝策略?(4种拒接策略)

  1. AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
  2. CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。(回退,谁让你找我的,你回去找谁
public class UDFThreadPool {
    public static void main(String[] args) {
        ExecutorService threadpool = new ThreadPoolExecutor(
                2,
                5,
                2L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        try {
            for (int i = 0; i < 10; i++) {
                final int tmpi = i;
                threadpool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t" + "号窗口给顾客" + tmpi + "办理业务");
                    try {
                        TimeUnit.MILLISECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadpool.shutdown();
        }
    }
}
结果:
pool-1-thread-1	号窗口给顾客0办理业务
main	号窗口给顾客8办理业务
pool-1-thread-2	号窗口给顾客1办理业务
pool-1-thread-4	号窗口给顾客6办理业务
pool-1-thread-3	号窗口给顾客5办理业务
pool-1-thread-5	号窗口给顾客7办理业务
main	号窗口给顾客9办理业务
pool-1-thread-2	号窗口给顾客2办理业务
pool-1-thread-1	号窗口给顾客3办理业务
pool-1-thread-3	号窗口给顾客4办理业务

Process finished with exit code 0

CallerRunsPolicy这种策略当队列满了而且线程数已经达到了最大的maximumPoolSize,这是,会把这个任务返还给任务提交者那个线程进行处理。

  1. DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
  2. DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略。

以上内置拒绝策略均实现了RejectedExecutionHandle接口

在工作中 单一的/固定数的/可变的三种创建线程池的方法哪个用的多??(超级大坑)

答:一个都不用,在工作中只能使用自定义的线程池
Executors中JDK已经给你提供了,为什么不用呢?
阿里巴巴java开发手册中规定:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
2)CacheThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

在工作中如何使用线程池,是否自定义过线程池?
public class UDFThreadPool {
    public static void main(String[] args) {
        System.out.println(Runtime.getRuntime().availableProcessors());
        ExecutorService threadpool = new ThreadPoolExecutor(
                2,
                Runtime.getRuntime().availableProcessors()+1,
                2L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        try {
            for (int i = 0; i < 10; i++) {
                final int tmpi = i;
                threadpool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t" + "号窗口给顾客" + tmpi + "办理业务");
                    try {
                        TimeUnit.MILLISECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadpool.shutdown();
        }
    }
}

JAVA流式计算(不是IO流哦!!!切记)

流(Stream)到底是什么呢?

它是数据渠道,用于操作数据源(集合,数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算”

流的特点
  1. Stream自己不会存储元素
  2. Stream不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  3. Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。(有点类似于Spark中的RDD)
怎么用?

阶段

  1. 创建一个Stream:一个 数据源 (数组,集合)
  2. 中间操作:一个中间操作,处理 数据源数据。
  3. 终止操作:一个终止操作,执行中间操作链,产生结果。
    源头=>中间流水线=>结果

使用java流式计算可以方便的实现对一些sql的实现。

题目:一个User类,有 id name,age三个字段。目前有5个对象,要求,选出id为偶数的,且age大于等于24的对象,并将name变成大写,最后输出排序最前面的那个 name。

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Arrays;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
class User
{
    private int id;
    private String name;
    private int age;
}

public class StreamDemo {
    // 流式计算
    public static void main(String[] args) {
        /**
         * 需求:选出id为偶数的,年龄大于等于24的 且将选择出来的用户的名字变成大写,最后返回最大的那个名字
         */
        // TODO 1. 首先创建数据源
        User user1 = new User(11,"a",22);
        User user2 = new User(12,"b",23);
        User user3 = new User(13,"c",24);
        User user4 = new User(14,"d",28);
        User user5 = new User(16,"e",26);
        // 需求
        List<User> list = Arrays.asList(user1,user2,user3,user4,user5);
        list.stream().filter(user -> {return user.getId()%2==0;}).filter(user -> {return user.getAge()>=24;}).map(user -> {return user.getName().toUpperCase();}).sorted((o1, o2) -> {return o2.compareTo(o1);}).limit(1).forEach(System.out::println);

    }
}
结果:
E

Process finished with exit code 0
分支合并框架 ForkJoin(大事化小 小事化了)

ForkJoinPool
既然任务是被逐渐的化小,那就需要把这些任务存在一个池子里面,这个池子就是ForkJoinPool,它与其它的ExecutorService区别主要在于它使用“工作窃取“,那什么是工作窃取呢?
工作窃取:一个大任务会被划分成无数个小任务,这些任务被分配到不同的队列,这些队列有些干活干的块,有些干得慢。于是干得快的,一看自己没任务需要执行了,就去隔壁的队列里面拿去任务执行。

ForkJoinTask(类似于futureTask,这个抽象类实现了Future接口,可以get() 返回值
ForkJoinTask就是ForkJoinPool里面的每一个任务。他主要有两个子类:RecursiveAction和RecursiveTask。然后通过fork()方法去分配任务执行任务,通过join()方法汇总任务结果,

RecursiveTask(public abstract class RecursiveTask extends ForkJoinTask)(自己调用自己,类似于分治法)**

ForkJoin

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;


class MyTask extends RecursiveTask<Integer>
{
    public static final Integer ADJUST_VALUE = 10;
    private int begin;
    private int end;
    private int result;

    public MyTask(int begin, int end) {
        this.begin=begin;
        this.end=end;
    }

    @Override
    protected Integer compute() {
        if((end-begin)<=ADJUST_VALUE)
        {
            for (int i = begin; i < end; i++) {
                result = result+i;
            }
        }else
        {
            int middle = (begin+end)/2;
            MyTask myTask1 = new MyTask(begin, middle);
            MyTask myTask2 = new MyTask(middle+1, end);
            myTask1.fork(); // 自动调用compute方法
            myTask2.fork(); //自动调用compute方法
            // join 获取返回值
            result = myTask1.join()+myTask2.join();
        }
        return result;
    }
}



public class ForkJoinDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //TODO 线程操作资源类
        MyTask myTask = new MyTask(0, 1000);
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> joinTask = forkJoinPool.submit(myTask);
        System.out.println(joinTask.get());
        forkJoinPool.shutdown();
    }
}
结果:
435927

Process finished with exit code 0
JAVA中的异步回调(CompletableFuture)

这里我只写了一个Demo,关于CompletableFuture类的详细介绍,可以参考有了CompletableFuture,使得异步编程没那么难了

public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        CompletableFuture.runAsync(); runAsync:异步回调,没有返回值
//        CompletableFuture.supplyAsync(); supplyAsync:异步回调,有返回值
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t没有返回值");
        });
        future1.get();


        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            return Thread.currentThread().getName();
        });
        System.out.println(future2.whenCompleteAsync((s, throwable) -> {
            System.out.println("********s:" + s);
            System.out.println("********throwable:" + throwable);
        }).exceptionally(throwable -> {
            return throwable.getMessage();
        }).get());


    }
}
结果:
ForkJoinPool.commonPool-worker-1	没有返回值
********s:ForkJoinPool.commonPool-worker-1
********throwable:null
ForkJoinPool.commonPool-worker-1

Process finished with exit code 0

public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        CompletableFuture.runAsync(); runAsync:异步回调,没有返回值
//        CompletableFuture.supplyAsync(); supplyAsync:异步回调,有返回值
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "\t没有返回值");
        });
        future1.get();


        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            int a = 10/0;
            return Thread.currentThread().getName();
        });
        System.out.println(future2.whenCompleteAsync((s, throwable) -> {
            System.out.println("********s:" + s);
            System.out.println("********throwable:" + throwable);
        }).exceptionally(throwable -> {
            return throwable.getMessage();
        }).get());


    }
}
结果
ForkJoinPool.commonPool-worker-1	没有返回值
********s:null
********throwable:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero

Process finished with exit code 0
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值