Java多线程

一、多线程介绍

1.1并发与并行

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、创建线程三种方法

  1. 继承Thread类:创建线程的最直接方法是继承java.lang.Thread类,并重写run()方法,将线程要执行的任务写入其中。创建一个类的实例并调用其start()方法即可启动线程。这种方法简单直观,适合简单的线程任务。但这种方法有一个缺点,由于Java不支持多继承,如果该类已经继承了其他类,则无法通过继承Thread类来创建线程。
  2. 实现Runnable接口:另一种创建线程的方法是实现java.lang.Runnable接口。创建一个实现Runnable接口的类,并实现其run()方法,将线程要执行的任务写入其中。然后,创建一个Thread对象,将Runnable实现类的实例作为参数传递给Thread构造函数,最后调用Thread实例的start()方法启动线程。这种方法的优点是可以继承其他类,同时避免了单继承的限制。而且,多个线程可以共享同一个Runnable实例,从而实现资源共享。
  3. 使用Callable和Future:如果需要在线程执行完毕后获取结果,可以使用java.util.concurrent.Callable接口和java.util.concurrent.Future接口。创建一个实现Callable接口的类,并在call()方法中定义线程任务及返回值。然后,创建一个FutureTask对象,并将Callable实现类的实例传递给它。再将这个FutureTask对象传递给Thread对象并启动线程。最后,通过FutureTask对象的get()方法获取线程执行的结果。这种方法适用于需要获取线程执行结果的场景,且call()方法可以抛出异常,使得错误处理更为方便。

2.1继承Thread类

  1. 继承Threadk
  2. 重写run方法
  3. 创建类对象,调用start方法开启线程
class MyThread extends Thread {
    @Override
    public void run() {
        // 线程要执行的任务
        System.out.println("MyThread is running");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start(); // 启动线程
    }
}

2.2实现Runnable接口

class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程要执行的任务
        System.out.println("MyRunnable is running");
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start(); // 启动线程
    }
}

2.3实现Callable接口

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        // 线程要执行的任务,并返回结果
        return "MyCallable result";
    }
}

