多线程&JUC的学习

1、什么是线程?

**进程:**进程是程序的基本执行实体。一个软件运行之后就是一个进程。

线程 :是操作系统能够运行运算调度的最小单位。它被包含在进程 之中,是进程中的实际运作单位。简单理解:应用软件中互相独立,可以同时运行的功能。

2、多线程的作用?

提高效率。

3、多线程的应用场景?

只要你想让多个事情同时运行就需要用到多线程。

如:软件中的耗时操作、所有的聊天软件、所有的后台服务器等。

4、并发和并行

并发: 在同一时刻,有多个指令在单个CPU交替进行。

并行: 在同一时刻,有多个指令在多个CPU同时执行。

5、多线程的实现方式

5.1 继承Thread类的方式进行实现

ThreadDemo测试类中:

java 复制代码
public class ThreadDemo {
    public static void main(String[] args) {
        /*
        * 多线程的第一种启动方式:
        *   1. 自己定义一个类继承Thread
        *   2. 重写run方法
        *   3. 创建子类的对象,并启动线程
        * */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("线程1");//给线程取名字
        t2.setName("线程2");

        t1.start();//开启线程
        t2.start();
    }
}

创建自己的MyThread类并继承Thread类:

java 复制代码
public class MyThread extends Thread{

    @Override
    public void run() {
        //书写线程要执行代码
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "HelloWorld");
        }
    }
}
5.2 实现Runnable接口的方式进行实现

ThreadDemo类:

java 复制代码
public class ThreadDemo {
    public static void main(String[] args) {
        /*
         * 多线程的第二种启动方式:
         *   1.自己定义一个类实现Runnable接口
         *   2.重写里面的run方法
         *   3.创建自己的类的对象
         *   4.创建一个Thread类的对象,并开启线程
         * */

        //创建MyRun的对象
        //表示多线程要执行的任务
        MyRun mr = new MyRun();

        //创建线程对象
        Thread t1 = new Thread(mr);//执行MyRun里的代码,将任务传递给线程
        Thread t2 = new Thread(mr);

        //给线程设置名字
        t1.setName("线程1");
        t2.setName("线程2");

        //开启线程
        t1.start();
        t2.start();
    }
}

创建MyRun类实现Runnable接口:

java 复制代码
public class MyRun implements Runnable{

    @Override
    public void run() {
        //书写线程要执行的代码
        for (int i = 0; i < 100; i++) {
            //获取到当前线程的对象
            /*Thread t = Thread.currentThread();//谁执行这个代码就获取谁的对象,t1,t2都会执行这个方法
            System.out.println(t.getName() + "HelloWorld!");//所以能通过返回的对象获取线程的名字,打印出来*/
            System.out.println(Thread.currentThread().getName() + "HelloWorld!");
        }
    }
}
5.3 利用Callable接口和Future接口方式实现
java 复制代码
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

            /*
            *   多线程的第三种实现方式:
            *       特点:可以获取到多线程运行的结果
            *
            *       1. 创建一个类MyCallable实现Callable接口
            *       2. 重写call (是有返回值的,表示多线程运行的结果)
            *
            *       3. 创建MyCallable的对象(表示多线程要执行的任务)
            *       4. 创建FutureTask的对象(作用管理多线程运行的结果)
            *       5. 创建Thread类的对象,并启动(表示线程)
            * */

        //创建MyCallable的对象(表示多线程要执行的任务)
        MyCallable mc = new MyCallable();
        //创建FutureTask的对象(作用管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(mc);
        //创建线程的对象
        Thread t1 = new Thread(ft);
        //启动线程
        t1.start();

        //获取多线程运行的结果
        Integer result = ft.get();
        System.out.println(result);
    }
}

class MyCallable implements Callable<Integer> {//泛型里是返回值类型

    @Override
    public Integer call() throws Exception {
        //求1~100之间的和
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum = sum + i;
        }
        return sum;
    }
}
5.4 多线程三种方式实现对比

继承Thread类:

优点:

编程比较简单,可以直接使用Thread类中的方法。

缺点:

