java多线程的编程实现方式与线程管理机制

多线程

进程:

进程是程序的基本执行实体

每一个正在运行的软件都是一个进程

线程:

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

简单理解:

应用软件中互相独立,可以同时运行的功能

为什么有多线程

提高程序的效率

多线程应用场景:

软件中的耗时操作:拷贝、迁移大文件、加载大量的资源文件

所有的聊天软件

所有的后台服务器

并发和并行

并发:

在同一时刻,有多个指令在单个CPU上交替执行

并行:

在同一时刻,有多个指令在单个CPU上同时进行

CPU有:2核4线程、4核8线程、8核16线程、16核32线程、32核64线程

线程的数量就是:你的电脑同时能运行多少条线程

多线程的实现方式

1、继承Thread类的方式进行实现

  • 将一个类声明为Thread的子类。 这个子类应该重写run类的方法Thread 。 然后可以分配并启动子类的实例。

ctrl+D向下复制该行
package com.cg.a01threadrealize1;
​
public class MyThread extends Thread{
​
    @Override
    public void run() {
        //书写线程要执行的代码
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"HelloWorld");
        }
    }
}
package com.cg.a01threadrealize1;
​
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");
​
        //start 开启线程
        t1.start();
        t2.start();
    }
}
————————————————————————————————————————————————————————————————————————
线程1HelloWorld
线程1HelloWorld
线程2HelloWorld
线程1HelloWorld
线程2HelloWorld
线程1HelloWorld
线程1HelloWorld
线程2HelloWorld
线程2HelloWorld
线程2HelloWorld
线程1HelloWorld
............
    两个线程结果交替进行

2、实现Runnable接口的方式进行实现

  • 创建一个线程是声明实现类Runnable接口。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。

package com.cg.a02threadrealize2;
​
public class MyRun implements Runnable{
    
    @Override
    public void run() {
        //书写线程要执行的代码
        for (int i = 0; i < 100; i++) {
            //先获取到当前线程的对象
//            Thread t = Thread.currentThread();
//            System.out.println(t.getName()+"HelloWorld");
            System.out.println(Thread.currentThread().getName()+"HelloWorld");
        }
    }
}
package com.cg.a02threadrealize2;
​
public class ThreadDemo {
​
    public static void main(String[] args) {
        /*
         * 多线程的第二种启动方式:
         *   1、自己定义一个类实现Runnable接口
         *   2、重写里面的run方法
         *   3、创建自己的类的对象
         *   3、创建一个Thread类的对象,并开启线程
         * */
​
        //创建MyRun的对象
        //表示多线程要执行的任务
        MyRun mr = new MyRun();
​
        //创建线程对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
​
        //给线程设置名字,方便区分
        t1.setName("线程1");
        t2.setName("线程2");
​
        //开启线程
        t1.start();
        t2.start();
​
​
    }
}
——————————————————————————————————————————————————————————————————
    运行结果交替出现:
    线程2HelloWorld
    线程2HelloWorld
    线程2HelloWorld
    线程2HelloWorld
    线程1HelloWorld
    线程1HelloWorld
    线程1HelloWorld
    线程2HelloWorld
    线程1HelloWorld
    线程1HelloWorld
    线程2HelloWorld
    线程1HelloWorld
    线程2HelloWorld
    线程2HelloWorld
    线程1HelloWorld
    线程2HelloWorld
    线程1HelloWorld
    线程1HelloWorld
    ..........

3、利用Callable接口和Future接口方式实现

前两种方式的补充

重写的run方法都没有返回值,无法获取多线程的返回结果

