java多线程笔记

1 多线程的实现方式

1.1 继承Thread类

  1. 自定义一个类继承Thread类
  2. 重写run方法
  3. 创建自定义类对象,启动线程
package com.ldz.thread_.itheima;

public class Thread1 {

    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("t1");
        t2.setName("t2");

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

}

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

1.2 实现Runnable接口

  1. 自定义类实现Runnable接口
  2. 重写run方法
  3. 创建自定义类对象,表示要执行的任务
  4. 创建线程对象,传入自定义对象,并开启线程
package com.ldz.thread_.itheima;

public class Thread2 {

    public static void main(String[] args) {
        // 创建MyRun对象,表示要执行的任务
        MyRun myRun = new MyRun();

        // 创建线程对象
        Thread t1 = new Thread(myRun);
        Thread t2 = new Thread(myRun);

        t1.setName("t1");
        t2.setName("t2");

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

}

class MyRun implements Runnable {

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

1.3 利用Callable接口和Future接口

该方法有返回值,前两种没有返回值

  1. 自定义类实现Callable接口
  2. 重写call方法,表示要执行的任务
  3. 创建自定义类的对象,表示多线程要执行的任务
  4. 创建FutureTask类的对象(作用管理多线程运行的结果)
  5. 创建Thread类的对象,并启动(表示线程)
package com.ldz.thread_.itheima;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Thread3 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(myCallable);
        Thread t = new Thread(task);
        t.start();
        System.out.println(task.get());
    }

}

class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {

        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum += i;
        }

        return sum;
    }
}

1.4 三种实现方式对比

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3LAZnwcX-1680262285011)(assets/image-20230330204009644.png)]

2 常见成员方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iKKX8iyD-1680262285012)(assets/image-20230330204426841.png)]

2.1 String getName()

默认值:Thread-X(X从0开始)

2.2 static Thread currentThread()

获取当前线程对象

当JVM虚拟器启动后,会自动启动多条线程。

其中有一条线程叫做main线程

它的作用就是调用main方法,并执行里面的代码

2.3 守护线程

将某个线程设置为守护线程后,其他没有设置的线程就是非守护线程

当非守护线程结束后,守护线程也会陆续结束

package com.ldz.thread_.itheima;

public class Thread4 {

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

}

class MyThread1 extends Thread {

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

class MyThread2 extends Thread {

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

3 线程的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mqcnbpxs-1680262285012)(assets/image-20230331100956487.png)]

4 同步代码块

目的是解决线程不安全问题,如超卖问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zzENXDeh-1680262285012)(assets/image-20230331101311837.png)]

synchronized后面跟的锁,在多个线程间必须唯一,可以定义静态成员变量或者使用该类的字节码对象

package com.ldz.thread_.itheima;

public class Thread5 {

    public static void main(String[] args) {
        MyThread3 t1 = new MyThread3();
        MyThread3 t2 = new MyThread3();
        MyThread3 t3 = new MyThread3();

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

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

}

class MyThread3 extends Thread {

    static int ticket = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (MyThread3.class) {
                if (ticket > 100) {
                    break;
                }
                try {
                    Thread.sleep(100);
                    System.out.println(getName() + "正在卖第" + ticket++ + "张票");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5 同步方法

把synchronized加到方法上

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WRcNBwIn-1680262285013)(assets/image-20230331102556496.png)]

package com.ldz.thread_.itheima;

public class Thread6 {

    public static void main(String[] args) {
        MyThread4 t1 = new MyThread4();
        MyThread4 t2 = new MyThread4();
        MyThread4 t3 = new MyThread4();

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

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

}

class MyThread4 extends Thread {

    static int ticket = 1;

    @Override
    public void run() {
        while (true) {
            if (!t()) break;
        }
    }

    private synchronized static boolean t() {

        if (ticket > 100) {
            return false;
        }
        try {
            Thread.sleep(1);
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket++ + "张票");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            return true;
        }

    }
}

6 锁

同步代码块、同步方法是自动上锁、解锁。手动上锁就引出了这里的锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Sqlj7vU-1680262285013)(assets/image-20230331103742695.png)]

  1. 多个线程间,必须用统一把锁,用static声明成员变量
  2. finnally中释放锁,避免break后,锁没有释放
package com.ldz.thread_.itheima;

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

public class Thread7 {

    public static void main(String[] args) {
        MyRunnable2 myRunnable2 = new MyRunnable2(new ReentrantLock());

        Thread t1 = new Thread(myRunnable2);
        Thread t2 = new Thread(myRunnable2);
        Thread t3 = new Thread(myRunnable2);

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

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

}

class MyRunnable2 implements Runnable {

    private static int ticket = 1;

    private Lock lock;

    public MyRunnable2(Lock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (ticket > 100) {
                    break;
                }
                Thread.sleep(10);
                System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket++ + "张票");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }
}

7 死锁

2个线程,互相等待

8 等待唤醒机制