Java中是单继承,扩展性较差,继承了一个类,不能再继承其他类。

实现Runnable接口:

优点:

扩展性强,实现该接口的同时,还可以继承其他的类。

缺点:

编程相对复杂,不能直接使用Thread类中的方法。想要使用Thread类中的方法,必须获取线程对象,用对象去调用。

实现Callable接口:

优点:

可以获取到多线程的结果。扩展性强,实现该接口的同时,还可以继承其他的类。

缺点:

编程相对复杂,不能直接使用Thread类中的方法。

6、多线程中常用的成员方法

String getName() 返回此线程的名称
void setName(String name) 设置线程的名字(构造方法也可以设置名字)

细节:
1、如果我们没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X序号,从0开始的)
2、如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置

static Thread currentThread() 获取当前线程的对象

细节:
当JVM虚拟机启动之后,会自动的启动多条线程
其中有一条线程就叫做main线程
他的作用就是去调用main方法,并执行里面的代码
在以前,我们写的所有的代码,其实都是运行在main线程当中

static void sleep(long time) 让线程休眠指定的时间,单位为毫秒

细节:
1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
2、方法的参数:就表示睡眠的时间,单位毫秒
1 秒= 1000毫秒
3、当时间到了之后,线程会自动的醒来,继续执行下面的其他代码

java 复制代码
package com.itheima.a04threadmethod1;

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
       /*
            String getName()                    返回此线程的名称
            void setName(String name)           设置线程的名字(构造方法也可以设置名字)
            细节:
                1、如果我们没有给线程设置名字,线程也是有默认的名字的
                        格式:Thread-X(X序号,从0开始的)
                2、如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置

            static Thread currentThread()       获取当前线程的对象
            细节:
                当JVM虚拟机启动之后,会自动的启动多条线程
                其中有一条线程就叫做main线程
                他的作用就是去调用main方法,并执行里面的代码
                在以前,我们写的所有的代码,其实都是运行在main线程当中

            static void sleep(long time)        让线程休眠指定的时间,单位为毫秒
            细节:
                1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
                2、方法的参数:就表示睡眠的时间,单位毫秒
                    1 秒= 1000毫秒
                3、当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
       */
        //1.创建线程的对象
        MyThread t1 = new MyThread("飞机");
        MyThread t2 = new MyThread("坦克");

        //2.开启线程
        t1.start();
        t2.start();

        //哪条线程执行到这个方法,此时获取的就是哪条线程的对象
        Thread t = Thread.currentThread();
        String name = t.getName();
        System.out.println(name);//main

        System.out.println("11111111111");
        Thread.sleep(5000);
        System.out.println("22222222222");
    }
}
class MyThread extends Thread{

    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1000);//每打印一次休息一秒钟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName() + "@" + i);
        }
    }
}

7、线程的优先级

抢占式调度:多个线程抢占CPU的执行权。

非抢占式调度:所有线程轮流的执行。

Java中采用抢占式调度 ,重点掌握随机 ,优先级越大,那么这条线程抢到CPU的概率是最大的,但并不是绝对的,优先级小的也有可能先于优先级高的运行完。在Java中优先级有10级,1~10,最小的是1,最大的是10,如果没有设置优先级,默认是5。

java 复制代码
package com.itheima.a05threadmethod2;

public class ThreadDemo {
    public static void main(String[] args){
       /*
            setPriority(int newPriority)        设置线程的优先级
            final int getPriority()             获取线程的优先级
       */
        //创建线程要执行的参数对象
        MyRunnable mr = new MyRunnable();
        //创建线程对象
        Thread t1 = new Thread(mr,"飞机");
        Thread t2 = new Thread(mr,"坦克");

        t1.setPriority(1);
        t2.setPriority(10);

        t1.start();
        t2.start();
    }
}
class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

8、设置为守护线程

final void setDaemon(boolean on) 设置为守护线程

细节:
当其他的非守护线程执行完毕之后,守护线程会陆续结束
通俗易懂:
当女神线程结束了,那么备胎也没有存在的必要了

