回顾多线程
线程和进程
1.线程是在进程下进行的,
2.线程是进程的最小单位
3.一个进程是由多个线程组成的
4.Java默认的有两个线程:main、GC
5.Java真的可以开启线程吗?
不可以,只能通过本地方法去调用,native本地方法,底层的C++,Java无法直接操作硬件。
并发和并行
- 解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
- 解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
- 解释三:并行是在多台处理器上同时处理多个任务。如 hadoop 分布式集群,并发是在一台处理器上“同时”处理多个任务。
- 解释四:并发:多线程操作同一个资源;并行:多个人一起走路
获取CPU的核数:
- 任务管理器
- 通过代码来实现
System.out.println(Runtime.getRuntime().availableProcessors());
线程的几个状态:
//源码中的说明
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超时等待,也属于阻塞的一种
TIMED_WAITING,
//终止
TERMINATED;
}
wait和sleep区别:
-
来自不同的类
wait–>Object;sleep–>Thread -
关于锁的释放
wait会释放锁,sleep不会释放锁 -
使用的范围是不同的
wait:必须在同步代码块中;sleep:可以在任何地方 -
是否需要捕获异常
wait:不需要捕获异常;sleep:必须要捕获异常
synchronized和Lock的区别
//synchroized
public static void main(String[] args) {
//多个线程操作同一资源类,把资源丢入线程
Ticket ticket = new Ticket();
//lambda表达式,jdk1.8新特性,把资源丢入线程
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;
public synchronized void sale(){
if (num>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(num--)+"张票,剩余:"+num);
}
}
//Lock
public static void main(String[] args) {
//多个线程操作同一资源类,把资源丢入线程
Ticket2 ticket2 = new Ticket2();
//lambda表达式,jdk1.8新特性,把资源丢入线程
new Thread(()-> { for (int i = 1; i < 60; i++) ticket2.sale();},"A").start();
new Thread(()-> {for (int i = 1; i < 60; i++) ticket2.sale(); },"B").start();
new Thread(()-> {for (int i = 1; i < 60; i++) ticket2.sale(); },"C").start();
}
}
//OOP
class Ticket2{
//属性加方法
private int num =50;
//Lock三部曲:new ReentrantLock();加锁,解锁
Lock lock=new ReentrantLock();
public synchronized void sale(){
lock.lock();
try{
if (num>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(num--)+"张票,剩余:"+num);
}
}finally {
lock.unlock();
}
}
区别:
- synchronized内置的Java关键字,Lock是一个Java类
- synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
- synchronized会自动释放锁,Lock必须手动释放锁。如果不手动释放锁,会产生死锁
- synchronized线程1(获得锁,阻塞)、线程2(一直等待);Lock锁就不一定会一直等待下去
- synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码。
- synchronized可重入锁,是不可以中断的,非公平的:lock,可重入锁,非公平(公平与否可以自己通过代码实现)源码介绍如下图:
生产者和消费者
传统的生产者和消费者问题
public static void main(String[] args) {
Date date = new Date();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Date{
private int num=0;
//+1
public synchronized void increment() throws InterruptedException {
if (num!=0){
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"->"+num);
this.notifyAll();//通知其他线程,+1完毕
}
//-1
public synchronized void decrement() throws InterruptedException {
if (num==0){
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"->"+num);
this.notifyAll();//通知其他线程,—1完毕
}
运行结果为:
如果此时有ABCD四个线程,同时进行+1和-1,此时就会出错,会出现其他的各种数字。参考jdk文档,我们需要把if改成while就可改正这个错误,文档说明如下图。不过此时的运行结果四条线程是随机出现的,并没有什么规律,后期可以用Lock锁的Conditoin进行唤醒指定的线程,
Lock版的生产者和消费者问题
Synchronized和Lock的对比:
class Date2{
private int num=0;
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
//+1
public void increment() throws InterruptedException {
lock.lock();
try {
while (num!=0){
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"->"+num);
//通知其他线程,+1完毕
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
lock.lock();
try {
while (num==0){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"->"+num);
//通知其他线程,—1完毕
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
主要代码也就是下面这几行:
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
lock.lock();//加锁
condition.await();//等待
condition.signalAll();//唤醒
lock.unlock();//解锁
Lock方法为什么比synchnorized的方法更好用呢?
因为Lock中的Condition可以实现精准唤醒线程,如果同时有几个线程,notify方法操作下,线程运行比较随意,而condition.signalAll中可以精准的操作去唤醒哪个线程。
有三条线程,ABCA依次进行执行,A -> B -> C -> A
private Lock lock=new ReentrantLock();
private Condition condition1=lock.newCondition();
private Condition condition2=lock.newCondition();
private Condition condition3=lock.newCondition();
private int num=1;
public void printA(){
lock.lock();
try {
while (num!=1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"AAAAAAAAAAA");
//唤醒指定的B
num=2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (num!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"BBBBBBBB");
num=3; //唤醒指定的C
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (num!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"CCCCCCCCCCC");
num=1; //唤醒指定的A
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
运行结果就是和我们设想的是相同的,按照ABCA依次进行执行的。
八锁现象进行对锁的理解
//Test01
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
try {
phone.Send();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
phone.Call();
},"A").start();
}
}
class Phone{
public synchronized void Send() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("发短信");
}
public synchronized void Call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
//Test02
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
try {
phone.Send();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
phone.hello();
},"A").start();
}
}
class Phone2{
public synchronized void Send() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("发短信");
}
public void hello(){
System.out.println("hello");
}
}
//Test03
public static void main(String[] args) {
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
try {
phone1.Send();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
phone2.Call();
},"A").start();
new Thread(()->{
phone2.hello();
},"A").start();
}
}
class Phone3{
public synchronized void Send() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("发短信");
}
public synchronized void Call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
//Test04
public static void main(String[] args) {
Phone4 phone = new Phone4();
new Thread(()->{
try {
phone.Send();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
phone.Call();
},"A").start();
}
}
class Phone4{
public static synchronized void Send() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("发短信");
}
public static synchronized void Call(){
System.out.println("打电话");
}
//Test05
public static void main(String[] args) {
Phone5 phone1 = new Phone5();
Phone5 phone2 = new Phone5();
new Thread(()->{
try {
phone1.Send();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
phone2.Call();
},"A").start();
}
}
class Phone5{
public static synchronized void Send() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("发短信");
}
public static synchronized void Call(){
System.out.println("打电话");
}
//Test06
public static void main(String[] args) {
Phone6 phone1 = new Phone6();
new Thread(()->{
try {
phone1.Send();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
phone1.Call();
},"A").start();
}
}
class Phone6{
public static synchronized void Send() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("发短信");
}
public synchronized void Call(){
System.out.println("打电话");
}
//Test07
public static void main(String[] args) {
Phone7 phone1 = new Phone7();
Phone7 phone2 = new Phone7();
new Thread(()->{
try {
phone1.Send();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
phone2.Call();
},"A").start();
}
}
class Phone7{
public static synchronized void Send() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("发短信");
}
public synchronized void Call(){
System.out.println("打电话");
}
以上几种情况对比分析:
-
Test01:
两个对象同时用一把锁,谁先拿到这个锁,谁就先执行
运行结果是:1.发短信,打电话 -
Test02:
一个线程加锁,一个线程不加锁,执行的时候首先执行的是不加锁的那个线程,然后执行的是加锁的那个线程
运行结果:1.hello 2.发短信 -
Test03:
两个线程两个锁,此时就看哪个线程有没有延迟,发短信那个锁加了一个4s的延时,所以此时打电话这个线程先执行
运行结果:1.打电话 2.发短信 -
Test04:
两个线程一个锁,此时加了static静态代码块,synchronized锁的对象是方法的调用者,同时加入了static,此时锁的是class,一个类只有一个class对象,
类一加载就有了,static走的是class对象,锁的也是class。
运行结果:1.发短信 2.打电话 -
Test05:
两个线程两个锁,此时加了static静态方法,运行结果和Test03不同,此时static锁的是class,两个对象的class对象是一个模板,一个类只有一个class
运行结果:1.发短信 2.打电话 -
Test06:
一个是静态代码块(static),一个是普通代码块,静态代码块锁的是class模板(发短息),普通的代码锁的是调用者,打电话的方法不需要去等待发短信的锁
运行结果:1.打电话 2.发信息 -
Test07:
静态方法走的是class模板(发短信),后面走的是普通方法,不需要等待
运行结果:1.打电话 2.发信息
集合类不安全
在并发的情况下,我们使用的List,Map、Set都是不安全的。
List在单线程的时候是安全的,但是在多线程的时候就是不安全的。
//单线程
public static void main(String[] args) {
List<String>list= Arrays.asList("a","b","c");
list.forEach(System.out::println);
}
//多线程
public static void main(String[] args) {
List<String > list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
CopyOnWriteArrayList
- 此时会报错java.util.ConcurrentModificationException(并发修改异常),此时有三种解决方案:
- 方案一:List<String > list = new Vector<>();
- 方案二:List<String>list= Collections.synchronizedList(new ArrayList<>());
- 方案三:List<String>list= new CopyOnWriteArrayList<>();
CopyOnWriteArrayList写入时复制,是计算机设计领域的一种优化,简称COW,用来提高效率。多个线程调用的时候,在读取的时候是固定的,在写入的时候覆盖。为了避免覆盖的发生,造成数据问题就用CopyOnWrite。
CopyOnWrite比Vector好在哪里?
通过如下两张源码的对比图可以发现,Vector中使用的是synchronized方法,一般效率比较低。而CopyOnWrite使用的是新版的Lock的方法,效率较高
CopyOnWriteArraySet
public static void main(String[] args) {
Set<String > set =new HashSet<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString());
System.out.println(set);
},String.valueOf(i)).start();
}
}
同理可得解决方案如下:
- Set set =Collections.synchronizedSet(new HashSet<>());
- Set set =new ConcurrentSkipListSet<>();
HashSet底层是什么?
public HashSet() {
map = new HashMap<>();
}
//add本质是map key 是无法重复的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
HashMap默认因子的相关源码:
ConcurrentHashMap
public static void main(String[] args) {
//0.75默认的加载因子
//默认的加载因子: new HashMap<>(16,0.75);
HashMap<String, String> map = new HashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
修改如下:
//并发下的HashMap
Map<String, String> map = new ConcurrentHashMap<>();
查看API文档,如下图:
Callable
Callable也是线程的实现方法之一,一般用Runnable较多。Callable需要返回结果
由API文档可知:
- 可以有返回值
- 可以抛出异常
- 方法不同run() / call()
注意:
callable 不能直接用Thread开启。可以通过Runnable中的FutureTask方法进行实现。相关API文档如下图:
public static void main(String[] args) throws ExecutionException, InterruptedException {
//实现过程如下:
//new Thread(new Runnable()).start;
//new Thread(new new FutureTask<>()).start;
//new Thread(new new FutureTask<>(Callable)).start;
MyThread thread = new MyThread();
FutureTask task = new FutureTask(thread);
new Thread(task,"A").start();
//get方法获取返回Callable的返回结果,
//可能会产生阻塞,解决方法:把他放到最后,或者用异步通信来处理
String o =(String ) task.get();
System.out.println(o);
}
}
class MyThread implements Callable<String >{
@Override
public String call() throws Exception {
System.out.println("call()");
return "1234567789";
}
}
如果此时有两个线程,同时开启,结果只打印一次。
常用的辅助类
CountDownLatch
//减法计数器
public static void main(String[] args) throws InterruptedException {
//总数为6,必须是执行任务的时候,使用
CountDownLatch downLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"Go Out");
downLatch.countDown();//数量-1
},String.valueOf(i)).start();
downLatch.await();//等待计数器归零,然后在往下执行
System.out.println("Close Door");
}
}
常用的方法如下:
downLatch.countDown();//数量-1
downLatch.await();//等待计数器归零,然后在往下执行
CyclicBarrier
//加法计数器
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙");
});
for (int i = 0; i < 7; i++) {
//lambda表达式不能直接读取外部的i,需要借助于final来进行实现
final int temp=i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+temp+"龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
Semaphore
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);
System.out.println(Thread.currentThread().getName()+"离开停车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//释放
}
},String.valueOf(i)).start();
}
重点方法:
semaphore.acquire();//得到,如果已经满了,等待,等待被释放为止
semaphore.release();//释放,会将当前的信号量+1,然后唤醒等待的线程
作用:多个共享资源互斥的使用,并发限流,控制最大的线程数
读写锁
- 独占锁(写锁)一次只能被一个线程占有
- 共享锁(读锁)多个线程可以同时占有
- ReadWriteLock
读-读 可以共存
读-写 不能共存
写-写 不能共存
public static void main(String[] args) {
MyCache myCache = new MyCache();
//写入
for (int i = 1; i <= 5; i++) {
final int temp=i;
new Thread(()->{
myCache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
//读取
for (int i = 1; i <= 5; i++) {
final int temp=i;
new Thread(()->{
myCache.pop(temp+"");
},String.valueOf(i)).start();
}
}
}
//volatile只能保证可见性,不能保证原子性
class MyCache{
private volatile Map<String,Object>map=new HashMap();
//读写锁
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
private Lock lock=new ReentrantLock();
//存,写入的东西,一次只能进去一个线程
public void put(String key,String value){
//写的时候是按照顺序依次进行,而读的时候就是随机的
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
//取,读,所有人都可以读
public void pop(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
阻塞队列
阻塞队列的图文解释:
BlockingQueue
BlockingQueue我们并不陌生,通过查看API文档我们可以看出他和List和Set之间的关系
队列就是先进先出,双端队列就是两个端口可以同时进行取出
什么时候我们会使用阻塞队列?
多线程并发处理,线程池
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞,等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer() |
移除 | remove() | poll() | take() | poll() |
检测队首元素 | element | peek | - | - |
public static void test01(){
//没有返回值,会抛出异常
//队列的大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
//抛出异常,java.lang.IllegalStateException: Queue full
//System.out.println(arrayBlockingQueue.add("d"));
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
}
public static void test02(){
//有反回值,不抛出异常
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
// System.out.println(blockingQueue.offer("d"));//不会抛出异常,返回一个false
System.out.println(blockingQueue.element());//查看队首元素是谁
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());//返回值为null,没有抛出异常
}
public static void test03() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//blockingQueue.put("d");//队列没有位置了,一直等待
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
//System.out.println(blockingQueue.take());//没有这个元素,一直阻塞
}
public static void test04() throws InterruptedException {
//有反回值,不抛出异常
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
//blockingQueue.offer("c",2, TimeUnit.SECONDS);//等待2s,如果没有位置就退出
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));//等待2s,如果没有就退出
}
SynchronousQueue同步队列
没有容量,必须进去一个元素,等这个元素被取出后,才能在放进去一个元素。put (), take()
BlockingQueue是SynchronousQueue的父类,SynchronousQueue与其他的方法不同,必须等待放进去的类取出来后,才能接着放入下一个
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();
}
},"1").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();
}
},"2").start();
}
运行结果如下:
线程池(重点)
池化技术以及线程创建方法
public static void main(String[] args) {
//方法一:单个线程
ExecutorService threadPoll = Executors.newSingleThreadExecutor();//单一的
//方法二:固定线程大小
ExecutorService threadPoll = Executors.newFixedThreadPool(5);//创建一个固定大小的线程池
//方法三:可伸缩的线程
ExecutorService threadPoll = Executors.newCachedThreadPool();//遇强则强,可变得
try{
for (int i = 0; i < 10; i++) {
//使用了线程池之后,需要用线程池来创建现成
threadPoll.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//使用完现成之后,必须要关闭线程
threadPoll.shutdown();
}
}
源码分析:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//本质:都用的是ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大核心线程池大小
long keepAliveTime,//超时了没有人调用就会被释放
TimeUnit unit,//超时单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂:创建线程的,一般不用动
RejectedExecutionHandler handler//拒绝策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
7大参数
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大核心线程池大小
long keepAliveTime,//超时了没有人调用就会被释放
TimeUnit unit,//超时单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂:创建线程的,一般不用动
RejectedExecutionHandler handler//拒绝策略)
四种拒绝策略
第一种:
ExecutorService threadPoll = new ThreadPoolExecutor(
2,//一开始的时候的两个业务员
5,//最大业务员办理容量
3,//办理业务员不忙的时候的不开放的个数
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),//休息区的个数
Executors.defaultThreadFactory(),//线程工厂,一般是不动的
new ThreadPoolExecutor.AbortPolicy());//银行满了,此时还有人过来,银行不处理这个业务员,抛出异常
第二种:
new ThreadPoolExecutor.CallerRunsPolicy());//哪来的去哪里
第三种:
new ThreadPoolExecutor.DiscardPolicy());//队列满了,不会抛出异常,会直接丢掉任务
运行结果
第四种:
线程较少时,没有什么反应,一般使用与线程较多的时候
new ThreadPoolExecutor.DiscardOldestPolicy());//队列满了,尝试去和最早的竞争,也不会抛出异常
IO密集型,CPU密集型:(调优)
public static void main(String[] args) {
//获取CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());
ExecutorService threadPoll = new ThreadPoolExecutor(
2,//一开始的时候的两个业务员
5,//最大业务员办理容量
3,//办理业务员不忙的时候的不开放的个数
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),//休息区的个数
Executors.defaultThreadFactory(),//线程工厂,一般是不动的
new ThreadPoolExecutor.DiscardOldestPolicy());//队列满了,尝试去和最早的竞争,也不会抛出异常
try{
for (int i = 1; i <=9; i++) {
//使用了线程池之后,需要用线程池来创建现成
threadPoll.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//使用完现成之后,必须要关闭线程
threadPoll.shutdown();
}
}
}
池的最大发的大小如何定义设置:
1.CPU密集型,几核,就是几,可以保持CPU效率最高
2.IO密集型>判断你的程序中十分耗IO的线程
程序:15个大型任务,IO十分占用资源
四大函数式接口(必须掌握)
函数式接口
函数式接口:只有一个方法的接口
例如:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
//超级多FunctionalInterface
//简化编程模型,在新版本的框架底层大量使用
//foreach(消费者类的函数接口)
Function函数式接口
源码:
//Function函数式接口:有一个输入参数,有一个输出
//只要是函数式接口就可以用lambda表达式
public static void main(String[] args) {
//匿名内部类
Function function=new Function<String,String >() {
@Override
public String apply(String str) {
return str;
}
};
//Function function1= (str)->{ return str;};
System.out.println(function.apply("asp"));
}
}
运行结果:
Predicate段定型接口
源码:
代码演示:
//段定型接口:有一个输入参数,,返回值只能是布尔型
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"));
}
}
运行结果:
Consumer消费型接口
//Consumer:消费型接口:只有输入,没有返回值
public static void main(String[] args) {
//Consumer<String> consumer = new Consumer<String>() {
// @Override
// public void accept(String str) {
// System.out.println(str);
//}
// };
// consumer.accept("asd");
Consumer<String> consumer = (str) -> {
System.out.println(str);
};
consumer.accept("asd");
}
Supplier供给型接口
//Supplier:供给型接口,没有参数,只有返回值
public static void main(String[] args) {
Supplier supplier = new Supplier<Integer>() {
@Override
public Integer get() {
System.out.println("get()");
return 1024;
}
};
//Supplier supplier =()->{return 1024;};
System.out.println(supplier.get());
}
}
Stream流式计算
/*
现在有五个用户,进行筛选
1.ID是偶数
2.年龄大于23
3.用户名转换为大写字母
4.用户名字母倒着写
5.只输出一个用户
* */
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);
//集合就是存储
List<User> list= Arrays.asList(u1,u2,u3,u4,u5);
//计算交给Stream流运算
list.stream()
.filter(u->{return u.getId()%2==0;})//filter->筛选
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getStr().toUpperCase();})//转换为大写字母
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})//排序
.limit(1)
.forEach(System.out::println);//打印
}
ForkJoin
什么是ForkJoin?
ForkJoi在jdk1.7并行执行任务!提高效率,大数据量
大数据:MapReduce本质就是:把大任务拆分为小任务
ForkJoin工作窃取:
如下图所示:A线程和B线程,此时B线程已经执行完毕,A线程才执行了一半,B线程则会把A线程没有执行的部分偷过来给执行,提高效率,维护的都是双端队列
ForkJoin用法:
如何使用ForkJoin:
1.通过ForkJoinPool来执行
2.计算任务forkJoinPool.execute(ForkJoinTask task)
3.计算类要继承ForkJoinTask
public class ForkJoinDemo extends RecursiveTask<Long> {
private long start;
private long end;
//临界值,中间值
private long temp=10000L;
public ForkJoinDemo(long start, long end) {
this.start = start;
this.end = end;
}
//计算方法
@Override
protected Long compute() {
long sum = 0L;
if ((end - start) < temp) {
for (long i = start; i < end; i++) {
sum = +i;
}
return sum;
} else {
long middle=(end-start)/2;//中间值
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
task1.fork();//拆分任务,把任务压入队列
ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
task2.fork();//拆分任务,把任务压入队列
return task1.join()+task2.join();
}
}
}
测试:
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//test1();//时间为:365
//test2();//数字运行不出来
test3();//时间为:215
}
public static void test1(){
long sum = 0L;
long start=System.currentTimeMillis();
for (long i = 1L; i <= 10_0000_0000; i++) {
sum +=i;
}
long end=System.currentTimeMillis();
System.out.println("sum="+sum+" 时间为:"+(end-start));
}
public static void test2() throws ExecutionException, InterruptedException {
long start=System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务
Long sum=submit.get();//会存在阻塞等待
long end=System.currentTimeMillis();
System.out.println("sum="+sum+"时间为:"+(end-start));
}
public static void test3(){
long start=System.currentTimeMillis();
//Stream并行流,规约计算
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end=System.currentTimeMillis();
System.out.println("sum="+sum+"时间为:"+(end-start));
}
}
异步回调
Future设计的初衷:对将来的某个事件的结果进行建模