多 线 程

1.什么是多线程?
有了多线程,我们就可以让程序同时做多件事情
2.多线程的作用?
提高效率
3.多线程的应用场景?
只要你想让多个事情同时运行就需要用到多线程
比如:软件中的耗时操作、所有的聊天软件、所有的服务器

1.进程和线程【理解】

  • 进程:是正在运行的程序

    独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的 并发性:任何进程都可以同其他进程一起并发执行

  • 线程:是进程中的单个顺序控制流,是一条执行路径

    单线程:一个进程如果只有一条执行路径,则称为单线程程序

    多线程:一个进程如果有多条执行路径,则称为多线程程序

2.并发和并行【理解】

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

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

3.多线程的实现方式

  1. 继承Thread类的方式进行实现
  2. 实现Runnable接口的方式进行实现
  3. 利用Callable接口和Future接口方式实现

4.实现多线程方式一:继承Thread类【应用】

  1. 方法介绍

    方法名说明
    void run()在线程开启后,此方法将被调用执行
    void start()使此线程开始执行,Java虚拟机会调用run方法()
  2. 实现步骤

    1. 定义一个类MyThread继承Thread类

    2. 在MyThread类中重写run()方法

    3. 创建MyThread类的对象

    4. 启动线程

  3. 代码演示
public class MyThread extends Thread{

    @Override
    public void run() {
        //书写线程要执行代码
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "HelloWorld");
        }
    }
}
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();

    }
}

两个小问题

  • 为什么要重写run()方法?

    因为run()是用来封装被线程执行的代码

  • run()方法和start()方法的区别?

    run():封装线程执行的代码,直接调用,相当于普通方法的调用

    start():启动线程;然后由JVM调用此线程的run()方法

5实现多线程方式二:实现Runnable接口【应用】

  • Thread构造方法

    方法名说明
    Thread(Runnable target)分配一个新的Thread对象
    Thread(Runnable target, String name)分配一个新的Thread对象
  • 实现步骤

    • 定义一个类MyRunnable实现Runnable接口

    • 在MyRunnable类中重写run()方法

    • 创建MyRunnable类的对象

    • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数

    • 启动线程

代码演示

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





    }
}

6.实现多线程方式三: 实现Callable接口【应用】

  • 方法介绍

    方法名说明
    V call()计算结果,如果无法计算结果,则抛出一个异常
    FutureTask(Callable<V> callable)创建一个 FutureTask,一旦运行就执行给定的 Callable
    V get()如有必要,等待计算完成,然后获取其结果
  • 实现步骤

    1. 定义一个类MyCallable实现Callable接口

    2. 在MyCallable类中重写call()方法

    3. 创建MyCallable类的对象

    4. 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数

    5. 创建Thread类的对象,把FutureTask对象作为构造方法的参数

    6. 启动线程

    7. 再调用get方法,就可以获取线程结束之后的结果。

  • 代码演示

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


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


    }
}

三种实现方式对比:

优点缺点
继承Thread类

编程比较简单,可以直接使用

Thread类中的方法

可以扩展性较差,

不能再继承其他的类

实现Runnable接口扩展性强,实现该接口的同时还可以继承其他的类编程相对复杂,不能直接使用Thread类中的方法
实现callable接口

7.设置和获取线程名称线程休眠【应用】

  • 方法介绍

    方法名说明
    void setName(String name)将此线程的名称更改为等于参数name
    String getName()返回此线程的名称
    Thread currentThread()返回对当前正在执行的线程对象的引用
    static void sleep(long millis)使当前正在执行的线程停留(暂停执行)指定的毫秒数

  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、当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
      

package com.itheima.a04threadmethod1;

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.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");*/


    }
}

8线程优先级【应用】

  • 线程调度

    • 两种调度方式

      • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

      • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

    • Java使用的是抢占式调度模型

    • 随机性

      假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

优先级相关方法

方法名说明
final int getPriority()返回此线程的优先级
final void setPriority(int newPriority)更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
public class MyRunnable implements Runnable{

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



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



    }
}

9.守护线程【应用】

当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.

  • 相关方法

    方法名说明
    void setDaemon(boolean on)将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
public class MyThread1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class MyThread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "---" + i);
        }
    }
}
public class Demo {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");

        //把第二个线程设置为守护线程
        //当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
        t2.setDaemon(true);

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

10.礼让线程

     相关方法

方法名说明
public static void yield()出让线程/礼让线程
public class MyThread extends Thread{

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