java 复制代码
package com.itheima.a06threadmethod3;

public class ThreadDemo {
    public static void main(String[] args) {
       /*
            final void setDaemon(boolean on)    设置为守护线程
            细节:
                当其他的非守护线程执行完毕之后,守护线程会陆续结束
            通俗易懂:
                当女神线程结束了,那么备胎也没有存在的必要了
       */
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");
        t2.setDaemon(true);//把第二个线程设置为守护线程(备胎线程)
        t1.start();
        t2.start();
    }
}

class MyThread1 extends Thread{

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}

class MyThread2 extends Thread{

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}

9、出让线程/礼让线程**(了解)**

public static void yield() 出让线程/礼让线程

java 复制代码
package com.itheima.a07threadmethod4;

public class ThreadDemo {
    public static void main(String[] args) {
       /*
            public static void yield()      出让线程/礼让线程

       */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("飞机");
        t2.setName("坦克");

        t1.start();
        t2.start();
    }
}
class MyThread extends Thread{

    @Override
    public void run() {//"飞机"  "坦克"
        for (int i = 1; i <= 100; i++) {

            System.out.println(getName() + "@" + i);
            //表示出让当前CPU的执行权
            Thread.yield();
        }
    }
}

细节:

假如"飞机"线程先抢到了CPU的使用权,执行yield方法,出让了CPU的使用权,但是新一轮循环开始时,"飞机"和"坦克"两个线程会重新抢夺CPU的使用权,所以"飞机"线程可能会再次抢到CPU的使用权。

次方法只是尽可能地让结果均匀一点,不是绝对的均匀。此方法了解即可。

10、插入线程/插队线程**(了解)**

public final void join() 插入线程/插队线程

java 复制代码
package com.itheima.a08threadmethod5;

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
       /*
            public final void join()  插入线程/插队线程
       */

        MyThread t = new MyThread();
        t.setName("土豆");
        t.start();

        //表示把t这个线程,插入到当前线程之前。
        //t:土豆
        //当前线程: main线程
        t.join();

        //执行在main线程当中的
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程" + i);
        }
    }
}
class MyThread extends Thread{

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}

11、同步代码块

同步代码块:把操作共享数据的代码锁起来。解决多线程操作共享数据时带来的数据安全问题。

格式:

synchronized (锁) {

操作共享数据的代码;

}

**特点1:**锁默认打开,有一个线程进去了,锁自动关闭。

**特点2:**里面的代码全部执行完毕,线程出来,锁自动打开。

java 复制代码
package com.itheima.a09threadsafe1;

public class ThreadDemo {
    public static void main(String[] args) {
       /*
           需求:
                某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
       */
        //创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        //起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        //开启线程
        t1.start();
        t2.start();
        t3.start();
    }
}

class MyThread extends Thread {

    //表示这个类所有的对象,都共享ticket数据
    static int ticket = 0;//0 ~ 99

    //    static Object object=new Object();//加static  锁对象可以是任意的,但要唯一
    @Override
    public void run() {
        while (true) {
            //同步代码块
            synchronized (MyThread.class) {//括号里的是当前类的字节码文件对象
                if (ticket < 100) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket + "张票!!!");
                } else {
                    break;
                }
            }
        }
    }
}

细节1:synchronized代码块不能写在循环的外面。

细节2:synchronized代码块锁对象一定要是唯一的。

12、同步方法

同步方法:就是把synchronized关键字加到方法上。

格式:

修饰符 synchronized 返回值类型 方法名(方法参数){...}

特点1:同步方法是锁在方法里面所有的代码

特点2:锁对象不能自己指定,有Java规定的

静态方法:锁对象是,当前类的字节码文件对象

非静态方法:锁对象是this,当前方法的调用者

java 复制代码
package com.itheima.a10threadsafe2;

