线程与并发编程记录

线程基本介绍

什么是线程

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中, 是进程中的实际运作单位

为什么会有线程

在多核CPU中,利用多线程可以实现真正意义上的并行执行

在一个应用进程中,会存在多个同时执行的任务,如果其中一个任务被阻塞,将会引起不依赖该任务的任务也被阻塞。通过对不同任务创 建不同的线程去处理,可以提升程序处理的实时性

线程可以认为是轻量级的进程,所以线程的创建、销毁比进程更快

线程的应用场景

使用多线程实现文件下载

后台任务:如定时向大量(100W以上)的用户发送邮件

异步处理:记录日志

多步骤的任务处理,可根据步骤特征选用不同个数和特征的线程来协作处理,多任务的分割,由一个主线程分割给多个线程完成

总结

多线程的本质是:合理的利用多核心CPU资源来实现线程的并行处理,来实现同一个进程内的多个任务的并行执行,同时基于线程本身的异步执行特性,提升任务处理的效率

Java中三种创建方式

继承Thread类
public class ThreadDemo  extends Thread{

    @Override
    public void run() {
        System.out.println("当前线程:"+ Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        threadDemo.start(); //启动一个线程
    }
}
实现Runnable接口
public class RunnableDemo implements Runnable {
    
    @Override
    public void run() {
        System.out.println("当前线程:"+ Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread = new Thread(runnableDemo);
        thread.start(); //启动一个线程
    }
}
实现Callable接口
public class CallableDemo implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("当前线程:"+ Thread.currentThread().getName());
        return "无聊人";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        Future<String> future = executorService.submit(new CallableDemo());
        //future.get() 是一个阻塞方法
        System.out.println(Thread.currentThread().getName() + "-----" + future.get());
    }
}

线程的生命周期

Java线程从创建到销毁,一共经历6个状态

NEW:初始状态,线程被构建,但是还没有调用start方法

RUNNABLED:运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为“运行中”

BLOCKED:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU使用权,阻塞也分为几种情况

WAITING: 等待状态

TIME_WAITING:超时等待状态,超时以后自动返回

TERMINATED:终止状态,表示当前线程执行完毕

在这里插入图片描述

public class ThreadStatusDemo {

