吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2060|回复: 19
收起左侧

[学习记录] 【自学笔记】 Java基础 - 多线程

  [复制链接]
LongJL 发表于 2021-7-21 10:52
本帖最后由 LongJL 于 2021-7-21 11:34 编辑

多线程

概述

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

并发与并行

并发:指俩个或多个事件在同一个时间段内发生 --- 交替执行
并行:指俩个或多个事件在同一时刻发生(同时发生)--- 同时执行

进程与线程

进程

一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程,进程也是程序的一次执行过程,是系统运行程序的基本单位,系统运行一个程序是一个进程从创建运行到消亡的过程

进程具有:独立性,动态性,并发性

独立性

进程时一个能独立运行得基本单位,同时也是系统分配资源喝调度得独立单位

动态性

进程的实质时程序的一次执行过程,进程时动态产生,动态消亡的。

并发性

任何进程都可以同其他进程一期并发执行

线程

进程中一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,一个进程中是可以有多个线程的,这个应用程序也可以称为多线程程序

单线程

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

多线程

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

实现多线程的方式

继承Thread类

步骤

1.定义一个类继承Thread
2.在类中重写run()方法
3.创建对象
4.调用start()方法启动线程

public class Demo1 {
    public static void main(String[] args) {
        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        // 开启线程
        t1.start();
        t2.start();
    }
}
public class MyThread extends Thread {

    @Override
    public void run(){
        // 代码就是线程在开启之后执行的代码
        for (int i = 0; i < 100; i++) {
            System.out.println("线程开启了" + i);
        }
    }
}
注意事项
  • run是赋值封装被线程执行的代码,直接调用相对于普通方法,并没有开启线程
  • 开启线程的是start()方法,先启动线程,然后由JVM调用此线程的run()方法

实现Runnable接口

步骤

1.定义一个类RunnableTest实现Runnable接口
2.在类中重写run()方法
3.创建RunnableTest对象
4.创建Thread类的对象,把RunnableTest对象作为构造方法的参数
4.调用start()方法启动线程

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}
public class RunnableDemo1 {
    public static void main(String[] args) {
        // 创建了一个参数的对象
        MyRunnable myRunnable = new MyRunnable();

        // 创建线程对象,把参数传递进去
        // 线程启动后调用的是参数的run方法
        Thread thread = new Thread(myRunnable);

        thread.start();
    }
}

利用Callable和Future接口

步骤

1.定义一个类MyCallable实现Callable接口
2.在MyCallable类中重写call()方法
3.创建Mycallable类的对象
4.创建Future的来实现类FutureTask对象,把MyCallable对象作为构造方法的参数
5.创建Thread类的对象,把FutureTask对象作为构造方法的参数
6.启动线程

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 CallableDemo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 线程开启之后需要执行里面的call方法
        MyCallable mc = new MyCallable();
        // 可以获取线程完毕之后的结果,也可以作为参数传递给Thread对象
        FutureTask<String> ft = new FutureTask<>(mc);
        // 创建线程对象
        Thread t1 = new Thread(ft);
        // 开启线程
        t1.start();
        String s = ft.get();
        System.out.println(s);
    }
}
注意事项
  • FutureTask中的get方法要字线程启动之后才能调用,get可以获取线程执行后的结果

三种方式的对比

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

线程类的常见方法

获取和设置线程的名称
  • String getName():返回此线程的名称
    注:线程由默认名字,格式:Thread-编号
  • void setName(String name):将此线程的名称更改为参数name
  • 通过构造方法也可以设置线程名字,需要再类中调用super
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);
        }
    }
}

public class ThreadMethodDemo1 {
    // 线程由默认名字,格式:Thread-编号
    public static void main(String[] args) {
//        MyThread mt1 = new MyThread();
//        MyThread mt2 = new MyThread();

        MyThread mt1 = new MyThread("小白");
        MyThread mt2 = new MyThread("小衣");

//        mt1.setName("小白");
//        mt2.setName("小衣");

        mt1.start();
        mt2.start();
    }
}
获取当前线程的对象
  • public static Thread currentThread():返回当前正在执行的线程对象的引用
public class ThreadMethodDemo2 {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
    }
}
package com.BJ.Thread.RunnableDemo;