public class Main {
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<String> future = executorService.submit(myCallable);
        executorService.shutdown(); // 关闭线程池
        try {
            String result = future.get(); // 获取线程执行的结果
            System.out.println("Result: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

2.4线程中常见的成员方法

在这里插入图片描述

1、getname/setname/currentThread()方法

public class MyThread01 extends Thread{
    //01、getname/setname  获取/设置线程名称

    @Override
    public void run() {
        Thread.currentThread().setName("我的线程1");
        System.out.println("输出线程的名称:"+Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        MyThread01 myThread01 = new MyThread01();
        myThread01.start();
    }
}
  1. sleep()方法
package JavaSE多线程_并发编程.创建线程01.线程常用方法;

import java.time.LocalDateTime;
import java.util.Date;

public class MyThread01 extends Thread{
    //01、getname/setname  获取/设置线程名称

    @Override
    public void run() {
        Thread.currentThread().setName("我的线程1");//setName()
        System.out.println("调用sleep,让线程休眠3秒"+ LocalDateTime.now());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("线程休眠3秒后"+LocalDateTime.now());

        System.out.println("输出线程的名称:"+Thread.currentThread().getName());//currentThread().getName()
    }

    public static void main(String[] args) {
        MyThread01 myThread01 = new MyThread01();
        myThread01.start();
    }
}

2、设置优先级setpriority()/获取优先级getpriority()

1、正常运行两个线程各100次查看结果状态

(1)线程实现类:

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //输出当前线程的名字一百次
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

(2)测试类

public class Main {
    public static void main(String[] args) {
        //创建两个线程,并设置线程名称分别是飞机和坦克
        MyRunnable myRunnable = new MyRunnable();

        Thread thread = new Thread(myRunnable,"飞机");
        Thread thread2 = new Thread(myRunnable,"坦克");

        thread.start();
        thread2.start();
    }
}

在这里插入图片描述

在这里插入图片描述

2、默认情况下的两个线程优先级查看

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
优先级变量,最小为1,最大为10,默认是5
在这里插入图片描述
在这里插入图片描述

3、设置线程优先级后运行

设置优先级,优先级越高抢到CPU的概率越大,并不代表一定比优先级低的先执行

package JavaSE多线程_并发编程.创建线程01.线程常用方法.线程优先级;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //输出当前线程的名字一百次
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

package JavaSE多线程_并发编程.创建线程01.线程常用方法.线程优先级;

public class Main {
    public static void main(String[] args) {
        //创建两个线程,并设置线程名称分别是飞机和坦克
        MyRunnable myRunnable = new MyRunnable();

        Thread thread = new Thread(myRunnable,"飞机");
        Thread thread2 = new Thread(myRunnable,"坦克");

        //查看默认情况下两个线程的优先级
        System.out.println(thread.getPriority());
        System.out.println(thread2.getPriority());
        //获取main线程的优先级
        System.out.println(Thread.currentThread().getPriority());


        //设置优先级,优先级越高抢到CPU的概率越大,并不代表一定比优先级低的先执行
        thread.setPriority(1);
        thread2.setPriority(10);


        thread.start();
        thread2.start();
    }
}

在这里插入图片描述

3、守护线程setDeamon()

public class Mythread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}
package JavaSE多线程_并发编程.创建线程01.线程常用方法.守护线程;

public class Mythread2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

package JavaSE多线程_并发编程.创建线程01.线程常用方法.守护线程;

public class Main {
    public static void main(String[] args) {
        //01、创建两个线程
        Mythread1 mythread1 = new Mythread1();
        mythread1.setName("女神");
        Mythread2 mythread2 = new Mythread2();
        mythread2.setName("备胎");

        //02、将备胎线程设置为守护线程
        mythread2.setDaemon(true);

        //03、开启线程
        mythread1.start();
        mythread2.start();
    }
}

在这里插入图片描述
在这里插入图片描述

1、应用场景

在这里插入图片描述

4、礼让线程yield(),只是尽可能礼让

  1. 让运行的结果尽可能地均匀一点,不会某一个线程抢占CPU之后执行很多次,执行完后释放CPU,重新抢夺。
  2. public static void yield()静态方法,直接用类名Thread调用,此方法直接写在run()方法中
package JavaSE多线程_并发编程.创建线程01.线程常用方法.礼让线程;

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <50 ; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

package JavaSE多线程_并发编程.创建线程01.线程常用方法.礼让线程;

public class Main {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("飞机");
        MyThread myThread2 = new MyThread();
        myThread2.setName("坦克");

        myThread.start();
        myThread2.start();

    }
}

在这里插入图片描述
引入礼让线程方法:
在这里插入图片描述

5、插队线程join()

在这里插入图片描述

在这里插入图片描述
d0ab5f95096417fb010e87b221ae2b3.png)

如果想将土豆线程插入到main线程之前,等土豆线程执行完了,在执行main线程
在这里插入图片描述

2.5线程的生命周期

在这里插入图片描述

2.6线程安全问题

需求:电影院有100张票,三个窗口进行售卖

1、正常逻辑代码出现的问题

在这里插入图片描述

在这里插入图片描述
改进1:共享数据
在这里插入图片描述

2、问题:仍然又重复卖票的问题和超出范围的问题

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3、原因分析

线程执行时具有随机性,都有可能在其他线程占用CPU的时候对CPU进行抢夺,其他线程操作的数据也就会被抢夺CPU的线程拿来继续使用,而抢夺CPU的线程以为拿到的还是原始数据,属于脏数据。
在这里插入图片描述
线程1抢到CPU执行权后执行run(),睡眠10毫秒,其他线程会趁机抢夺CPU
在这里插入图片描述
同理2,3线程都会执行到睡眠状态在这里插入图片描述
线程1醒来后继续执行,使得tickcet++,ticket=1
在这里插入图片描述

此时Ticket已经是1,线程1还没来得及执行下面的打印票量=1,线程2就将CPU执行权夺走,并对ticket进行了+1,此时ticket=2,同理线程3也进行了抢夺进行了+1,此时的Ticket已经是3,接下来,不管谁先执行下面的代码,输出的都是3。这是重复票的由来,三个窗口卖的都是3号票。
在这里插入图片描述
当执行到第99张票的时候,又重复以上情况,if中判断的的确是ticket=99满足小于100条件,但在执行ticket++的时候,由于线程运行的随机性,三个线程的抢夺执行权,使得在最终输出ticket之前都进行了+1操作,就会出现总票数可能是101或者102
在这里插入图片描述
在这里插入图片描述

4、解决问题-线程加锁-Synchronized

线程1执行共享数据这段代码的时候,其他线程进不来
在这里插入图片描述
在这里插入图片描述
确保锁对象的唯一性:是为了让抢夺CPU资源的线程针对同一把锁,如果该锁被使用,其他线程无法抢占资源
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

5、synchronized锁注意事项