package com.cg.a03threadrealize3;
​
import java.util.concurrent.Callable;
​
public 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;
    }
}
package com.cg.a03threadrealize3;
​
import java.util.concurrent.FutureTask;
​
public class ThreadDemo {
    public static void main(String[] args) throws Exception {
        /*
         * 多线程的第三种启动方式:
         *   特点:可以获取到多线程运行的结果
         *
         *   1、自己定义一个MyCallable类实现Callable接口
         *   2、重写里面的call方法(有返回值,表示多线程运行的结果)
         *
         *   3、创建自己的MyCallable类的对象
         *   4、创建一个FutureTask(Future是一个接口)的对象(作用管理多线程运行的结果)
         *   5、创建Thread类的对象(表示线程),并启动
         * */
​
        //创建MyCallable的对象(表示多线程要执行的任务)
        MyCallable mc = new MyCallable();
        //创建FutureTask的对象(作用管理多线程运行的结果)
        FutureTask<Integer> tf = new FutureTask<>(mc);
        //创建线程的对象
        Thread t1 = new Thread(tf);
        //启动线程
        t1.start();
​
        //获取到多线程运行的结果
        Integer result = tf.get();
        System.out.println(result);
​
    }
}
——————————————————————————————————————————————————————————————————————
    5050

三种方法区别

方法1:

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

缺:扩展性较差,不能再继承其他的类

方法2、3:

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

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

Thread常见的成员方法

方法名称说明
StringgetName()返回此线程的名称
voidsetName(String name)将此线程的名称更改为等于参数 name
static ThreadcurrentThread()返回对当前正在执行的线程对象的引用。
static voidsleep(long millis)使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
voidsetPriority(int newPriority)更改此线程的优先级
intgetPriority()返回此线程的优先级
voidsetDaemon(boolean on)将此线程标记为守护线程或用户线程
static voidyield()对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。 出让线程/礼让线程
voidjoin()等待这个线程死亡。 插入线程/插队线程

package com.cg.a04threadrealize4;
​
public 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);
        }
    }
}
package com.cg.a04threadrealize4;
​
public class ThreadDemo {
​
    public static void main(String[] args) throws InterruptedException {
        /*
            String         getName()                返回此线程的名称
            void           setName(String name)     设置线程的名字(构造方法也可以设置名字)
            细节:
                1、如果没有给线程设置名字,线程是有默认名字的
                    格式:Thread-x (x是序号,从0开始)
                2、构造方法也可以设置名字,子类不能继承构造方法
​
            static Thread  currentThread()          获取当前线程的对象
            细节:
                当JVM虚拟机启动之后,会自动的启动多条线程
                其中有一条线程就叫做main线程
                它的作用就是去调用main方法,并执行里面的代码
                再以前,我们写的所有的代码,其实都是运行在main线程当中
​
            static void    sleep(long millis)       让线程休眠指定的时间,单位毫秒
            细节:
                1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
                2、方法的参数:就表示睡眠的时间,单位毫秒
                    1秒 = 1000毫秒
                3、当时间到了之后,线程就会自动的醒来,继续执行下面的其他代码
​
        */
​
        //1、创建线程对象
        MyThread t1 = new MyThread("线程1");
        MyThread t2 = new MyThread("线程2");
​
        //2、开启线程
        t1.start();
        t2.start();
​
        //哪条线程执行到这个地方,此时获取的就是哪条线程的对象
        /*Thread t = Thread.currentThread();
        String name = t.getName();
        System.out.println(name);  main */
​
       /* System.out.println("1111111111111");
        Thread.sleep(5000);
        System.out.println("2222222222222");*/
​
​
    }
}

抢占式调度(随机性)

优先级越大,这条线程抢到cpu的概率越大

优先执行,优先级不是绝对的,是概率问题