public class RunnableDemo1 {
    public static void main(String[] args) {
        // 创建了一个参数的对象
        MyRunnable myRunnable = new MyRunnable();

        // 创建线程对象,把参数传递进去
        // 线程启动后调用的是参数的run方法
        Thread thread = new Thread(myRunnable);

        thread.start();

        MyRunnable myRunnable2 = new MyRunnable();

        // 创建线程对象,把参数传递进去
        // 线程启动后调用的是参数的run方法
        Thread thread2 = new Thread(myRunnable2);

        thread2.start();
    }
}

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "=" +i);
        }
    }
}
线程休眠
  • public static void sleep(long time):让线程休眠指定的时间,单位为毫秒
public class SleepDemo1 {
    public static void main(String[] args) throws InterruptedException {
//        System.out.println("睡眠前");
//
//        Thread.sleep(10000);
//
//        System.out.println("起床了");

        MyRunnable myRunnable = new MyRunnable();

        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);

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

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
                System.out.println(Thread.currentThread().getName() + "-" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
线程合并
  • public final void join():等待这个线程死亡。

注:
1.Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞

public class TestJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程Vip来了");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin = new TestJoin();

        Thread t1 = new Thread(testJoin);
        t1.start();

        for (int i = 0; i < 1000; i++) {
            if (i==200){
                t1.join(); // 插队
            }
            System.out.println("main" + i);
        }
    }
}
线程礼让
  • public static void yield():暂停当前正在执行的线程对象,并执行其他线程

注:
1.礼让线程,让当前正在执行的线程暂停,但不阻塞
2.让线程从运行状态转为就绪状态
3.让cpu重新调度,礼让不一定成功!

public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();

        new Thread(myYield).start();
        new Thread(myYield).start();
    }
}

class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始执行");
        Thread.yield();  // 礼让
        System.out.println(Thread.currentThread().getName() + "线程停止执行");
    }
}
中断线程
  • public static boolean interrupted():中断线程,不推荐使用
线程状态检测
  • public final boolean isAlive():测试这个线程是否活着。
线程调度

多线程的并发运行:计算机的CPU,再任意时刻只能执行一条机器指令。每个线程只有获取CPU的使用权才能执行代码,各个线程轮流获得CPU的试用期,分别执行各自的任务。

俩种调度模式
  • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
  • 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取CPU时间片相对多一些

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

线程的优先级

优先级范围:1~10,默认值是:5,优先级越高,抢到时间片几率越高

  • public final void setPriority(int newPriority):设置线程的优先级
  • public final int getPriority():获取线程的优先级
守护线程/后台线程
  • public final void setDaemon(boolean on):设置为守护线程
public class Demo2 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread1 t2 = new MyThread1();

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

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

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

线程的安全问题

不安全的程序

public class TicketMain {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();

        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t1);
        Thread thread3 = new Thread(t1);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();

    }
}

public class Ticket implements Runnable{
    private int ticketCount = 100;
    @Override
    public void run() {
        while (true){
            if (ticketCount == 0){
                break;

            }else{
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "买票,票还有" + ticketCount + "张" );
                ticketCount--;
            }
        }
    }
}

安全问题

1.相同得票出现了多次
2.出现了负数票

安全问题的解决方法

为什么会出现问题呢?

因为多线程共享数据

如何解决多线程安全问题呢?
  • 基本思想:让程序没有安全问题的环境
如何实现?
  • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
同步代码块

锁多条语句操作共享数据,可以实现同步代码块实现

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

注:默认清空是打开的,只要一个线程进去执行代码了,锁就会关闭。当线程执行完毕出来,锁才会自动打开

好处和弊端
  • 好处:解决了多线程的数据安全问题
  • 弊端:当线程很大时,因为每个线程都会去判断同步上的锁吗,这是很耗费资源的,无形中会降低程序的运行效率
public class TicketMain {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();

        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t1);
        Thread thread3 = new Thread(t1);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();

    }
}
public class Ticket implements Runnable{
    private int ticketCount = 100;
    private Object obj = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (obj){ // 多个线程必须使用同一把锁
                if (ticketCount == 0){
                    break;

                }else{
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "买票,票还有" + ticketCount + "张" );
                    ticketCount--;
                }
            }
        }
    }
}
同步方法