  1. 要保证锁的唯一性。
  2. 锁要加在循环里面,否则否一个进程一旦抢到锁会将所有票卖完。
  3. 当多线程是通过继承Thread类实现的时候,公共变量需要定义为static类型,因为继承Thread类实现的多线程,在创建的对象的时候会new出多个对象,导致公共变量也不唯一,必须使得多个线程在执行同一个公共变量才行。这样可以防止多个线程同时修改它,从而避免了数据不一致的问题。
    在这里插入图片描述在这里插入图片描述

6、同步方法

在这里插入图片描述
可以先写成同步代码块的形式,然后选中同步代码块代码->Ctrl+alt+M抽取方法->方法前面加Synchronized修饰

public class MySynchnized extends Thread{
    static int tickets=0;
    @Override
    public void run() {
        while (true){
                if (extracted()) break;
        }
    }
//同步方法
    private synchronized static boolean extracted() {
        if (tickets==100){
            return true;
        }else {
            tickets++;
            System.out.println("线程"+Thread.currentThread().getName()+"卖第"+tickets+"张票");
        }
        return false;
    }
}

在这里插入图片描述

7、Lock锁

在这里插入图片描述

public class MyLock extends Thread{
    static int tickets=0;
   Lock lock=new ReentrantLock();//需要像共享数据一样定义为static类型
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                if (tickets==100){
                    break;
                }else {
                    tickets++;
                    System.out.println("线程"+Thread.currentThread().getName()+"卖第"+tickets+"张票");
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
               // lock.unlock();
            }

        }

    }
}

问题1:超出在这里插入图片描述
原因:
在这里插入图片描述
解决:锁对象前面加static关键字

问题2:程序未停止
在这里插入图片描述
原因:
在这里插入图片描述
解决:想办法让解锁程序无论如何都要执行,使用try-catch-finally
在这里插入图片描述

public class MyLock extends Thread{
    static int tickets=0;
    static Lock lock=new ReentrantLock();//需要像共享数据一样定义为static类型
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                if (tickets==100){
                    break;
                }else {
                    tickets++;
                    System.out.println("线程"+Thread.currentThread().getName()+"卖第"+tickets+"张票");
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }

        }

    }
}

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

在这里插入图片描述
桌子上有面条-》吃货执行
桌子上没面条-》生产者制造执行
在这里插入图片描述

1、消费者等待

消费者先抢到CPU执行权,发现桌子上没有面条,于是变成等待wait状态,并释放CPU执行权,此时的CPU肯定会被厨师抢到,初始开始做面条,当厨师做完后会对吃货进行提示,notify唤醒吃货来吃。
在这里插入图片描述
在这里插入图片描述

2、生产者等待

厨师先抢到CUP执行权,但是桌子上有面条,就不能再制作面条,只能等待消费者吃完面条才能做,消费者吃完后需要唤醒厨师继续做
在这里插入图片描述
代码逻辑:
在这里插入图片描述
厨师:

public class Cook extends Thread{
    @Override
    public void run() {
        //1循环
        //2同步代码块
        //3共享数据是否到末尾,Yes
        //4共享数据是否到末尾,No
        while (true){
            synchronized (Desk.lock){
                if (Desk.count==0){
                    break;//10碗吃完
                }else {
                    //厨师的核心逻辑
                    //01判断桌子上是否有食物
                    if (Desk.foodflag==1){
                        //02有食物就等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        //03没有
                        System.out.println(Thread.currentThread().getName()+"制作食物");
                        //04改变桌子状态
                        Desk.foodflag=1;
                        //05唤醒消费者吃
                        Desk.lock.notifyAll();
                    }

                }
            }


        }
    }
}

吃货:

public class Customer extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                if (Desk.count==0){
                    break;//10碗吃完
                }else {
                    //吃货的核心逻辑
                    /*
                    * 1.判断桌子上有无面条
                    * 2.没有:自己等待,
                    * 3.有:吃完,并唤醒厨师做面条,count--
                    * 4.修改桌子状态*/
                    if (Desk.foodflag==0){//1.判断桌子上有无面条
                        try {
                            Desk.lock.wait();//2.没有:自己等待,
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {//3.有:吃完,并唤醒厨师做面条,count--
                        Desk.count--;
                        System.out.println(Thread.currentThread().getName()+"还能再吃"+Desk.count+"碗");
                        Desk.lock.notifyAll();
                        //4.修改桌子状态
                        Desk.foodflag=0;

                    }

                }
            }


        }
    }
}

桌子:

public class Desk {
    //通过变量来控制 0:没食物  1:有食物
    public static int foodflag=0;

    //总个数,最多做十碗
    public static int count=10;

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

//测试类

public class Test {
    public static void main(String[] args) {
        Customer customer = new Customer();
        Cook cook = new Cook();

        customer.setName("吃货");
        cook.setName("厨师");

        customer.start();
        cook.start();
    }
}

3、阻塞队列实现

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

接口无法new对象,只能通过两个实现类,第一个可以自定义队列长度。
注意:生产者与消费者必须针对同一个阻塞队列,阻塞队列可以创建在测试类中
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
厨师:

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

    }
}