            System.out.println(getName() + "@" + i);
            //表示出让当前CPU的执行权
            Thread.yield();
        }
    }
}
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();


    }
}

11.插入线程

相关方法

方法名说明
public final void join()

 插入线程/插队线程

public class MyThread extends Thread{

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

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


    }
}

12.线程的生命周期

13.线程的安全问题

  • 安全问题出现的条件

    • 是多线程环境

    • 有共享数据

    • 有多条语句操作共享数据

  • 如何解决多线程安全问题呢?

    • 基本思想:让程序没有安全问题的环境

怎么实现呢?

  • 把多条语句操作共享数据的代码给起来,让任意时刻只能有一个线程执行即可

  • Java提供了同步代码块的方式来解决

同步代码块格式:

synchronized(任意对象) { 
                    多条语句操作共享数据的代码 
}

synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

特点1:锁默认打卉,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开

细节:

1.锁必须放在循环里面

2.锁必须唯一,一般都写成当前类的字节码文件 如:synchronized (MyThread.class) {}

同步的好处和弊端

  • 好处:解决了多线程的数据安全问题

  • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

14.同步方法解决数据安全问题【应用】

  • 同步方法的格式

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

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

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

  • 特点2.锁对象不能自己指定

    同步方法的锁对象是什么呢?

    非静态:this

  • 静态同步方法

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

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

    同步静态方法的锁对象是什么呢?

    类名.class

15Lock锁【应用】

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

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

  • ReentrantLock构造方法

    方法名说明
    ReentrantLock()创建一个ReentrantLock的实例
  • 加锁解锁方法

    方法名说明
    void lock()获得锁
    void unlock()释放锁
package com.itheima.a11threadsafe3;

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

public class MyThread extends Thread{

    static int ticket = 0;

    static Lock lock = new ReentrantLock();

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

16死锁【理解】

  • 概述

    线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

  • 什么情况下会产生死锁

    1. 资源有限

    2. 同步嵌套

17生产者和消费者案例【应用】

Object类的等待和唤醒方法

方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程
  • 案例需求

    • 桌子类(Desk):定义表示包子数量的变量,定义锁对象变量,定义标记桌子上有无包子的变量

    • 生产者类(Cooker):实现Runnable接口,重写run()方法,设置线程任务

      1.判断是否有包子,决定当前线程是否执行

      2.如果有包子,就进入等待状态,如果没有包子,继续执行,生产包子

      3.生产包子之后,更新桌子上包子状态,唤醒消费者消费包子

    • 消费者类(Foodie):实现Runnable接口,重写run()方法,设置线程任务

      1.判断是否有包子,决定当前线程是否执行

      2.如果没有包子,就进入等待状态,如果有包子,就消费包子

      3.消费包子后,更新桌子上包子状态,唤醒生产者生产包子

    • 测试类(Demo):里面有main方法,main方法中的代码步骤如下

      创建生产者线程和消费者线程对象

      分别开启两个线程

18线程的六种状态

19线程池

线程池的设计思路 :

  1. 准备一个任务容器

  2. 一次性启动多个(2个)消费者线程

  3. 刚开始任务容器是空的,所以线程都在wait

  4. 直到一个外部线程向这个任务容器中扔了一个"任务",就会有一个消费者线程被唤醒

  5. 这个消费者线程取出"任务",并且执行这个任务,执行完毕后,继续等待下一次任务的到来

核心原理:

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

线程池代码实现
1,创建线程池
2,提交任务
3,所有的任务全部执行完毕,关闭线程池


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

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

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

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

自定义线程池

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
        (核心线程数量,最大线程数量,空闲线程最大存活时间,任务队列,创建线程工厂,任务的拒绝策略);

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

任务拒绝策略

任务拒绝策略说明
ThreadPoolExecutor. AbortPolicy默认策略:丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常这是不推荐的做法
ThreadPoolExecutor.Discardoldestpolicy抛弃队列中等待最久的任务然后把当前任务加入队列中
ThreadPoolExecutor.callerRunsPolicy调用任务的run()方法绕过线程池直接执行
 ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,  //核心线程数量,能小于0
                6,  //最大线程数,不能小于0,最大数量 >= 核心线程数量
                60,//空闲线程最大存活时间
                TimeUnit.SECONDS,//时间单位
                new ArrayBlockingQueue<>(3),//任务队列
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略

最大并行数:自己电脑的处理器个数

线程池多大合适?

  • 20
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值