把synchronized关键字加到方法是,同步方法的锁对象是this

格式
修饰符 synchronized 返回值类型 方法名(方法参数){}
同步代码块和同步方法的区别
  • 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
  • 同步代码块可以指定锁对象,同步方法不能指定锁对象
public class Demo {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

        t1.setName("窗口一");
        t2.setName("窗口二");

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

    }
}

public class MyRunnable implements Runnable {
    private int ticketCount = 100;
    @Override
    public void run() {
        while (true){
            if ("窗口一".equals(Thread.currentThread().getName())){

                // 同步方法
                Boolean aBoolean = synchronizedMethod();
                if (aBoolean){
                    break;
                }
            }

            if ("窗口二".equals(Thread.currentThread().getName())){
                // 同步代码块
                synchronized (this){
                    if (ticketCount == 0){
                        break;
                    }else {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张");
                    }
                }
            }
        }
    }

    private synchronized Boolean synchronizedMethod() {
        if (ticketCount == 0){
            return true;
        }else {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张");
            return false;
        }
    }
}
同步静态方法
格式
修饰符 static synchronized 返回值类型 方法名(方法参数){}

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

public class Demo {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

        t1.setName("窗口一");
        t2.setName("窗口二");

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

    }
}

public class MyRunnable implements Runnable {
    private static int ticketCount = 100;
    @Override
    public void run() {
        while (true){
            if ("窗口一".equals(Thread.currentThread().getName())){

                // 同步方法
                Boolean aBoolean = synchronizedMethod();
                if (aBoolean){
                    break;
                }
            }

            if ("窗口二".equals(Thread.currentThread().getName())){
                // 同步代码块
                synchronized (MyRunnable.class){
                    if (ticketCount == 0){
                        break;
                    }else {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张");
                    }
                }
            }
        }
    }

    // 静态只能调用静态
    private static synchronized Boolean synchronizedMethod() {
        if (ticketCount == 0){
            return true;
        }else {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张");
            return false;
        }
    }
}
Lock锁
方法
  • void lock():获得锁
  • void unlock() :释放锁
注意事项
  • Lock是接口不能直接实例化,所有需要它的实现类ReentrantLock来实例化
ReentrantLock的构造方法
  • ReentrantLock() :创建一个ReentrantLock的实例
死锁

线程的死锁是指由于俩个或多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前进执行, 主要是因为锁的嵌套

生产者与消费者

消费者等待
消费者
  1. 判断桌子上是否有产品
  2. 如果没有就等待
生产者
  1. 生产产品
  2. 把产品放到桌子上
  3. 叫醒等待的消费者来取产品
生产者等待
生产者
  1. 判断桌子上是否有产品,如果有就等待,如果没有才生产
  2. 把产品放到桌子上
  3. 叫醒等待的消费者来取产品
消费者
  1. 判断桌子上是否有产品
  2. 如果没有就等待
  3. 如果有就取走
  4. 取走之后,桌子就空了,叫醒等待的生产者继续生产,汉堡包的总数量减一
等待和唤醒的方法(在Object类中)
  • void wait()导致当前线程等待,直到另一个线程调用该对象的notify0方法或notifyAll()方法
  • void notify()唤醒正在等待对象监视器的单个线程
  • void notifyAll()唤醒正在等待对象监视器的所有线程
public class Demo {
    public static void main(String[] args) {

        Desk desk = new Desk();

        Foodie foodie = new Foodie(desk);
        Cooker cooker = new Cooker(desk);

        foodie.start();
        cooker.start();
    }
}

public class Foodie extends Thread {
    private Desk desk;

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