    public static void main(String[] args) {
        //TIME_WAITING
        new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "WLR_TIME_WAITING").start();
        //WAITING
        new Thread(() -> {
            while (true) {
                synchronized (ThreadStatusDemo.class) {
                    try {
                        ThreadStatusDemo.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "WLR_WAITING").start();
        //BLOCKED
        new Thread(new BlockedDemo(), "Blocked01").start();
        new Thread(new BlockedDemo(), "Blocked02").start();
        //在target中找到对应class文件 cmd中  jps找到运行进程id  通过 jstack id 找到运行日志
    }

    static class BlockedDemo extends Thread {
        @Override
        public void run() {
            synchronized (BlockedDemo.class) {
                while (true) {
                    try {
                        TimeUnit.SECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

}

线程基本操作及原理

Thread.join使用及原理

​ Thread.join的作用是保证线程执行结果的可见性

public class ThreadJoinDemo {

    private static int x = 0;
    private static int i = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            i = 1;
            x = 2;
        });
        Thread t2 = new Thread(() -> {
            i = i + x;
        });
        t1.start();
        t1.join(); //t1线程的执行结果对t2可见(t1线程一定要比t2线程优先执行)  --阻塞
        t2.start();
        Thread.sleep(1000);
        System.out.println("----" + i);
    }
}

​ Thread.join的本质其实是wait/notifyall

​ 让主线程进入阻塞状态,join底层就是使用了wait进行了等待,有wait就必然会有 notify/notify_all,notify并不是在java代码层面中实现的,他是在jvm实现的,线程的销毁都是在jvm中实现的,join调用完在销毁时jvm中有一个ensure_join(this); 方法会去实现lock.notify_all(thread); 进行唤醒阻塞下的主线程

在这里插入图片描述

thread.sleep

​ 使线程暂停执行一段时间,直到等待的时间结束才恢复执行或在这段时间内被中断

工作流程:

​ 挂起线程并修改其运行状态

​ 用sleep()提供的参数来设置一个定时器。

​ 当时间结束,定时器会触发,内核收到中断后修改线程的运行状态。

例如线程会被标志为就绪而进入就绪队列等待调度

问题思考:

  1. 假设现在是 2019-11-18 12:00:00.000,如果我调用一下

Thread.Sleep(1000) ,在 2019-11-18 12:00:01.000 的时候,这个线程会

不会被唤醒?

​ 该线程在未来1秒来退出cpu,不参与cpu竞争,但是在一秒过后这个时候也许另外一个线程正在使用cpu,那么这个时候操作系统是不会重新分配cpu的,直到那个线程挂起或者结束,就算正好轮到操作系统分配cpu,当前线程也不一定是那个优先级最高的那个,cpu可能还是会被其他的线程抢占过去

  1. Thread.Sleep(0) 的意义

    类似于Thread.yield() 出让cpu 触发操作系统,重新进行cpu竞争

线程的调度算法

​ 操作系统中,CPU竞争有很多种策略。Unix系统使用的是时间片算法,而Windows则属于抢占式的。

wait和notify

wait:作用是挂起当前线程,释放获取到的锁,直到别的线程调用了这个对象的notify或notifyAll方法。
notify:作用是唤醒因调用wait挂起的线程,如果有过个线程,随机唤醒一个。
notifyAll:作用是唤醒全部因调用wait挂起的线程。

通俗一点就是wait进行阻塞线程,notify进行唤醒线程

例如当在处理生产者和消费者的时候

一个线程修改了一个对象的值,而另个线程感知到了变化,然后进行响应的操作

//生产者
public class Producer implements Runnable {
    private Queue<String> bags;
    private int size;

    public Producer(Queue<String> bags, int size) {
        this.bags = bags;
        this.size = size;
    }


    @Override
    public void run() {
        int i = 0;
        while (true) {
            i++;
            synchronized (bags) {
                while (bags.size() == size) {
                    System.out.println("bags已经满了");
                    //bags达到生产最大值 进行阻塞
                    try {
                        bags.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("生产者-生产:bag" + i);
                bags.add("bag" + i); //生产
                // 唤醒处于阻塞状态下的消费者
                bags.notifyAll();
            }
        }
    }
}
//消费者
public class Consumer implements Runnable {
    private Queue<String> bags;
    private int size;

    public Consumer(Queue<String> bags, int size) {
        this.bags = bags;
        this.size = size;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (bags) {
                while (bags.isEmpty()) {
                    System.out.println("bags为空");
                    try {
                        bags.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String bag = bags.remove();
                System.out.println("消费者消费:" + bag);
                // 唤醒处于阻塞状态下的生产者
                bags.notifyAll();
            }
        }
    }
}
//测试
public class WaitNotifyDemo {
    public static void main(String[] args) {
        Queue<String> queue = new LinkedList<>();
        int size = 10;
        Producer producer = new Producer(queue, size);//生产者
        Consumer consumer = new Consumer(queue, size);//消费者
        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(consumer);
        t1.start();
        t2.start();
    }
}

为什么wait/notify需要加synchronized

​ 1. 其实wait/notify本质上其实是一种条件的竞争,至少来说,wait和notify方法一定是互斥存在的,既然要实现互斥,那么synchronized就是一个很好的解决方法

​ 2. wait和notify是用于实现多个线程之间的通信,而通信必然会存在一个通信的载体,比如我们小时候玩的游戏,用两个纸杯,中间用一根线连接,然后可以实现 比较远距离的对话。而这根线就是载体,那么在这里也是一样,wait/notify是基 于synchronized来实现通信的。也就是两者必须要在同一个频道也就是同一个锁的 范围内

Thread.interrupted Thread.interrupt

interrupt

如何正确终止一个线程

很多人会想到Thread.stop,不过现在被弃用了

stop底层用的是用的是native方法,相当于在操作系统层面上,jvm层面上直接去把这个线程给掐断,类似Linux中去结束一个进程,kill -9 这种方法是有损害的对 同时也会导致结果的不准确性

存在场景(while循环,线程处于阻塞状态下 中断才有意义)

interrupt方法(中断比较友好) interrupt(Java中看不到该源码存在于jvm中)

public class InterruptDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {//判断中断标识 默认是false
                System.out.println("无聊人");
            }
        });
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();//中断(友好)把中断标识由false->true
    }
}

当其他线程通过调用当前线程的interrupt方法,表示向当前线程打个招呼,告诉他可以中断线程的执行了,至于什么时候中断,取决于当前线程自己

interrupted

Thread.interrupted()对设置中断标识的线程复位,并且返回当前的中断状态

public class InterruptedDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            while (true) {
                //当为true 标识被中断过
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("中断" + Thread.currentThread().isInterrupted());
                    Thread.interrupted();
                    System.out.println("复位" + Thread.currentThread().isInterrupted());
                }
            }
        });
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();//中断
    }
}