消费者:

public class Customer extends Thread{
    ArrayBlockingQueue<String> queue;

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

    @Override
    public void run() {
        while (true){
            try {
                String food=queue.take();//tack底层也进行了加锁,不需要我们自己定义
                System.out.println("获取食物"+food);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(1);
        Customer customer = new Customer(queue);
        Cook cook = new Cook(queue);
        customer.setName("吃货");
        cook.setName("厨师");

        customer.start();
        cook.start();
    }
}

2.8案例:抢红包

package JavaSE多线程_并发编程.案例;

import java.util.Random;

/*需求:
* 假设有100块的红包,分成3个包,现在五个人去抢
* 1.红包总金额100为共享数据
* 2.红包数量3也是共享数据
* 3.五个人为五个线程
* 打印结果:
* XXX抢到了XXX元
* XXX抢到了XXX元
* XXX抢到了XXX元
* XXX没抢到
* XXX没抢到
* */
public class MyThread extends Thread {
    //01、定义共享数据
    public static double money=100;
    public static int count=3;
    //红包最小金额
    public static final double Minmoney=0.01;
    @Override
    public void run() {
        //1.循环,抢红包中一个人(线程)只能抢一次,所以不需要循环
        //2.同步代码块
        synchronized (MyThread.class){
            //3.判断共享数据是否执行到末尾:Yes
            if (count==0){
                System.out.println("红包已经抽完");
                System.out.println(Thread.currentThread().getName()+"没有抽到红包");

            }else {
                //4.判断共享数据是否执行到末尾:No
                //定义一个抽中的金额
                double prize=0;
                //(1)如果还剩一个包,剩下的钱都给他
                if (count==1){
                    prize=money;
                    System.out.println(getName()+"抽到了"+prize+"元");
                }else {
                    //(2)第一次和第二次抽需要随机,但是第一次抽的金额最大只能是99.8
                    Random random = new Random();
                    //但是第一次抽的金额最大只能是99.8
                    double bounds=  money-(count-1)* Minmoney;
                    //prize =random.nextDouble(bounds);//nextDouble只能在JDK17以后使用
                    
                    //random的范围是0-bounds,包括0
                    if (prize<Minmoney){
                        prize=Minmoney;//如果分到的钱小于最小金额0.01那么就将其设置为0.01
                    }
                }
                //每次抽完要减去分掉的钱
                money=money-prize;
                //红包个数减一
                count--;
                System.out.println(getName()+"抢到了"+prize+"元");


            }

        }

    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        MyThread myThread3 = new MyThread();
        MyThread myThread4 = new MyThread();
        MyThread myThread5 = new MyThread();
        //Alt+鼠标左键竖着选中多个变量
        //先空格出对应的行数,再Alt加左键选中行,粘贴
        myThread1.setName("线程1");
        myThread2.setName("线程2");
        myThread3.setName("线程3");
        myThread4.setName("线程4");
        myThread5.setName("线程5");
        myThread1.start();
        myThread2.start();
        myThread3.start();
        myThread4.start();
        myThread5.start();
    }
}

三、线程池创建线程

在这里插入图片描述线程池用来存储线程,刚开始也是空的,当第一个任务到来,需要创建一个线程的时候,会在线程池中进行创建线程,任务执行完后线程自动返回线程池,当后续再来其他任务的时候,如果之前创建的线程够用,就直接从线程池中拿,如果不够再创建。
在这里插入图片描述
线程池也有上线,可自定义大小
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

public class MyThreadPool {
    public static void main(String[] args) {
        //01、获取一个没有数量上线的的线程池对象
        ExecutorService pool1= Executors.newCachedThreadPool();
        //ExecutorService pool1= Executors.newFixedThreadPool(3);//设定线程池大小
        //02、提交4个任务
        pool1.submit(new MyRunnable());//类型选中一个runnable的
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        pool1.submit(new MyRunnable());
        //03、销毁线程池,一般不销毁
        //pool1.shutdown();

        /*线程池中会创建多个线程执行任务
        * pool-1-thread-2执行任务100
            pool-1-thread-4执行任务96
            pool-1-thread-4执行任务97
            pool-1-thread-4执行任务98
            pool-1-thread-4执行任务99
            pool-1-thread-4执行任务100
            pool-1-thread-3执行任务96
            pool-1-thread-3执行任务97
            pool-1-thread-3执行任务98
            pool-1-thread-3执行任务99
            pool-1-thread-3执行任务100*/
    }
}
public class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i <=100 ; i++) {
            System.out.println(Thread.currentThread().getName()+"执行任务"+i);
        }
    }
}
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泰勒今天想展开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值