    @Override
    public void run() {
        // 套路
            // 1.  while (true)死循环
            // 2.  synchronized 锁,锁对象要唯一
            // 3.  判断,共享数据是否结束,结束
            // 4.  判断,共享数据是否结束,没有结束
        while (true) {
            synchronized (desk.getLock()) {
                if (desk.getCount() == 0) {
                    break;
                } else {
                    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 Cooker extends Thread {

    private Desk desk;

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

    @Override
    public void run() {
        while (true) {
            synchronized (desk.getLock()) {
                if (desk.getCount() == 0) {
                    break;
                } else {
                    if (desk.isFlag()) {
                        try {
                            desk.getLock().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        System.out.println("生产汉堡包");
                        desk.setFlag(true);
                        desk.getLock().notifyAll();
                    }
                }
            }
        }
    }
}

public class Desk {

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

    // 汉堡包的总数量
    private int count = 10;

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

    public Desk() {
    }

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

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

阻塞队列基础结构

Iterable  -> Collection -> Queue -> BlockingQueue -> ArrayBlockingQueue

BlockingQueue -> ArrayBlockingQueue:底层是数组,有界

BlockingQueue -> LinkedBlockingQueue:底层是链表,无界。但不是真正意义上的无界,最大为int的最大值

BlockingQueue的核心方法
  • put(anObject):将参数放入队列,如果放不进去会阻塞、
  • take():取出第一个数据,取不到会阻塞
ArrayBlockingQueue
构造方法
  • ArrayBlockingQueue(int capacity)创建具有给定(固定)容量和默认访问策略的 ArrayBlockingQueue 。  
  • ArrayBlockingQueue(int capacity, boolean fair) 创建一个 ArrayBlockingQueue具有给定(固定)容量和指定访问策略。  
  • ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)创建一个 ArrayBlockingQueue具有给定(固定)容量,指定访问策略和最初包含给定集合中的元素,添加在收集迭代器的遍历顺序。
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        // 阻塞队列的基本用法

        // 创建组设队列的对象,容量为1
        ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);

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

        // 取元素
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take()); // 因为取不到值 就阻塞了 后面的代码不会执行
        System.out.println("程序结束");

    }
}
public class Demo {
    public static void main(String[] args) {
        // 创建阻塞队列
        ArrayBlockingQueue<String> list = new ArrayBlockingQueue<>(1);

        // 创建线程并开启

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

        c.start();
        f.start();
    }
}
public class Cooker extends Thread {
    private ArrayBlockingQueue<String> list;
    public Cooker(ArrayBlockingQueue<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true){
            try {
                list.put("汉堡包");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Foodie extends Thread{

    private ArrayBlockingQueue<String> list;
    public Foodie(ArrayBlockingQueue<String> list) {
        this.list = list;
    }
    @Override
    public void run() {
        while (true) {
            try {
                String take = list.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } 
        }
    }
}

多线程进阶

虚拟机中的线程状态

  • 新建(NEW):创建线程对象 -> 创建线程对象
  • 就绪(Runnable):有执行资格,但没有执行权  -> start方法
  • 结束(TERMINATED):线程结束,编程垃圾  -> 全部代码运行完毕
  • 阻塞(BLOCKED):没有执行资格,没有执行权 -> 无法获取锁对象
  • 等待(WAITING):没有执行资格,没有执行权 -> wait方法
  • 计时等待(TIMED_WAITING):没有执行资格,没有执行权 -> sleep方法

线程池

步骤

  1. 创建Executors静态方法,创建线程池
  2. submit方法 注:池子会帮忙创建对象,任务执行完毕,也会自动把线程归还给线程池
  3. 关闭连接池,shutdown方法
Executors
创建线程池方法
  • newCachedThreadPool():创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。
  • newFixedThreadPool(int nThreads):创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。 参数可以规定最多可以容纳多少个线程
public class ThreadPoolDemo1 {
    public static void main(String[] args) throws InterruptedException {
        // 创建默认线程池对象,池子默认是空的,默认最多可以容纳int类型的最大值
        ExecutorService executorService = Executors.newCachedThreadPool();
        // Executors --- 可以帮助创建线程池对象
        // ExecutorService --- 控制线程池对象

        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });

        Thread.sleep(2000);

        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });

        executorService.shutdown();
    }
}
public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });

        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "在执行了");
        });

        executorService.shutdown();
    }
}
ThreadPoolExecutor
  • ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) :创建一个新 ThreadPoolExecutor给定的初始参数。
    注:
    参数一:核心线程数量,
    参数二:最大线程数,
    参数三:空闲线程最大存活时间,
    参数四:时间单位(从TimeUnit里面获取单位),----TimeUnit
    参数五:任务队列, --- 让任务队列中等待,等有线程空闲了,再从这个队列中获取任务并且执行
    参数六:创建线程工厂,--- 按照默认的方式创建线程对象
    参数七:任务的拒绝策略 --- ① 什么适合拒绝 当提交的任务 > 池子中最大线程数量 + 队列容量 ② 如何拒绝 任务拒绝策略
