多线程基础知识

简单了解线程:

是指从软件或者硬件上实现多个线程并发执行的技术。
具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。

并发和并行:

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

进程和线程的:

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

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

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

Thread类

构造方法:
Thread()

      分配新的 Thread 对象。 

Thread(Runnable target)

      分配新的 Thread 对象。 

Thread(Runnable target, String name)

      分配新的 Thread 对象。 

Thread(String name)

      分配新的 Thread 对象。 

Thread(ThreadGroup group, Runnable target)

      分配新的 Thread 对象。 

Thread(ThreadGroup group, Runnable target, String name)

      分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,并作为 group 所引用的线程组的一员。 

Thread(ThreadGroup group, Runnable target, String name, long stackSize)

      分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,作为 group 所引用的线程组的一员,并具有指定的堆栈大小。 

Thread(ThreadGroup group, String name)
分配新的 Thread 对象。

常用方法:
static Thread currentThread()

      返回对当前正在执行的线程对象的引用。 

long getId()

      返回该线程的标识符。 

String getName()

      返回该线程的名称。 

int getPriority()

      返回线程的优先级 

void interrupt()

      中断线程。 不能滥用

static boolean interrupted()

      测试当前线程是否已经中断。 

boolean isAlive()

      测试线程是否处于活动状态。 

boolean isDaemon()

      测试该线程是否为守护线程。 

boolean isInterrupted()

      测试线程是否已经中断。 

void join()

      等待该线程终止。 当该线程结束才执行别的线程

void join(long millis)

      等待该线程终止的时间最长为 millis 毫秒。 

void join(long millis, int nanos)

      等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。 

setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
setName(String name)

      改变线程名称,使之与参数 name 相同。 

void setPriority(int newPriority)

      更改线程的优先级。 

static void sleep(long millis)

      在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 

static void sleep(long millis, int nanos)

      在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 

void start()

      使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 

static void yield()

      暂停当前正在执行的线程对象,并执行其他线程。 

实现多线程方式一:继承Thread类

实现步骤:

  •  定义一个类MyThread继承Thread类
    
  • 在MyThread类中重写run()方法
    
  •  创建MyThread类的对象
    
  • 启动线程
    

常用方法:
void run()

在线程开启后,此方法将被调用执行void start()使此线程开始执行,Java虚拟机会调用run方法()
//继承Thread类
   public class MyThread extends Thread {
    @Override
    //重写run方法,将线程要执行的语句写入run方法体里
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println(i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
    //创建两个线程my1跟my2
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

//        my1.run();
//        my2.run();

        //void start()调用Start方法是线程进入就绪状态, 导致此线程开始执行; Java虚拟机调用此线程的run方法
        my1.start();
        my2.start();
    }
}

两个小问题:
- 为什么要重写run()方法?

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

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

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

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

实现多线程方式二:实现Runnable接口

实现步骤:

  • 定义一个类MyRunnable实现Runnable接口
    
  • 在MyRunnable类中重写run()方法
    
  • 创建MyRunnable类的对象
    
  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
    
  • 启动线程
    
//创建一个类实现Runnable接口
public class MyRunnable implements Runnable {
//重写run方法,将线程要执行的代码写在方法体内
    @Override
    public void run() {
        for(int i=0; i<100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class MyRunnableDemo {
    public static void main(String[] args) {
        //创建MyRunnable类的对象
        MyRunnable my = new MyRunnable();

        //创建Thread类的对象,把MyRunnable对象作为构造方法的参数
        //Thread(Runnable target)
//        Thread t1 = new Thread(my);
//        Thread t2 = new Thread(my);
		//构造方法可以传Runnable对象,跟线程名
        //Thread(Runnable target, String name)
        Thread t1 = new Thread(my,"坦克");
        Thread t2 = new Thread(my,"飞机");

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

实现多线程方式三: 实现Callable接口

实现步骤:

  • 定义一个类MyCallable实现Callable接口
    
  • 在MyCallable类中重写call()方法
    
  • 创建MyCallable类的对象
    
  • 创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
    
  • 创建Thread类的对象,把FutureTask对象作为构造方法的参数
    
  • 启动线程
    
  • 再调用get方法,就可以获取线程结束之后的结果
    

常用方法:
FutureTask(Callable callable)

创建一个 FutureTask,一旦运行就执行给定的 Callable

V call()

计算结果,如果无法计算结果,则抛出一个异常

V get()

如有必要,等待计算完成,然后获取其结果
//创建一个对象,实现Callable接口,注意泛型是线程运行完毕后返回的结果的类型,
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("跟女孩表白" + i);
        }
        //返回值就表示线程运行完毕之后的结果
        return "答应";
    }
}
public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程开启之后需要执行里面的call方法
        MyCallable mc = new MyCallable();

        //Thread t1 = new Thread(mc);

        //可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
        FutureTask<String> ft = new FutureTask<>(mc);
      
     
        //创建线程对象,将futureTask对象作为参数传入
        Thread t1 = new Thread(ft);
        //获取线程结束后的结果
        String s = ft.get();
        //开启线程                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
        t1.start();

        //String s = ft.get();
        System.out.println(s);
    }
}