线程的安全性分析

什么叫线程安全

当多个线程访问某个对象时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的

原子性 可见性 有序性

并发编程问题的源头-原子性 可见性 有序性

  1. 原子性

    即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

  2. 可见性

    是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

    而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

  3. 有序性

    即程序执行的顺序按照代码的先后顺序执行

    在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性

Java内存模型

在这里插入图片描述

在这里插入图片描述

JMM

在这里插入图片描述

Java内存模型是一种抽象结构,它提供了合理的禁用缓存以及禁止重排序

的方法来解决可见性、有序性问题

可见性、有序性问题 解决方案

synchronized、Volatile、final关键字

Happens-Before原则

同步关键字synchronized

作用

可以解决可见性、原子性、有序性问题

范围

  1. 对于普通同步方法,锁是当前实例对象。
//javap -v xxx.class
//对象锁(同一个对象有用) 同一个对象调用才会实现互斥
public synchronized void demo(){

}
  1. 对于静态同步方法,锁是当前类的Class对象。
//类级别锁 xxx.class  不同对象也会发生互斥 互斥必须有同一个资源进行争夺
public synchronized static void demo1(){

}
  1. 对于同步方法块,锁是Synchonized括号里配置的对象。

    //范围可控
    synchronized(this){//对象锁
        
    }
    /*************/
    synchronized(xxx.class){//类级别锁
        
    }
    

java6之前都叫重量级锁

优化

自适应自旋锁

引入偏向锁、轻量级锁

锁消除、锁粗化

Volatile

作用

volatile可以用来解决可见性和有序性问题

解决可见性

Lock指令的作用

硬件层面提供的

将当前处理器缓存行的数据写回到系统内存。 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。(就是禁用缓存)

什么情况下用到volatile

当存在多个线程对同一个共享变量进行修改的时候,

需要增加volatile,保证数据修改的实时可见

解决有序性

CPU层面的内存屏障

Store Barrier:强制所有在store屏障指令之前的store指令,都在

该store屏障指令执行之前被执行,并把store缓冲区的数据都刷到CPU缓存

Load Barrier:强制所有在load屏障指令之后的load指令,都在该load屏障

指令执行之后被执行,并且一直等到load缓冲区被该CPU读完才能执行之后的

load指令

Full Barrier:复合了load和storee屏障的功能

什么是内存屏障

总结

本质上来说:volatile实际上是通过内存屏障来防止指令重排序以及

禁止cpu高速缓存来解决可见性问题。

而#Lock指令,它本意上是禁止高速缓存解决可见性问题,但实际上在

这里,它表示的是一种内存屏障的功能。也就是说针对当前的硬件环境,

JMM层面采用Lock指令作为内存屏障来解决可见性问题

final

final在Java中是一个保留的关键字,可以声明成员变量、方法、类以

及本地变量。一旦你将引用声明作final,你将不能改变这个引用了

final域和线程安全

首先要防止溢出带来的重排序问题,防止"逃逸"

对于final域,编译器和处理器要遵守两个重排序规则。

在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一 个引用变量,这两个操作之间不能重排序。

初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操

作之间不能重排序。

写final域重排序规则

JMM禁止编译器把final域的写重排序到构造函数之外。

编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域的写重排序到构造 函数之外

读域的重排序规则

在一个线程中,初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作,编译器会在读final域操作的前面插入一个LoadLoad屏障。

Happens-Before

Happens-Before是一种可见性规则,它表达的含义是前面一个操作的结果对后续操作是可见的

6种Happens-Before规则

程序顺序规则

监视器锁规则

Volatile变量规则

传递性

start()规则

Join()规则

Atomic

原子类Atomic-无锁工具的典范

public class AtomicDemo {
    private static AtomicInteger atomicInteger = new AtomicInteger();

    public static void incr() {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        atomicInteger.incrementAndGet();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(AtomicDemo::incr).start();
        }
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("----" + atomicInteger.get());
    }

}

ThreadLocal

public class ThreadLocalDemo {
    public static final ThreadLocal<Integer> LOCAL = new ThreadLocal<Integer>() {
        protected Integer initialValue() {
            return 0; //初始值
        }
    };

    public static void main(String[] args) {
        Thread[] threads = new Thread[5];
        //希望每个线程拿到的都是0
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(() -> {
                int num = LOCAL.get();//拿到初始值
                num += 5;
                LOCAL.set(num);
                System.out.println(Thread.currentThread().getName() + "->" + num);
            }, "Thread-" + i);
        }
        for (Thread t : threads) {
            t.start();
        }
    }
}