// 参数一:核心线程数量 不能小于0
// 参数二:最大线程数   不能小于0,最大数量>=核心线程数量
// 参数三:空闲线程最大存活时间  不能小于0
// 参数四:时间单位  
// 参数五:任务队列  不能为Null
// 参数六:创建线程工厂  不能为Null
// 参数七:任务的拒绝策略  不能为Null

public class ThreadPoolDemo3 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5,2, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        pool.shutdown();
    }
}
任务拒绝策略
  • ThreadPoolExecutor AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。是默认-的策略。
  • ThreadPoolExecutor.DiscardPolicy :丢弃任务,但是不抛出异常这是不推荐的做法。
  • ThreadPoolExecutor.DiscardOldestPolicy :抛弃队列中等待最久的任务然后把当前任务加入队列中。
  • ThreadPoolExecutor.CallerRunsPolicy.调用任务的run0方法绕过线程池直接执行。

volatile

强制线程每次再使用的时候,都会看一下共享区域最新的值,如果没加,那么代码每次执行的时候读取的是变量副本里面的值,如果再其他线程发送了值得改变。其他线程是不知道了加了volatile就可以解决这个问题。

public class VolatileDemo1 {
    public static void main(String[] args) {
        Girl girl = new Girl();
        girl.setName("小路");
        girl.start();

        Boy boy = new Boy();
        boy.setName("小皮");
        boy.start();
    }
}
public class Girl extends Thread{

    @Override
    public void run() {
        while (Money.money == 100000){
        }
        System.out.println("结婚基金已经不是10W了");
    }
}
public class Boy extends Thread {
    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Money.money = 90000;
    }
}
public class Money {
    public static volatile int money = 100000;
}

注:

  • Synchronized同步代码块也可以解决线程读取不到最新值得问题,同步代码块每次执行时
    1. 线程获得锁
    2. 清空变量副本
    3. 考本共享变量最新得值到变量副本中
    4. 执行代码
    5. 将修改后变量副本中的值赋值给共享数据
    6. 释放锁
  • volatile关键字不能保证原子性,只能保证线程每次再使用共享数据的时候时最新值

原子性

概述

原子性是指再一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不好收到任何因素的干扰而中断,要么所有的操作都不执行, 多个操作时一个不可分割的整体

public class MyAtomThread implements Runnable {
    private int count = 0; // 送冰淇淋的数量
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            // 1.从共享数据中读取数据到本线程栈中
            // 2.修改本线程栈中变量副本的值
            // 3.会把本线程栈中变量副本的值赋值给共享数据
            count++;
            System.out.println("已经送了" + count + "个冰淇淋");
        }
    }
}

public class AtomDemo {
    public static void main(String[] args) {
        MyAtomThread atom = new MyAtomThread();

        for (int i = 0; i < 100; i++) {
            new Thread(atom).start();
        }
    }
}
代码问题
  • count++ 不是原子性操作,也就是说他在执行的过程中,有可能会被其他线程打断操作

解决方案

  1. 同步代码块
  2. 原子类(效率更高)

原子类 AtomicInteger

public AtomicInteger() :初始化-个默认值为0的原子型Integer
public AtomicInteger(int initialValue):初始化一个指定值的原子型Integer
int get():获取值
int getAndIncrement():以原子方式将当前值加1 ,注意,这里返回的是自增前的值。
int incrementAndGet():以原子方式将当前值加1 ,注意,这里返回的是自增后的值。
int addAndGet(int data):以原子方式将输入的数值与实例中的值( AtomicInteger里的value )相加,并返回结果。
int getAndSet(int value):以原子方式设置为newValue的值,并返回旧值。