public class ThreadDemo {
    public static void main(String[] args) {
       /*
           需求:
           某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
           利用同步方法完成
           技巧:同步代码块
       */
        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        Thread t3 = new Thread(mr);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class MyRunnable implements Runnable {

    int ticket = 0;

    @Override
    public void run() {
        //1.循环
        while (true) {
            //2.同步代码块(同步方法)
            if (method()) break;

        }
    }

    //非静态,锁对象是this
    private synchronized boolean method() {
        //3.判断共享数据是否到了末尾,如果到了末尾
        if (ticket == 100) {
            return true;
        } else {
            //4.判断共享数据是否到了末尾,如果没有到末尾
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "在卖第" + ticket + "张票!!!");
        }
        return false;
    }
}

StringBuilder和StringBuffer

如果代码是单线程不需要考虑多线程数据安全的状况,用StringBuilder。

多线程需要考虑数据安全用StringBuffer,StringBuffer是线程安全的,里面所有方法都是同步的。

13、Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里上了锁,在哪里释放了锁。为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。Lock中提供了获得锁和释放锁的方法:

void Lock():获得锁手动上锁

void unLock():释放锁手动释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。

ReentranrLock的构造方法:

ReentranrLock():创建一个ReentranrLock的实例

java 复制代码
package com.itheima.a11threadsafe3;

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

public class ThreadDemo {
    public static void main(String[] args) {
       /*
           需求:
                某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
                用JDK5的lock实现
       */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class MyThread extends Thread{

    static int ticket = 0;

    static Lock lock = new ReentrantLock();//Lock是一个接口,要创建它实现类的对象

    @Override
    public void run() {
        //1.循环
        while(true){
            //2.同步代码块
            //synchronized (MyThread.class){
            lock.lock();// 加锁   //2 //3
            try {
                //3.判断
                if(ticket == 100){
                    break;
                    //4.判断
                }else{
                    Thread.sleep(10);
                    ticket++;
                    System.out.println(getName() + "在卖第" + ticket + "张票!!!");
                }
                //  }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();//释放锁
            }
        }
    }
}

14、死锁

死锁:在程序当中出现了锁的嵌套,外面一个锁,里面一个锁。

注意:死锁不是一个知识点,是一个错误!

理解以下代码,不要犯错误。

java 复制代码
package com.itheima.a12deadlock;


public class ThreadDemo {
    public static void main(String[] args) {
       /*
           需求:
                死锁
       */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("线程A");
        t2.setName("线程B");

        t1.start();
        t2.start();
    }
}
class MyThread extends Thread {

    //定义两把锁,一把A锁,一把B锁
    static Object objA = new Object();
    static Object objB = new Object();

    @Override
    public void run() {
        //1.循环
        while (true) {
            if ("线程A".equals(getName())) {//如果是线程A
                synchronized (objA) {
                    System.out.println("线程A拿到了A锁,准备拿B锁");//A
                    synchronized (objB) {
                        System.out.println("线程A拿到了B锁,顺利执行完一轮");
                    }
                }
            } else if ("线程B".equals(getName())) {//如果是线程B
                if ("线程B".equals(getName())) {
                    synchronized (objB) {
                        System.out.println("线程B拿到了B锁,准备拿A锁");//B
                        synchronized (objA) {
                            System.out.println("线程B拿到了A锁,顺利执行完一轮");
                        }
                    }
                }
            }
        }
    }
}

15、等待唤醒机制

15.1 用生产者和消费者来实现

生产者:生产数据。

消费者:消费数据。

生产者和消费者模式是一个十分经典的多线程协作模式。

以厨师和吃货为例,生产者是厨师,消费者是吃货。核心思想:利用桌子来控制线程的执行。

常见方法:

void wait() 当前线程等待,直到被其他线程唤醒

void notify() 随机唤醒单个线程

void notify() 唤醒所有线程

java 复制代码
package com.itheima.a13waitandnotify;


public class ThreadDemo {
    public static void main(String[] args) {
       /*
       *    需求:完成生产者和消费者(等待唤醒机制)的代码
       *         实现线程轮流交替执行的效果
       * */
        //创建线程的对象
        Cook c = new Cook();
        Foodie f = new Foodie();
        //给线程设置名字
        c.setName("厨师");
        f.setName("吃货");
        //开启线程
        c.start();
        f.start();
    }
}

class Cook extends Thread{

    @Override
    public void run() {
        /*
         * 1. 循环
         * 2. 同步代码块
         * 3. 判断共享数据是否到了末尾(到了末尾)
         * 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
         * */

        while (true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    //判断桌子上是否有食物
                    if(Desk.foodFlag == 1){
                        //如果有,就等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        //如果没有,就制作食物
                        System.out.println("厨师做了一碗面条");
                        //修改桌子上的食物状态
                        Desk.foodFlag = 1;
                        //叫醒等待的消费者开吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}


class Foodie extends Thread{

    @Override
    public void run() {
        /*
         * 1. 循环
         * 2. 同步代码块
         * 3. 判断共享数据是否到了末尾(到了末尾)
         * 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
         * */

        while(true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    //先判断桌子上是否有面条
                    if(Desk.foodFlag == 0){
                        //如果没有,就等待
                        try {
                            Desk.lock.wait();//让当前线程跟锁进行绑定
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        //把吃的总数-1
                        Desk.count--;
                        //如果有,就开吃
                        System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗!!!");
                        //吃完之后,唤醒厨师继续做
                        Desk.lock.notifyAll();
                        //修改桌子的状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}

class Desk {
    /*
     * 作用:控制生产者和消费者的执行
     *
     * */

    //是否有面条  0:没有面条  1:有面条
    public static int foodFlag = 0;

    //总个数
    public static int count = 10;

    //锁对象
    public static Object lock = new Object();
}
15.2 用阻塞队列方式实现

BlockingQueue阻塞队列的两个实现类:

ArrayBlockingQueue:底层是数组,有界(有长度的界限,创建此类对象时,要指定队列的长度)。

LinkedBlockingQueue:底层是链表,无界。但不是真正的无界,最大为int的最大值。

java 复制代码
package com.itheima.a14waitandnotify;


import java.util.concurrent.ArrayBlockingQueue;

public class ThreadDemo {
    public static void main(String[] args) {
       /*
       *    需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
       *    细节:
       *           生产者和消费者必须使用同一个阻塞队列
       * */

        //1.创建阻塞队列的对象,泛型里表示队列里数据的类型
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
        
        //2.创建线程的对象,并把阻塞队列传递过去
        Cook c = new Cook(queue);
        Foodie f = new Foodie(queue);

        //3.开启线程
        c.start();
        f.start();
    }
}

class Cook extends Thread{

    ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while(true){
            //不断的把面条放到阻塞队列当中
            try {
                queue.put("面条");//放进数据
                System.out.println("厨师放了一碗面条");//定义在锁的外面,控制台看着数据有问题,其实没影响
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Foodie extends Thread{

    ArrayBlockingQueue<String> queue;

    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while(true){
            //不断从阻塞队列中获取面条
            try {
                String food = queue.take();//取出数据
                System.out.println(food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//put和take方法底层有锁

16、线程的六种状态

**New:**新建状态,创建线程对象

至今尚未启动的线程处于这种状态。
**Runnable:**就绪状态,start方法

正在Java虚拟机中执行的线程处于这种状态。
**Blocked:**阻塞状态,无法获得锁对象

受阻塞并等待某个监视器锁的线程处于这种状态。
**Waiting:**等待状态,wait方法

无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
**Timed_Waiting:**计时等待状态,sleep方法

等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
**Terminated:**结束状态,全部代码运行完毕

已经退出的线程处于这种状态。

在给定时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反应所有操作系统线程状态。

17、线程池

线程池主要核心原理:

  • 创建一个池子,池子中是空的。
  • 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还池子,下次再提交任务时,不需要创建新的线程,直接复用已有的线程即可。
  • 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。

线程池代码实现:

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。

public static ExecutorService newCachedThreadPool()

创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool(int nThreads)

创建有上限的线程池

步骤:

  1. 创建线程池

  2. 提交任务

  3. 所有任务全部执行完毕,关闭线程池

java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {
    /*
        public static ExecutorService newCachedThreadPool()             创建一个没有上限的线程池
        public static ExecutorService newFixedThreadPool (int nThreads) 创建有上限的线程池
    */
        
        //1.获取线程池对象
      //ExecutorService pool1 = Executors.newCachedThreadPool();
        ExecutorService pool2 = Executors.newFixedThreadPool(3);
        //2.提交任务
        pool2.submit(new MyRunnable());
        pool2.submit(new MyRunnable());
        pool2.submit(new MyRunnable());
        pool2.submit(new MyRunnable());
        pool2.submit(new MyRunnable());
        pool2.submit(new MyRunnable());
        
        //3.销毁线程池
        //pool1.shutdown();
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}

18、自定义线程池

任务拒绝策略:

ThreadPoolExecutor.AbortPolicy

默认策略:丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy 丢弃任务,但是不抛出异常,不推荐
ThreadPoolExecutor.DiscardOldestPolicy

抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy 调用任务的run()方法,绕过线程池直接执行

java 复制代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyThreadPoolDemo1 {
    public static void main(String[] args){
    //创建自己的线程池对象
    /*
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
        (核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

        参数一:核心线程数量              不能小于0
        参数二:最大线程数                不能小于0,最大数量 >= 核心线程数量
        参数三:空闲线程最大存活时间       不能小于0
        参数四:时间单位                  用TimeUnit指定
        参数五:任务队列                  不能为null
        参数六:创建线程工厂              不能为null
        参数七:任务的拒绝策略             不能为null
    */

        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,  //核心线程数量,能小于0
                6,  //最大线程数,不能小于0,最大数量 >= 核心线程数量
                60,//空闲线程最大存活时间
                TimeUnit.SECONDS,//时间单位
                new ArrayBlockingQueue<>(3),//任务队列
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
        );

    }
}

19、线程池多大合适?

什么是最大并行数?

跟电脑的CPU有关系,4核8线程,4核可以同时干四件事情,因特尔的超线程技术,可以把原本的4个虚拟成8个,最大并行数是8。

java 复制代码
public class MyThreadPoolDemo2 {
    public static void main(String[] args) {
        //Runtime.getRuntime()获取系统的运行环境
        int count = Runtime.getRuntime().availableProcessors();//向Java虚拟机返回可用处理器的数目
        System.out.println(count);
    }
}

CPU密集型运算:最大并行数 + 1(候补的)
I/O密集型运算:

最大并行数 * 期望CPU利用率 * 总时间(CPU计算时间+等待时间)/CPU计算时间

(thread dump 工具可以测试到CPU的计算时间和等待时间)

以4核8线程为例:

从本地文件中读取两个数据,并进行相加

操作1:读取两个数据(跟硬盘有关系,跟CPU没有关系) 假设用时1秒钟

操作2:相加(跟CPU有关系) 假设用时1秒钟

总时间是2秒,CPU计算时间是1秒

那么:8 * 100% * (100% / 50%) =16 ,此时可以规定线程池的总大小就是16

学习笔记,有问题请指出,谢谢。

相关推荐
朝九晚五ฺ7 小时前
【Linux探索学习】第十四弹——进程优先级:深入理解操作系统中的进程优先级
linux·运维·学习
猫爪笔记9 小时前
前端:HTML (学习笔记)【1】
前端·笔记·学习·html
pq113_610 小时前
ftdi_sio应用学习笔记 3 - GPIO
笔记·学习·ftdi_sio
澄澈i10 小时前
设计模式学习[8]---原型模式
学习·设计模式·原型模式
爱米的前端小笔记10 小时前
前端八股自学笔记分享—页面布局(二)
前端·笔记·学习·面试·求职招聘
alikami11 小时前
【前端】前端学习
学习
一只小菜鸡..11 小时前
241118学习日志——[CSDIY] [ByteDance] 后端训练营 [06]
学习
Hacker_Oldv12 小时前
网络安全的学习路线
学习·安全·web安全
蒟蒻的贤13 小时前
vue学习11.21
javascript·vue.js·学习
高 朗13 小时前
【GO基础学习】基础语法(2)切片slice
开发语言·学习·golang·slice