自学内容网 自学内容网

初识多线程

多线程

什么是多线程

线程

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

简单理解:应用软件中互相独立。可以同时运行的功能。

进程

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

多线程的应用场景

软件中的耗时操作,拷贝、迁移大文件,加载大量的资源文件,所有的聊天软件,所有的后台服务器等。

1721806319917

多线程的两个概念

并发

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

并行

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

1721806508030

多线程的实现方式

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

package com.example.threadcase01;

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

package com.example.threadcase01;

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

②实现Runnable接口的方式进行实现

package com.example.theadcase02;

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);
        Thread t2 = new Thread(mr);

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

        //开启线程
        t1.start();
        t2.start();
    }
}
package com.example.theadcase02;

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");
        }
    }
}

③利用Callable接口和Future接口的方式实现

package com.example.threadcase03;

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 = 0; i < sum; i++) {
            sum += i;
        }
        return sum;
    }
}
package com.example.threadcase03;

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对象,并启动
        Thread t1 = new Thread(ft);

        t1.setName("线程1");

        t1.start();
        
        //获取线程的结果
        Integer result = ft.get();
        System.out.println(result);


    }
}

多线程三种实现方式对比

1721808677002

常见的成员方法

1721808764520

package com.example.threadcase04;

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方法,并执行里面所有的代码
        *       在以前,我们写的所有代码,其实都是运行在mian线程当中
        * 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);

        System.out.println("11111");
        Thread.sleep(5000);
        System.out.println("2222");
    }
}
package com.example.threadcase04;

public class MyThread extends Thread{
    public MyThread() {
    }

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

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

线程的调度方式

抢占式调度

多个线程抢占CPU的执行权,体现随机性。

非抢占式调度

所有线程轮流执行

package com.example.Threadmethod3;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
        * final void setDeamon(boolean on)  设置为守护线程
        * 细节:
        *   当其他的非守护线程执行完毕之后,守护线程也会陆续结束
        * 通俗易懂:
        *   结合以下代码解释:当线程一(唔西迪西)结束了,那么玛卡巴卡线程也没有存在的必要了
        * */

        //1.创建线程对象
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        //2.给线程设置名字
        t1.setName("唔西迪西");
        t2.setName("玛卡巴卡");

        //3.将线程2设置为守护线程(备胎线程)
        t2.setDaemon(true);

        //4.开启线程
        t1.start();
        t2.start();
    }
}
package com.example.Threadmethod3;

public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(getName()+"@"+i);
        }
    }
}
package com.example.Threadmethod3;

public class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <=  100; i++) {
            System.out.println(getName()+"@"+i);
        }
    }
}
package com.example.threadmethod4;

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

        //1.创建两个线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        //2.设置线程名称
        t1.setName("唔西迪西");
        t2.setName("玛卡巴卡");

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

    }
}
package com.example.threadmethod4;

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

            //出让当前CPU的执行权
            Thread.yield();
        }
    }
}
package com.example.threadmethod5;

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 = 1; i <= 10; i++) {
            System.out.println("main线程"+i);
        }
    }
}
package com.example.threadmethod5;

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


        }
    }
}

线程的生命周期

1721871939231

线程安全的问题

案例导入

/** 需求:
*某电影院目前正在上映某国产大片,共有100张电影票,目前有3个窗口售卖,请你设计一个程序模拟该影院卖票* */
package com.example.threadsafe1;

public class MyThread extends Thread{
    static  int ticket = 0;
    @Override
    public void run() {
        while (true){
            if(ticket < 100){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                ticket++;
                System.out.println(getName()+"正在卖第"+ticket + "张票");

            }else {
                break;
            }
        }
    }
}
package com.example.threadsafe1;

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();
    }
}
卖票引发的安全问题

①相同的票出现了多次。

②出现了超出范围的票。

原因

线程执行时,有随机性。

解决方案:
同步代码块

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

1721873952972

package com.example.threadsafe1;

public class MyThread extends Thread{
    //表示这个类的所有对象,都可以共享ticked的数据
    static  int ticket = 0;

    //锁对象,一定是唯一的
    static Object obj = new Object();
    @Override
    public void run() {
        while (true){
            //同步代码块
            synchronized (obj){
                if(ticket < 100){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                    ticket++;
                    System.out.println(getName()+"正在卖第"+ticket);

                }else {
                    break;
                }
            }
        }
    }
}
package com.example.threadsafe1;

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();
    }
}
同步方法

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

1721874419773

package com.example.threadsafe2;

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

    }
}
package com.example.threadsafe2;

public class MyRunnable implements Runnable {
    int ticked = 0;

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

        }
    }

    private synchronized boolean method() {
        //3.判断共享数据是否到了末尾,如果到了末尾
        if (ticked == 100) {
            return true;
        } else {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //4.判断共享数据是否到了末尾,如果没到末尾
            ticked++;
            System.out.println(Thread.currentThread().getName() + "在卖第" + ticked + "张票");
        }
        return false;
    }
}
Lock锁

1721875434594

package com.example.threadsafe3;

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

public class MyThread extends Thread {
    //表示这个类的所有对象,都可以共享ticked的数据
    static int ticket = 0;
    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            //同步代码块
//            synchronized (MyThread.class){
            lock.lock();
            try {
                if (ticket < 100) {
                    Thread.sleep(100);

                    ticket++;
                    System.out.println(getName() + "正在卖第" + ticket);

                } else {
                    break;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
//            }
        }
    }
}
package com.example.threadsafe3;