public class AtomIntergerDemo1 {
    public static void main(String[] args) {
        AtomicInteger ai = new AtomicInteger();

        System.out.println(ai);

        AtomicInteger ai2 = new AtomicInteger(10);
        System.out.println(ai2);
    }
}
public class AtomIntergerDemo2 {
    public static void main(String[] args) {
        AtomicInteger ai = new AtomicInteger();

        System.out.println(ai.get());

        ai.getAndIncrement();
        System.out.println(ai.get());

        int andIncrement = ai.incrementAndGet();
        System.out.println(andIncrement);

        int i = ai.addAndGet(10);
        System.out.println(i);

        int andAdd = ai.getAndAdd(20);

        System.out.println(andAdd);
        System.out.println(ai);

        int andSet = ai.getAndSet(100);
        System.out.println(andSet);
        System.out.println(ai);
    }
}
AtomicInteger 原理
  • 自旋锁 + CAS算法
CAS算法

有三个操作数(内存值V,旧的预期值A,要修改的值B)
1.当旧的预期值A == 内存值 此时修改成功,将V改为B
2.当旧的预期值A != 内存值 此时修改失败,不做任何操作,并且重新获取现在的最新值(这个重新获取的动作就是自旋)

CAS和synchronized的区别
相同点
  • 再多线程情况下,都可以保证共享数据的安全性
不同点
  • synchronized总时从最坏的角度触发,认为每次获取数据的时候,别人都要可能修改。所以在每次操作共享数据之前,都会上锁。(悲观锁)
  • cas时从乐观的角度出发,假设每次获取数据别人都不好修改,所以就不会上锁。只不过在修改共享数据的时候,会检测一下,别人有没有修改过这个数据。如果别人修改过,那么我再次获取现在最新的值。如果别人没有修改过,那么我现在直接修改共享数据的值。(乐观锁)

并发工具类

Hashtable

HashMap是线程不安全的(多线程环境下可能会存在问题)。
为了保证数据的安全性我们可以使用Hashtable,但是Hashtable的效率低下

  1. Hashtable采取悲观锁synchronized的形式保证数据的安全性
  2. 只要有线程访问,会将整张表全部锁起来,所以Hashtable的效率低下。
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Hashtable<String, String> hm = new Hashtable<>();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 25; i++) {
                hm.put(i + "", i + "");
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 25; i < 51; i++) {
                hm.put(i + "", i + "");
            }
        });

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

        System.out.println("===============================");
        // 为了T1和T2能把数据全部添加完毕
        Thread.sleep(1000);

        for (int i = 0; i < 51; i++) {
            System.out.println(hm.get(i + ""));
        }
    }
}

ConcurrentHashMap

  • 保证效率又保证数据的安全
    1.7版本的底层
    底层创建
    1. 默认创建一个长度16 ,加载因子为0.75的大数组。这个大数组一-旦创建无法扩容
    2. 还会创建一个长度为2的小数组,把地址值赋值给0索引处其他索引位置的元素都是null
底层添加

1 ,第1次会根据键的哈希值来计算出在大数组中应存入的位置。
如果为null ,则按照模板创建小数组创建完毕,会二次哈希,计算出在小数组中应存入的位置。直接存入
如果不为null ,就会根据记录的地址值找到小数组。二次哈希,计算出在小数组中应存入的位置。如果需要扩容,则将小数组扩容两倍如果不需要扩容,则就会小数组的这个位置有没有元素如果没有元素,则直接存,如果有元素,就会调用equals方法,比较属性值。如果equals为true,则不存。如果为false,则形成哈希桶结构

1.8版本的底层

1 ,如果使用空参构造创建C oncurrentHashMap对象,则什么事情都不做。在第一次添加元素的时候创建哈希表
2 ,计算当前元素应存入的索引。
3 ,如果该索引位置为null ,则利用cas算法,将本结点添加到数组中。
4 ,如果该索引位置不为null ,则利用volatile关键字获得当前位置最新的结点地址,挂在
他下面,变成链表。
5 ,当链表的长度大于等于8时,自动转换成红黑树
6,以链表或者红黑树头结点为锁对象,配合悲观锁保证多线程操作集合时数据的安全性。

CountDownLatch

使用场景:让某一条线程等待其他线程执行完毕之后再执行

方法

public CountDownL atch(int count)参数传递线程数,表示等待线程数量
public void await()让线程等待
public void countDown()当前线程执行完毕