package com.cg.a05threadrealize2;

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "——————" + i);
        }
    }
}
package com.cg.a05threadrealize2;
​
public class ThreadDemo {
​
    public static void main(String[] args) {
        /*
            void  setPriority(int newPriority)   更改此线程的优先级
            int   getPriority()                  返回此线程的优先级
        */
​
        //创建线程要执行的参数对象
        MyRunnable mr = new MyRunnable();
​
        //创建线程对象
        Thread t1 = new Thread(mr,"飞机");
        Thread t2 = new Thread(mr,"坦克");
​
        /*//默认优先级是多少 ——5
        System.out.println(t1.getPriority());
        System.out.println(t2.getPriority());
        System.out.println(Thread.currentThread().getPriority());*/
​
        t1.setPriority(1);
        t2.setPriority(10);
​
        t1.start();
        t2.start();
​
    }
}

守护线程

package com.cg.a06threadrealize1;

public class MyThread1 extends Thread{

    @Override
    public void run() {
        //书写线程要执行的代码
        for (int i = 1; i <= 10; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}
package com.cg.a06threadrealize1;

public class MyThread2 extends Thread{

    @Override
    public void run() {
        //书写线程要执行的代码
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}
package com.cg.a06threadrealize1;

public class ThreadDemo {

    public static void main(String[] args) {
        /*
            final void setDaemon(boolean on) 设置为守护线程
            细节:
                当其他的非守护线程执行完毕之后,守护线程会陆续结束
                通俗易懂:
                当线程1结束了,线程2也没有存在的必要了
        */

        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("线程1");
        t2.setName("线程2");

        //把第二个线程设置为守护线程(备胎线程)
        t2.setDaemon(true);

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

出让线程/礼让线程

package com.cg.a07threadrealize1;

public class MyThread extends Thread{

    @Override
    public void run() {
        //书写线程要执行的代码
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + "@" + i);

            //表示出让当前CPU的执行权
            //尽可能让结果均匀一点,不是绝对的
            //比如线程1又会抢回执行权
            Thread.yield();
        }
    }
}
package com.cg.a07threadrealize1;

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

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

        t1.setName("线程1");
        t2.setName("线程2");

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

插入线程/插队线程

package com.cg.a08threadrealize1;

public class MyThread extends Thread{

    @Override
    public void run() {
        //书写线程要执行的代码
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + "@" + i);
        }
    }
}
package com.cg.a08threadrealize1;
​
import com.cg.a07threadrealize1.MyThread;
​
public class ThreadDemo {
​
    public static void main(String[] args) {
        /*
            public final void join()     插入线程/插队线程
        */
​
        MyThread t = new MyThread();
        t.setName("土豆");
        t.start();
​
​
        //执行在main线程当中
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程" + i);
        }
    }
}
——————————————————————————————————————————————
    main线程0
    main线程1
    main线程2
    main线程3
    main线程4
    main线程5
    main线程6
    main线程7
    main线程8
    main线程9
    土豆@1
    土豆@2
    土豆@3
    土豆@4
    土豆@5
    土豆@6
    土豆@7
    土豆@8
    土豆@9
    ..........
package com.cg.a08threadrealize1;
​
import com.cg.a07threadrealize1.MyThread;
​
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);
        }
    }
}
__________________________________________________________________________
    土豆@1
    土豆@2
    土豆@3
    土豆@4
    土豆@5
    土豆@6
    土豆@7
    土豆@8
    土豆@9
    ......
    main线程3
    main线程4
    main线程5
    main线程6
    main线程7
    main线程8
    main线程9

线程的生命周期

线程安全

问题

卖出了重复的超出范围的票

package com.cg.a09threadrealize1;

public class MyThread extends Thread{

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

    @Override
    public void run() {
        while (true){
            if (ticket < 100){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket++;
                System.out.println(getName() + "正在买第" + ticket + "张票!!!");
            }else {
                break;
            }
        }
    }
}
package com.cg.a09threadrealize1;

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();

    }
}
——————————————————————————————————————————————————————————————————————————
    窗口2正在买第2张票!!!
    窗口1正在买第3张票!!!
    窗口3正在买第2张票!!!
    窗口2正在买第5张票!!!
    窗口1正在买第4张票!!!
    窗口3正在买第6张票!!!
    窗口2正在买第7张票!!!
    ......
    窗口2正在买第99张票!!!
    窗口3正在买第100张票!!!
    窗口1正在买第101张票!!!
    窗口2正在买第102张票!!!