发布与逃逸

发布对象

发布的意思是使一个对象能够被当前范围之外的代码所使用

不安全发布

对象溢出

一种错误的发布,当一个对象还没有构造完成时,就使它被其他线程所见

安全发布对象的四种方法

在静态初始化函数中初始化一个对象引用

将对象的引用保存到volatile类型的域或者AtomicReference对象中(利用volatile happen-before规则)

将对象的引用保存到某个正确构造对象的final类型域中(初始化安全性)

将对象的引用保存到一个由锁保护的域中(读写都上锁)

JUC 之 AQS

重入锁ReentrantLock

一个持有锁的线程,在释放锁之前,如果再次访问加了该同步锁的其他方法, 这个线程不需要再次争抢锁,只需要记录重入次数

什么是锁

锁是用来解决多线程并发访问共享资源所带来的数据安全性问题的手段。对一个共享资源加锁后,如果有一个线程获得了锁,那么其他线程无法访问这个共享资源

public class LockDemo {
    static Lock lock = new ReentrantLock();//重入锁 解决死锁问题
    public static int count = 0;

    public static void incr() {//递增
        try {
            lock.lock();//获得锁
            Thread.sleep(1);
            decr();
            count++;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//释放锁
        }
    }

    public static void decr() {//递减
        lock.lock();//有需要争抢锁  (不需要争抢锁,记录重入次数)
        count--;
        lock.unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(LockDemo::incr).start();
        }
        Thread.sleep(4000);
        System.out.println("-------" + count);
    }
}

AQS

CountDownLatch

countdownlatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。从命名可以解读到countdown是倒数的意思,类似于我们倒计时的概念。

public class CountDownLatchDemo {

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(3);

        new Thread(()->{
            countDownLatch.countDown(); //倒计时 3 - 1 = 2
        }).start();
        new Thread(()->{
            countDownLatch.countDown(); //倒计时 2 - 1 = 1
        }).start();
        new Thread(()->{
            countDownLatch.countDown(); //倒计时 1 - 1 = 0
        }).start();
        try {
            countDownLatch.await();
            System.out.println("线程执行结束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class CountDownLatchDemo01 implements Runnable{
    static CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(new CountDownLatchDemo01()).start();
        }
        countDownLatch.countDown();
    }