三种实现方式的对比

  • 实现Runnable、Callable接口
    • 	 好处: 扩展性强,实现该接口的同时还可以继承其他的类
      
    •  缺点: 编程相对复杂,不能直接使用Thread类中的方法
      
  • 继承Thread类
    • 好处: 编程比较简单,可以直接使用Thread类中的方法
      
    • 缺点: 可以扩展性较差,不能再继承其他的类
      

同步代码块解决数据安全问题

  • 安全问题出现的条件

    • 是多线程环境

    • 有共享数据

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

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

    • 基本思想:让程序没有安全问题的环境
  • 怎么实现呢?

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

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

  • 同步代码块格式:

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

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

  • 同步的好处和弊端

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

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

同步方法解决数据安全问题

  • 同步方法的格式

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

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

    同步方法的锁对象: this

  • 静态同步方法

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

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

    同步静态方法的锁对象: 类名.class

​ Lock锁

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

  • Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
    ReentrantLock构造方法
    ReentrantLock()

    创建一个ReentrantLock的实例 
    

常用方法:
void lock()

获得锁  

void unlock()

释放锁  

直接在要操作共享资源的前面使用lock()方法,在操作共享资源后面调用unlock方法。

死锁

  • 概述

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

  • 什么情况下会产生死锁

    1. 资源有限
    2. 同步嵌套
      就是当同步方法嵌套时产生的一种情况,比如所当一个线程与到一个同步方法时,方法里没有线程,默认是开着的,这时线程拿到锁对象,进入方法体,这时方法体里也调用一个同步方法,但是被另一个线程拿到了对象锁,而且也进入了方法体,导致这个同步方法锁上了,线程过不去只能等待,二另一个线程也是这种情况,所以导致死锁。
      生产者和消费者模式概述

生产者和消费者模式概述

  • 概述

    生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。

    所谓生产者消费者问题,实际上主要是包含了两类线程:

    ​ 一类是生产者线程用于生产数据

    ​ 一类是消费者线程用于消费数据

    为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

    生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

    消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

-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方法中的代码步骤如下

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

    分别开启两个线程
    代码实现:

public class Desk {

    //定义一个标记
    //true 就表示桌子上有汉堡包的,此时允许吃货执行
    //false 就表示桌子上没有汉堡包的,此时允许厨师执行
    //public static boolean flag = false;
    private boolean flag;

    //汉堡包的总数量
    //public static int count = 10;
    //以后我们在使用这种必须有默认值的变量
   // private int count = 10;
    private int count;

    //锁对象
    //public static final Object lock = new Object();
    private final Object lock = new Object();

    public Desk() {
        this(false,10); // 在空参内部调用带参,对成员变量进行赋值,之后就可以直接使用成员变量了
    }

    public Desk(boolean flag, int count) {
        this.flag = flag;
        this.count = count;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public Object getLock() {
        return lock;
    }

    @Override
    public String toString() {
        return "Desk{" +
                "flag=" + flag +
                ", count=" + count +
                ", lock=" + lock +
                '}';
    }
}

public class Cooker extends Thread {

    private Desk desk;

    public Cooker(Desk desk) {
        this.desk = desk;
    }
//    生产者步骤:
//            1,判断桌子上是否有汉堡包
//    如果有就等待,如果没有才生产。
//            2,把汉堡包放在桌子上。
//            3,叫醒等待的消费者开吃。