方法:使用同步代码块

把操作共享数据的代码锁起来

格式

synchronized(锁对象){
    操作共享数据的代码
}

特点:

1、锁默认打开,有一个线程进去了,锁自动关闭

2、里面的代码全部执行完毕,线程出来,锁自动打开

锁对象不一样,就没有意义了,相当于,一人一个门

package com.cg.a09threadrealize1;

public class MyThread extends Thread{

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

    //锁对象一定要是唯一的
    //static Object obj = new Object();

    @Override
    public void run() {
        while (true){
            //线程安全 —— 同步代码块
            synchronized (MyThread.class){ //obj
                if (ticket < 100){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket++;
                    System.out.println(getName() + "正在买第" + ticket + "张票!!!");
                }else {
                    break;
                }
            }
        }
    }
}

方法:同步方法

格式

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

特点:

1、同步方法是锁住方法里面所有的代码

2、锁对象不能自己指定

非静态:this

静态:当前类的字节码文件对象

抽取方法快捷键:ctrl+alt+M

package com.cg.a10threadrealize2;

public class MyRunnable implements Runnable {


    //不用使用static 因为这个类只创建一次,作为一个参数让线程执行
    int ticket = 0;


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

    //方法是非静态的 锁对象是this , 这里是创建的mr
    private synchronized boolean method() {
        //3、判断共享数据是否到了末尾,如果到了末尾
        if (ticket == 100) {
            return true;
        } else {
            //4、判断共享数据是否到了末尾,如果没有到末尾
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "正在买第" + ticket + "张票!!!");
        }
        return false;
    }
}
package com.cg.a10threadrealize2;

public class ThreadDemo {

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


        MyRunnable mr = new MyRunnable();

        //创建线程对象
        Thread t1 = new Thread(mr, "窗口1");
        Thread t2 = new Thread(mr, "窗口2");
        Thread t3 = new Thread(mr, "窗口3");

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

    }

}

lock锁

在同步代码块:锁自动关闭、锁自动打开

JDK5提供了一个新的锁对象Lock,可以手动的去加或释放锁

Lock比使用synchronized方法和语句可以获得更广泛的锁定操作

Lock中提供了获得锁和释放锁的方法

void lock():获得锁

void unlock():释放锁

lock是接口,不能直接实例化,采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法

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

package com.cg.a11threadrealize1;

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

public class MyThread extends Thread {

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

    //共享 同一把锁
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        //1、循环
        while (true) {
            //2、同步代码块()
            lock.lock();
            //synchronized (MyThread.class){
            try {
                //3、判断共享数据是否到了末尾,如果到了末尾
                if (ticket == 100) {
                    break;
                } else {
                    //4、判断共享数据是否到了末尾,如果没有到末尾
                    Thread.sleep(100);
                    ticket++;
                    System.out.println(Thread.currentThread().getName() + "正在买第" + ticket + "张票!!!");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally { //finally 里面的代码无论怎样都会被执行
                lock.unlock();
            }
            //}
        }
    }
}

package com.cg.a11threadrealize1;


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();

    }

}

死锁-是一个错误

锁形成了一个嵌套

外面一个锁,里面一个锁

千万不要让两个锁嵌套起来

package com.cg.a12threadrealize1;


public class MyThread extends Thread {

    //共享 同一把锁
    static Object objA = new Object();
    static Object objB = new Object();

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

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();

    }
}

等待唤醒机制(生产者和消费者)

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

核心思想:控制线程的执行

消费者:消费数据 生产者:生产数据

1、判断桌子上是否有食物 1、判断桌子上是否有食物

2、如果没有就等待 2、有:等待

3、如果有就开吃 3、没有:制作食物