import com.example.threadsafe1.MyThread;

public class ThreadDemo {
    public static void main(String[] args) {
        /*
        * 需求:
        *某电影院目前正在上映某国产大片,共有100张电影票,目前有3个窗口售卖,请你设计一个程序模拟该影院卖票
        *用JDK5Lock的方式实现
        **/

        com.example.threadsafe1.MyThread t1 = new com.example.threadsafe1.MyThread();
        com.example.threadsafe1.MyThread t2 = new com.example.threadsafe1.MyThread();
        com.example.threadsafe1.MyThread t3 = new MyThread();

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

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

死锁

死锁是一个错误,是由于锁的循环嵌套导致的。例如:有锁1、2,有线程A、B。线程A拿到锁1,线程B拿到锁2。线程A、B都在等对方释放锁,所以此时程序就会卡死,运行不下去。

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

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

案例导入

1721876674278

假设桌子上有面条就是吃货执行,他负责吃,如果没有面条就是厨师执行。厨师负责做面条。

生产者和消费者的理想情况

厨师先抢到CPU的执行权,此时桌子上没有面条。所以厨师在一开始就要做一碗面条放到桌子上让吃货线程来吃。相当于厨师做一个,吃货吃一个。厨师再做一个吃货,再吃一个。

消费者等待

1721876982376

1721877002656

生产者等待

1721877132154

常见方法

1721877187451

代码实现

基本写法
package com.example.waitandnotify;

public class Cook extends Thread{
    /*
     * 1.循环
     * 2.同步代码块(同步方法)
     * 3.判断共享数据是否到了末尾(到了)
     * 4.判断共享数据是否到了末尾(没到,执行核心逻辑)
     *
     * */
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else {
                    //判断桌子上是否有食物
                    if(Desk.foodFlag == 1){
                        //如果有,等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        //如果没有,就制作食物
                        System.out.println("厨师做了一碗面条");
                        //修改桌子上的食物状态
                        Desk.foodFlag = 1;
                        //叫醒等待的消费者开吃
                        Desk.lock.notifyAll();
                    }


                }
            }
        }

    }
}
package com.example.waitandnotify;

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) {
                            throw new RuntimeException(e);
                        }

                    }else {
//                        把吃完的总数-1
                        Desk.count--;
//                        如果有,就开吃
                        System.out.println("吃货在吃面条,还能再吃"+Desk.count+"碗面条!!");
//                        吃完之后唤醒厨师继续做
                        Desk.lock.notifyAll();//唤醒这把锁绑定的所有线程
//                        修改桌子的状态
                        Desk.foodFlag = 0;
                    }
                }
            }
        }

    }
}
package com.example.waitandnotify;

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

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

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

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

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();
    }
}
阻塞队列方式实现
什么是阻塞队列

连接生产者和消费者之间的管道(阻塞队列)。厨师做好面条之后就可以把面条放进管道。左边的消费者就可以从管道获取面条去吃。我们可以规定管道当中最多可以放多少碗面条。如果最多可放1碗,那么运行结果同上。

1721878854145

阻塞队列的继承结构

1721879210032

代码实现
package com.example.waitandnotify1;

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


    }
}
package com.example.waitandnotify1;

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) {
                throw new RuntimeException(e);
            }
        }

    }
}
package com.example.waitandnotify1;

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) {
                throw new RuntimeException(e);
            }
        }

    }
}

线程的状态

Java虚拟机没有定义运行状态。因为当线程抢占到CPU执行权的时候,它会把当前线程交给操作系统去管理。

1721886003992

1721886194168

线程池

1721886485198

1721886534026

1721886548778

package com.example.mythreadpool.threadpool1;

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.example.mythreadpool.threadpool1;

import java.util.concurrent.*;

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        /*
        *public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
        *public static ExecutorService newFixedThreadPool()  创建一个有上限的线程池
        *
        * */

        //1.创建线程池对象
//        ExecutorService pool1 = Executors.newCachedThreadPool();
        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());

        //3.销毁线程池
//        pool1.shutdown();
    }
}

自定义线程池的参数

1721887980496

拒绝策略

1721887911712

1721888215959

线程池多大合适

CPU密集型运算(计算比较多,读取文件比较少)

最大并行数+1

I/O密集型运算(读取本地文件或者数据库比较多)

最大并行数 ∗ 期望 C P U 利用率 ∗ [ 总时间( C P U 计算时间 + 等待时间) / C P U 计算时间 ] 最大并行数*期望CPU利用率*[总时间(CPU计算时间+等待时间)/CPU计算时间] 最大并行数期望CPU利用率[总时间(CPU计算时间+等待时间)/CPU计算时间]

总时间可以通过Thread dump进行测试。得到结果带入公式进行计算。

1721889176789

什么是最大并行数

4核8线程:好比CPU有四个大脑能同时的并行的去做8件事情。因特尔发明了超线程技术。它可以把原本的四个大脑虚拟成8个(8线程)。相当于最大并行数为8.

package com.example.mythreadpool.threadmethod2;

public class MyThreadPool {
    public static void main(String[] args) {
        //向Java虚拟机返回可用的处理器数量
        int count = Runtime.getRuntime().availableProcessors();
        System.out.println(count);
    }
}

原文地址:https://blog.csdn.net/m0_71513446/article/details/140690094

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!