    @Override
    public void run() {
        try {
            countDownLatch.await();//阻塞
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Semaphore

semaphore也就是我们常说的信号灯 semaphore可以控制同时访问的线程个数 通过acquire获取一个许可 如果没有就等待 通过release释放一个许可 有点类似限流的作用 叫信号的的原因也和他的用处有关 比如某商场就5个停车位 每个停车位只能停一辆车如果这个时候来了10辆车 必须要等前面有空的车位才能进入

public class SemaphoreDemo {
    public static void main(String[] args) {
        //当前可以获得最大许可数量是5个
        Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < 10; i++) {
            new car(i, semaphore).start();
        }
    }

    static class car extends Thread {
        private int num;
        private Semaphore semaphore;

        public car(int num, Semaphore semaphore) {
            this.num = num;
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();//获得一个许可
                System.out.println("第" + num + "占用一个车位");
                TimeUnit.SECONDS.sleep(2);
                System.out.println("第" + num + "辆车走了");
                semaphore.release();//释放许可
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

CyclicBarrier

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开启,所有被屏障拦截的线程才会继续工作。

当存在需要所有的子任务都完成时,才执行主任务,这个时候就可以选择使用CyclicBarrier

public class DataImportThread extends Thread {
    private String path;
    private CyclicBarrier cyclicBarrier;

    public DataImportThread(String path, CyclicBarrier cyclicBarrier) {
        this.path = path;
        this.cyclicBarrier = cyclicBarrier;
    }

    @Override
    public void run() {
        System.out.println("导入:" + path + "位置的数据");

        try {
            cyclicBarrier.await();//阻塞
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}
public class CycliBarrierDemo extends Thread {
    @Override
    public void run() {
        System.out.println("开始汇总");
    }

    /**
     * parties 如果因为某种原因导致没有足够多的线程调用await 这个时候会导致所有线程都会被阻塞
     * await(timeout, unit) 设置一个超时等待时间
     * reset重置计数 会抛出一个BrokenBarrierException异常
     *
     * @param args
     */
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new CycliBarrierDemo());
        new DataImportThread("path1", cyclicBarrier).start();
        new DataImportThread("path2", cyclicBarrier).start();
        new DataImportThread("path3", cyclicBarrier).start();
        //结束后做一个汇总
    }
}

Condition

Condition是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒

public class ConditionWaitDemo extends Thread {
    private Lock lock;
    private Condition condition;

    public ConditionWaitDemo(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        System.out.println("开始无聊人wait");
        lock.lock();
        try {
            condition.await();
            System.out.println("结束无聊人wait");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public class ConditionNotifyDemo extends Thread {
    private Lock lock;
    private Condition condition;

    public ConditionNotifyDemo(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        System.out.println("开始无聊人Notify");
        lock.lock();
        condition.signal();
        System.out.println("结束无聊人Notify");
        lock.unlock();
    }
}
public class ConditionDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        ConditionWaitDemo conditionWaitDemo = new ConditionWaitDemo(lock, condition);
        ConditionNotifyDemo conditionNotifyDemo = new ConditionNotifyDemo(lock, condition);
        conditionWaitDemo.start();
        conditionNotifyDemo.start();
    }
}

线程池

线程池基本认识

什么是线程池

提前创建好若干个线程放在一个容器中。如果有任务需要处理,则将任务直接分配给线程池中的线程来执行,任务处理完以后这个线程不会被销毁,而是等待后续分配任务

线程池的好处

  1. 降低创建线程和销毁线程的性能开销

  2. 提高响应速度,当有新任务需要执行是不需要等待线程创建就可以立马执行

  3. 合理的设置线程池大小可以避免因为线程数超过硬件资源瓶颈带来的问题

java中常见的线程池

newFixedThreadPool 创建一个固定的线程池

newSingleThreadExecutor 创建只有一个线程的线程池

newCachedThreadPool 根据实例去调整的线程池 动态调整 可伸缩

newScheduledThreadPool 带定时任务的线程池 延迟性 周期性

public class ThreadPoolDemo implements Runnable {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 100; i++) {
            executorService.execute(new ThreadPoolDemo());
        }
        executorService.shutdown();
    }

    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}

线程池的构造方法

public ThreadPoolExecutor( int corePoolSize, //核心线程数量
                               int maximumPoolSize,//最大线程数
                               long keepAliveTime, //超时时间,超出核心线程数量以外的线程空余存活时间
                               TimeUnit unit, //存活时间单位
                               BlockingQueue<Runnable> workQueue, //保存执行任务的队列
                               ThreadFactory threadFactory, //创建新线程使用的工厂
                               RejectedExecutionHandler handler //当任务无法执行的时候的处理方式
                             )

线程池原理

在这里插入图片描述

对线程池监控

可以自定义线程池 继承ThreadPoolExecutor类

public class ThreadPoolSelf extends ThreadPoolExecutor {
    public ThreadPoolSelf(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    public void shutdown() {
        super.shutdown();
    }

    //启动前
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        System.out.println(System.currentTimeMillis());

    }

    //启动后
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        System.out.println(System.currentTimeMillis());
        System.out.println("初始线程数" + this.getPoolSize());
        System.out.println("核心线程数" + this.getCorePoolSize());
        System.out.println("正在执行的任务数量" + this.getActiveCount());
        System.out.println("已经执行的任务数" + getCompletedTaskCount());
        System.out.println("任务总数" + this.getTaskCount());
    }
}
public class ExecutorSelf {

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolSelf(nThreads, nThreads,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>());
    }

}
public class ThreadPoolDemo implements Runnable {
    public static void main(String[] args) {
        ThreadPoolExecutor executorService = (ThreadPoolExecutor) ExecutorSelf.newFixedThreadPool(3);
        executorService.prestartAllCoreThreads(); //可以提前预热所有核心线程
        for (int i = 0; i < 100; i++) {
            executorService.execute(new ThreadPoolDemo());
        }
        executorService.shutdown();
    }

    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }
}

带返回值的线程

public class CallableFutureDemo implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("无聊人");
        return "wlr";
    }

    public static void main(String[] args) {
        CallableFutureDemo callableFutureDemo = new CallableFutureDemo();
        FutureTask futureTask = new FutureTask(callableFutureDemo);
        new Thread(futureTask).start();
        try {
            //get方法是属于阻塞方法
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

死锁

死锁发生的条件

互斥,共享资源 X 和 Y 只能被一个线程占用;

占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;

不可抢占,其他线程不能强行抢占线程 T1 占有的资源;

循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。

如何解决死锁问题

一旦发生死锁,一般没什么好的方法来解决,只能通过重启应用。所以如果要解决死锁问题,最好的方式就是提前规避

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值