4、吃完之后,唤醒厨师继续做 4、把食物放在桌子上

5、叫醒等待的消费者开吃

常见方法

方法名称说明
void wait()当前线程等待,直到被其他线程唤醒
void notify()随机唤醒单个线程
void notifyAll()唤醒所有线程

package com.cg.a13waitandnotify;

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

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

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

    //锁对象
    public static Object lock = new Object();
}
package com.cg.a13waitandnotify;

public 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;
                    }
                }
            }
        }
    }
}
package com.cg.a13waitandnotify;
​
public 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();
                    }
                }
            }
        }
    }
}
package com.cg.a13waitandnotify;
​
public class ThreadDemo {
    /*
        需求:完成生产者和消费者(等待唤醒机制)的代码
    *       实现线程轮流交替执行的效果
    */
    public static void main(String[] args) {
        //创建线程对象
        Cook cook = new Cook();
        Foodie foodie = new Foodie();
​
        //设置线程名字
        cook.setName("厨师");
        foodie.setName("吃货");
​
        //开启线程
        cook.start();
        foodie.start();
    }
}

等待唤醒机制(阻塞队列方式)

队列

数据放进一个管道,先放的被先拿

阻塞

放数据时:放不进去,会等着,也叫做阻塞

拿数据时:取出第一个数据,取不到会等着,也叫做阻塞

阻塞队列的继承结构

接口:

Iterable 迭代器、增强for

Collection 集合

Queue 队列

BlockingQueue 阻塞队列

不能直接创建对象

实现类

ArrayBlockingQueue:底层是数组,有界(有长度的界限)

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

package com.cg.a14waitandnotify;

import java.util.concurrent.ArrayBlockingQueue;

public 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();
            }
        }
    }
}
/*打印的语句没有被锁到 
该线程执行一次锁里面的内容就释放锁并唤醒其他线程 
锁被其他线程拿到 但没有被锁的语句任然可以执行 就会出现多次执行*/
package com.cg.a14waitandnotify;

import java.util.concurrent.ArrayBlockingQueue;

public 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();
            }
        }
    }
}
package com.cg.a14waitandnotify;
​
import java.util.concurrent.ArrayBlockingQueue;
​
public class ThreadDemo {
    /*
        需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
    *       细节:
                生产者和消费者必需用同一个阻塞队列
​
    */
    public static void main(String[] args) {
​
        //1、测试类创建阻塞对象
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
​
        //2、创建线程的对象,并把阻塞队列传递过去
        Cook cook = new Cook(queue);
        Foodie foodie = new Foodie(queue);
​
        //3、取名字
        cook.setName("厨师");
        foodie.setName("吃货");
​
        //4、开启线程
        cook.start();
        foodie.start();
    }
}

线程的状态(六大状态)

没有定义运行状态:被交出去了


线程池

前面写的多线程的弊端:

1、用到线程的时候就创建

2、用完之后就消失

会浪费操作系统的资源

核心原理

1、创建一个池子,池子中是空的

2、提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可

3、但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

代码实现

1、创建线程池

2、提交任务

3、所有任务全部执行完毕,关闭线程池(实际开发中一般不会关闭,服务器不会关闭,随时随地有新的任务)

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

方法名称说明
public static ExecutorService newCachedThreadPool()创建一个没有上限的线程池,最多21个亿的
public static ExecutorService newFixedThreadPool(int nThreads)创建有上限的线程池

package com.cg.a15threadpool1;

public class MyRunnable implements Runnable{

    @Override
    public void run() {

        System.out.println(Thread.currentThread().getName() + "---" );

    }
}
package com.cg.a15threadpool1;

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()             创建一个没有上限的线程池,最多21个亿的
            public static ExecutorService newFixedThreadPool(int nThreads)  创建有上限的线程池
        */

        //1、获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();

        //2、提交任务
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());


        //3、销毁线程池
        //pool1.shutdown();
    }
}
————————————————————————————————————————————————————————————————————————————————————————————
    pool-1-thread-6---
    pool-1-thread-4---
    pool-1-thread-5---
    pool-1-thread-1---
    pool-1-thread-3---
    pool-1-thread-2---