8.1 问题描述

一般来说,线程的执行顺序是没有规律的,如下图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jG5EhSi8-1680262285013)(assets/image-20230331111159698.png)]

如果想让线程交替执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I2T27MGA-1680262285014)(assets/image-20230331111230795.png)]

则需要使用等待唤醒机制(生产者和消费者)

8.2 例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UeRJSAIw-1680262285014)(assets/image-20230331111403160.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h38RkjEM-1680262285014)(assets/image-20230331111457139.png)]

当你在调用notify(), notifyAll(),wait(), wait(long), wait(long, int)等线程控制操作方法时,必须要有两个前提。

第一:必须要在被synchronized关键字控制的同步代码块中,才能调用这些方法。

第二,调用者必须为你当前的锁对象。

package com.ldz.thread_.waitandnotify;

import sun.security.krb5.internal.crypto.Des;

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

public class test {

    public static void main(String[] args) {
        Foodie foodie = new Foodie();
        Cook cook = new Cook();

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

        foodie.start();
        cook.start();
    }

}

class Desk {

    public static int foodFlag = 0;

    public static int count = 10;

    public static Lock lock = new ReentrantLock();

}

class Foodie extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                try {
                    if(Desk.count < 1) {
                        // 线程结束
                        break;
                    }
                    if (Desk.foodFlag == 0) Desk.lock.wait(); // 让当前线程跟锁对象进行绑定
                    else {
                        // 业务
                        System.out.println(getName() + "还能吃" + --Desk.count + "碗");
                        // 修改状态
                        Desk.foodFlag = 0;
                        // 唤醒生产者
                        Desk.lock.notifyAll();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class Cook extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                try {
                    if (Desk.count < 1) {
                        break;
                    }

                    if (Desk.foodFlag == 1) {
                        // 等待
                        Desk.lock.wait();
                    } else {
                        System.out.println(getName() + "生产了1个");
                        // 生产
                        Desk.foodFlag = 1;
                        // 唤醒消费者
                        Desk.lock.notifyAll();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}		

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

8.3.1 阻塞队列

  1. 阻塞:

    put数据时,放不进去(队列满了),会等待,就叫阻塞
    take数据时,取不到数据(队列空),会等待,也叫阻塞

  2. 队列:FIFO

8.3.2 阻塞队列的继承结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uck2uERI-1680262285015)(assets/image-20230331134534482.png)]

package com.ldz.thread_.waitandnotify2;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;

public class test {

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

        Cook cook = new Cook(blockingQueue);
        Foodie foodie = new Foodie(blockingQueue);

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

        cook.start();
        foodie.start();
    }

}

class Cook extends Thread {

    ArrayBlockingQueue<String> queue;

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

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
                queue.put("面条");
                System.out.println(getName() + "放了一碗面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Foodie extends Thread {

    ArrayBlockingQueue<String> queue;

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

    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(1000);
                String food = queue.take();
                System.out.println(getName() + "吃了一碗" + food);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

9 线程池

9.1 引出

不使用线程池,创建线程时,被创建的线程在运行完后就会destroy

下一次还要使用线程执行任务时,就会新建线程,然后线程destroy

9.2 线程池主要核心原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sRMZrw4h-1680262285015)(assets/image-20230331162352530.png)]

9.3 代码实现

  1. 创建线程池

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7b8syZcZ-1680262285016)(assets/image-20230331163004908.png)]

    ExecutorService pool = Executors.newCachedThreadPool();
    
    ExecutorService pool = Executors.newFixedThreadPool(3);
    
  2. 提交任务

    pool.submit(new MyRunnable())
    

    MyRunnable类要实现Runnable接口,表示要执行的任务

  3. 所有的任务全部执行完毕,关闭线程池(一般不关)

    pool.shutdown()
    

10 自定义线程池

10.1 参数

在这里插入图片描述

10.2 代码

ExecutorService pool = new ThreadPoolExecutor(
    3,	// 核心线程数量,不能小于0
    6,	// 最大线程数,大于等于核心线程数量
    60,	// 空闲线程最大存活时间
    TimeUnit.SECONDS,	// 时间单位
    new ArrayBlockingQueue<>(3),	// 任务队列
    Executors.defaultThreadFactory(),	// 创建线程工厂
    new ThreadPoolExecutor.AbortPolicy()	// 任务拒绝策略
);			

这里任务拒绝策略使用了内部类

为什么要使用内部类:

  1. 独立存在没有意义,要依赖外部类存在
  2. 内部类是一个独立的个体

10.3 不断地提交任务会有以下3个临界点:

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

    在这里插入图片描述

  2. 当核心线程满,队列满时,会创建临时线程

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lk7ATtVg-1680262285017)(assets/image-20230331164818555.png)]

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o5fDiKS1-1680262285017)(assets/image-20230331164933211.png)]

10.4 任务拒绝策略

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RDbh50sR-1680262285018)(assets/image-20230331165037954.png)]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值