    @Override
    public void run() {
        while(true){
            synchronized (desk.getLock()){
                if(desk.getCount() == 0){
                    break;
                }else{
                    //System.out.println("验证一下是否执行了");
                    if(!desk.isFlag()){
                        //生产
                        System.out.println("厨师正在生产汉堡包");
                        desk.setFlag(true);
                        desk.getLock().notifyAll();
                    }else{
                        try {
                            desk.getLock().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

public class Foodie extends Thread {
    private Desk desk;

    public Foodie(Desk desk) {
        this.desk = desk;
    }

    @Override
    public void run() {
//        1,判断桌子上是否有汉堡包。
//        2,如果没有就等待。
//        3,如果有就开吃
//        4,吃完之后,桌子上的汉堡包就没有了
//                叫醒等待的生产者继续生产
//        汉堡包的总数量减一

        //套路:
            //1. while(true)死循环
            //2. synchronized 锁,锁对象要唯一
            //3. 判断,共享数据是否结束. 结束
            //4. 判断,共享数据是否结束. 没有结束
        while(true){
            synchronized (desk.getLock()){
                if(desk.getCount() == 0){
                    break;
                }else{
                    //System.out.println("验证一下是否执行了");
                    if(desk.isFlag()){
                        //有
                        System.out.println("吃货在吃汉堡包");
                        desk.setFlag(false);
                        desk.getLock().notifyAll();
                        desk.setCount(desk.getCount() - 1);
                    }else{
                        //没有就等待
                        //使用什么对象当做锁,那么就必须用这个对象去调用等待和唤醒的方法.
                        try {
                            desk.getLock().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

    }
}

public class Demo {
    public static void main(String[] args) {
        /*消费者步骤:
        1,判断桌子上是否有汉堡包。
        2,如果没有就等待。
        3,如果有就开吃
        4,吃完之后,桌子上的汉堡包就没有了
                叫醒等待的生产者继续生产
        汉堡包的总数量减一*/

        /*生产者步骤:
        1,判断桌子上是否有汉堡包
        如果有就等待,如果没有才生产。
        2,把汉堡包放在桌子上。
        3,叫醒等待的消费者开吃。*/

        Desk desk = new Desk();

        Foodie f = new Foodie(desk);
        Cooker c = new Cooker(desk);

        f.start();
        c.start();

    }
} 

阻塞队列基本使用

阻塞队列继承结构
在这里插入图片描述

  • 常见BlockingQueue:

    ArrayBlockingQueue:

       底层是数组,有界
    

    LinkedBlockingQueue:

      底层是链表,无界.但不是真正的无界,最大为int的最大值
    
  • BlockingQueue的核心方法:

    put(anObject):

      将参数放入队列,如果放不进去会阻塞
    

    take():

      取出第一个数据,取不到会阻塞
    
public class Demo02 {
    public static void main(String[] args) throws Exception {
        // 创建阻塞队列的对象,容量为 1
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);

        // 存储元素
        arrayBlockingQueue.put("汉堡包");

        // 取元素
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take()); // 取不到会阻塞

        System.out.println("程序结束了");
    }

阻塞队列实现等待唤醒机制

案例需求

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

    1.构造方法中接收一个阻塞队列对象

    2.在run方法中循环向阻塞队列中添加包子

    3.打印添加结果

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

    1.构造方法中接收一个阻塞队列对象

    2.在run方法中循环获取阻塞队列中的包子

    3.打印获取结果

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

    创建阻塞队列对象

    创建生产者线程和消费者线程对象,构造方法中传入阻塞队列对象

    分别开启两个线程

  public class Cooker extends Thread {

    private ArrayBlockingQueue<String> bd;

    public Cooker(ArrayBlockingQueue<String> bd) {
        this.bd = bd;
    }
//    生产者步骤:
//            1,判断桌子上是否有汉堡包
//    如果有就等待,如果没有才生产。
//            2,把汉堡包放在桌子上。
//            3,叫醒等待的消费者开吃。

    @Override
    public void run() {
        while (true) {
            try {
                bd.put("汉堡包");
                System.out.println("厨师放入一个汉堡包");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Foodie extends Thread {
    private ArrayBlockingQueue<String> bd;

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

    @Override
    public void run() {
//        1,判断桌子上是否有汉堡包。
//        2,如果没有就等待。
//        3,如果有就开吃
//        4,吃完之后,桌子上的汉堡包就没有了
//                叫醒等待的生产者继续生产
//        汉堡包的总数量减一

        //套路:
        //1. while(true)死循环
        //2. synchronized 锁,锁对象要唯一
        //3. 判断,共享数据是否结束. 结束
        //4. 判断,共享数据是否结束. 没有结束
        while (true) {
            try {
                String take = bd.take();
                System.out.println("吃货将" + take + "拿出来吃了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

public class Demo {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> bd = new ArrayBlockingQueue<>(1);

        Foodie f = new Foodie(bd);
        Cooker c = new Cooker(bd);

        f.start();
        c.start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值