线程池的复用

package com.cg.a15threadpool1;

public class MyRunnable implements Runnable{

    @Override
    public void run() {
//        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" );
//        }
    }
}
package com.cg.a15threadpool1;
​
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()             创建一个没有上限的线程池,最多21个亿的
            public static ExecutorService newFixedThreadPool(int nThreads)  创建有上限的线程池
        */
​
        //1、获取线程池对象
        ExecutorService pool1 = Executors.newCachedThreadPool();
​
        //2、提交任务
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
​
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
​
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
​
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
​
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
​
        pool1.submit(new MyRunnable());
        Thread.sleep(1000);
​
        //3、销毁线程池
        //pool1.shutdown();
    }
}
——————————————————————————————————————————————————————————————————————————————————————————————————————————
    pool-1-thread-1---
    pool-1-thread-1---
    pool-1-thread-1---
    pool-1-thread-1---
    pool-1-thread-1---
    pool-1-thread-1---

有上限的线程池

package com.cg.a15threadpool1;
​
public class MyRunnable implements Runnable{
​
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
    }
}
package com.cg.a15threadpool1;
​
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()             创建一个没有上限的线程池,最多21个亿的
            public static ExecutorService newFixedThreadPool(int nThreads)  创建有上限的线程池
        */
​
        //1、获取线程池对象
        ExecutorService pool1 = Executors.newFixedThreadPool(3);
​
        //2、提交任务
        pool1.submit(new MyRunnable());
​
        pool1.submit(new MyRunnable());
​
        pool1.submit(new MyRunnable());
​
        pool1.submit(new MyRunnable());
​
        pool1.submit(new MyRunnable());
​
        pool1.submit(new MyRunnable());
​
        //3、销毁线程池
        //pool1.shutdown();
    }
}

可以debug看

自定义线程池

ThreadPoolExecutor

饭店的故事:

1、正式员工数量 ————核心线程数量(不能小于0)

2、餐厅最大员工数量 ————线程池中最大线程的数量(最大数量 >= 核心线程数量)

3、临时员工空闲多长时间被辞退(值) ————空闲时间(值)(不能小于0)

4、临时员工空闲多长时间被辞退(单位) ————空闲时间(单位)(用TimeUnit指定)

5、排队的客户 ————队列(不能为null)

6、从哪里招人 ————创建线程的方式(不能为null)

7、当排队人数过多,超出顾客请下次再来(拒绝服务) ————要执行的任务过多时的解决方案(不能为null)

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 创建一个新 ThreadPoolExecutor给定的初始参数。

三个临界点

核心线程:3

临时线程:3

队伍长度:3

1、当核心线程满时,再提交任务就会排队

2、当核心线程满,队伍满时,会创建临时线程

3、当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略

        当有10个任务:

        1,2,3被核心线程执行

        4,5,6进入等待队列

        7,8,9临时线程

        10就会触发拒绝策略

拒绝策略类

任务拒绝策略说明
static classThreadPoolExecutor.AbortPolicy被拒绝的任务的处理程序,抛出一个 RejectedExecutionException
static classThreadPoolExecutor.CallerRunsPolicy一个被拒绝的任务的处理程序,直接在 execute方法的调用线程中运行被拒绝的任务,除非执行程序已经被关闭,否则这个任务被丢弃
static classThreadPoolExecutor.DiscardOldestPolicy被拒绝的任务的处理程序,丢弃最旧的未处理请求,然后重试 execute ,除非执行程序关闭,在这种情况下,任务被丢弃
static classThreadPoolExecutor.DiscardPolicy被拒绝的任务的处理程序静默地丢弃被拒绝的任务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值