public class CountDownLatchDemo1 {
    public static void main(String[] args) {
        // 创建对象,传递四个线程
        // 在底层创建了与i个计数器,此时计时器的值3
        CountDownLatch countDownLatch = new CountDownLatch(3);
        // 创建四个线程对象,开启线程
        MotherThread motherThread = new MotherThread(countDownLatch);
        motherThread.start();

        ChildThread1 t1 = new ChildThread1(countDownLatch);
        t1.setName("小明");

        ChildThread2 t2 = new ChildThread2(countDownLatch);
        t2.setName("小王");

        ChildThread3 t3 = new ChildThread3(countDownLatch);
        t3.setName("小翔");

        t1.start();
        t2.start();
        t3.start();
    }
}
public class MotherThread extends Thread {
    private  CountDownLatch countDownLatch;
    public MotherThread(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;

    }

    @Override
    public void run() {
        // 等待
        try {
            // 当计数器变成0的时候,会自动唤醒这里的等待线程
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 收拾碗筷
        System.out.println("妈妈正在收拾碗筷");
    }
}
public class ChildThread1 extends Thread{
    private CountDownLatch countDownLatch;
    public ChildThread1(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;

    }
    @Override
    public void run() {
        // 吃饺子
        for (int i = 1; i <= 10; i++) {
            System.out.println(getName()+"在吃第"+i+"个饺子");
        }
        // 吃完说一声
        // 每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}

public class ChildThread2 extends Thread {
    private CountDownLatch countDownLatch;
    public ChildThread2(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;

    }
    @Override
    public void run() {
        // 吃饺子
        for (int i = 1; i <= 15; i++) {
            System.out.println(getName()+"在吃第"+i+"个饺子");
        }
        // 吃完说一声
        // 每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}
public class ChildThread3  extends Thread{
    private CountDownLatch countDownLatch;
    public ChildThread3(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;

    }
    @Override
    public void run() {
        // 吃饺子
        for (int i = 1; i <= 20; i++) {
            System.out.println(getName()+"在吃第"+i+"个饺子");
        }
        // 吃完说一声
        // 每一次countDown方法的时候,就让计数器-1
        countDownLatch.countDown();
    }
}

Semaphore

使用场景:可以控制访问特定资源的线程数量

步骤
  1. 场景Semaphore对象
  2. acquire()发通行证
  3. release()收回通行证
构造方法
  • public Semaphore(int permits):最多允许几条线程进入
public class MyRunnable implements Runnable{
    private Semaphore semaphore = new Semaphore(2);
    @Override
    public void run() {
        // 1.创建对象
        try {
            // 2.获得通行证
            semaphore.acquire();

            // 3.开始执行
            System.out.println("获得通行证");
            Thread.sleep(2000);
            System.out.println("规划通行证");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 4.收回通行证
        semaphore.release();

    }
}

public class SemaphoreDemo {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();

        for (int i = 0; i < 100; i++) {
            new Thread(mr).start();
        }
    }
}

免费评分

参与人数 9吾爱币 +5 热心值 +8 收起 理由
柑桔 + 1 + 1 用心讨论,共获提升!
lqwer123456 + 1 + 1 谢谢@Thanks!
lxlsz + 1 + 1 用心讨论,共获提升!
Lnine + 1 好文,
nstar1221 + 1 + 1 我很赞同!
hyue + 1 用心讨论,共获提升!
li969532749 + 1 真不戳
huanglaoji + 1 我很赞同!
QingYi. + 1 好文

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Neuron_ray 发表于 2021-7-21 11:02
一起学习
huyuhang 发表于 2021-7-21 11:14
QingYi. 发表于 2021-7-21 11:33
 楼主| LongJL 发表于 2021-7-21 11:34
QingYi. 发表于 2021-7-21 11:33
“多选择是指”  第一句話就有錯誤....

哈哈哈 粗心了
小小吃瓜少年 发表于 2021-7-21 12:36
坚持是最好的老师呀,有啥法子可以坚持下去
muyu1314520 发表于 2021-7-21 12:48

一起学习  
jiaong 发表于 2021-7-21 13:30
笔记挺长,学习了。
 楼主| LongJL 发表于 2021-7-21 15:29
小小吃瓜少年 发表于 2021-7-21 12:36
坚持是最好的老师呀,有啥法子可以坚持下去

写笔记   写笔记是一件很有趣的事情。
zxsbk 发表于 2021-7-21 16:37
多线程确实是难点
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-26 00:48

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表