线程安全、线程同步(三种加锁方式)、线程池(两种创建线程池方式、线程池处理Runnable任务、线程池处理Callable任务)、并发/并行

线程安全

多个线程,同时操作一个共享资源的时候,可能会出现业务安全问题

线程安全问题出现的问题?

1、存在多个线程在同时执行

2、同时访问一个共享资源

3、存在修改该共享资源

模拟线程安全问题

java 复制代码
package com.itheima.demo3threadsafe;

public class ThreadDemo1 {
    public static void main(String[] args) {
        //目标:穆尼线程安全问题
        //1、设计一个账户类,用于创建小明和小红的共同账户对象,存入10万
        Account acc = new Account(100000, "6516666");

        //2、设计线程类,创建小明和小红两个线程,模拟小明和小红同时去同一个账户取款10万
        new DrawThread("小明", acc).start();
        new DrawThread("小红", acc).start();
    }
}

//=================分界线=================

package com.itheima.demo3threadsafe;
//取钱线程类
public class DrawThread extends  Thread{
    private Account acc;//记住线程对象要处理的账户对象

    public DrawThread(String name, Account acc) {
        super(name);
        this.acc = acc;
    }

    @Override
    public void run() {
        //取钱
        acc.drawMoney(100000);
    }
}

//=================分界线=================

package com.itheima.demo3threadsafe;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//共同账户类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    private double money;//余额
    private String cardId;//卡号

    //小明和小红都到这里来取钱
    public void drawMoney(double imoney) {
        //拿到当前是谁来取钱
        String name = Thread.currentThread().getName();
        //判断余额是否充足
        if (money >= imoney) {
            System.out.println(name + "取钱成功,余额为:" + money);
            //更新余额
            money = money - imoney;
            System.out.println(name + "取钱成功,余额为:" + money);
        } else {
            //余额不足
            System.out.println(name + "取钱失败,余额不足");
        }
    }
}

编译结果:

出现线程安全问题

线程同步

线程同步是线程安全问题的解决方案

线程同步常见方案:

加锁

1、同步代码块

锁的获取机制:

1、对于同一个锁对象,同一时间内只能有一个线程能够获得该锁并执行同步代码块

2、线程在进入 synchronized(锁对象) 块时获取锁

3、执行完同步代码块后自动释放锁

选中核心代码按住"ctrl+Alt+t"键快捷锁住

使用this只锁定特定的账户实例,不同账户间的操作不会相互阻塞

锁对象的使用规范

java 复制代码
package com.itheima.demo4_synchronized_code.demo3threadsafe;

public class ThreadDemo1 {
    public static void main(String[] args) {
        //目标:线程同步的方式一演示:同步代码块
        //1、设计一个账户类,用于创建小明和小红的共同账户对象,存入10万
        Account acc = new Account(100000, "6516666");

        //2、设计线程类,创建小明和小红两个线程,模拟小明和小红同时去同一个账户取款10万
        new DrawThread("小明", acc).start();
        new DrawThread("小红", acc).start();
    }
}

//=================分界线=================

package com.itheima.demo4_synchronized_code.demo3threadsafe;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//共同账户类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    private double money;//余额
    private String cardId;//卡号

    //小明和小红都到这里来取钱
    public void drawMoney(double imoney) {
        //拿到当前是谁来取钱
        String name = Thread.currentThread().getName();
        //判断余额是否充足
        synchronized (this) //使用this只锁定特定的账户实例,不同账户间的操作不会相互阻塞
        {
            if (money >= imoney) {
                System.out.println(name + "取钱成功,余额为:" + money);
                //更新余额
                money = money - imoney;
                System.out.println(name + "取钱成功,余额为:" + money);
            } else {
                //余额不足
                System.out.println(name + "取钱失败,余额不足");
            }
        }
    }
}

//=================分界线=================

package com.itheima.demo4_synchronized_code.demo3threadsafe;

//取钱线程类
public class DrawThread extends  Thread{
    private Account acc;//记住线程对象要处理的账户对象

    public DrawThread(String name, Account acc) {
        super(name);
        this.acc = acc;
    }

    @Override
    public void run() {
        //取钱
        acc.drawMoney(100000);
    }
}

同步代码块小结

2、同步方法(简单)

1、给核心方法的前缀加上synchronized即可

2、锁的获取机制:

对于同一个实例对象,同一时间内只能有一个线程能够获得该实例的锁并执行同步方法

java 复制代码
public synchronized void drawMoney(double imoney) {
        //拿到当前是谁来取钱
        String name = Thread.currentThread().getName();
        //判断余额是否充足
        if (money >= imoney) {
            System.out.println(name + "取钱成功,余额为:" + money);
            //更新余额
            money = money - imoney;
            System.out.println(name + "取钱成功,余额为:" + money);
        } else {
            //余额不足
            System.out.println(name + "取钱失败,余额不足");
        }
    }

比较同步代码块和同步方法

同步方法小结

3、lock锁

new一个锁对象后先上锁,然后在final(Ctrl+Alt+t快捷键第7个)里解锁

java 复制代码
package com.itheima.demo6_lock.demo3threadsafe;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Account {
    private double money;//余额
    private String cardId;//卡号
    private final Lock lk = new ReentrantLock();//创建锁对象,不能静态,final是保护锁对象

    //小明和小红都到这里来取钱
    public void drawMoney(double imoney) {
        //拿到当前是谁来取钱
        String name = Thread.currentThread().getName();
        lk.lock();//上锁
        try {
            //判断余额是否充足
            if (money >= imoney) {
                System.out.println(name + "取钱成功,余额为:" + money);
                //更新余额
                money = money - imoney;
                System.out.println(name + "取钱成功,余额为:" + money);

            } else {
                //余额不足
                System.out.println(name + "取钱失败,余额不足");
            }
        } finally {
            lk.unlock();//解锁,使用Ctrl+Alt+t 把该代码放在final里解锁,即使出现异常也能顺利解锁
        }
    }
}

lock锁小结

线程池

假设有3个线程,9个任务,这三个线程各自处理一个任务,当前任务处理完后就可以继续处理下一个任务,即完成了线程复用,这个线程复用的技术就称为线程池

创建线程池

1、使用实现类创建

2、使用线程池的工具类

方式一:通过ThreadPoolExectuor创建线程池

7个参数:前四个简单,后三个固定

java 复制代码
package com.itheima.demo7executiorService;
import java.util.concurrent.*;

public class ExecutorServiceDemo1 {
    public static void main(String[] args) {
        //目标:创建线程池对象
        //1、请使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5,10,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

        //2、使用线程池处理任务,看看会不会复用线程
    }
}

方式二:通过Executors创建线程池

最后一个方法属于底层,重点是第一个

java 复制代码
package com.itheima.demo7executiorService;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorsDemo3 {
    public static void main(String[] args) {
        //通过线程池工具类:Executors,调用其静态方法直接得到线程池
        ExecutorService pool = Executors.newFixedThreadPool(3);//创建固定大小的线程池

        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));//线程复用

        try {
            System.out.println(f1.get());
            System.out.println(f2.get());
            System.out.println(f3.get());
            System.out.println(f4.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

通过Executors创建线程池缺点

通过Executors创建线程池小结

处理Runnable任务

java 复制代码
        //目标:创建线程池对象
        //1、请使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3,5,10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());

        //2、使用线程池处理任务,看看会不会复用线程
        Runnable target = new MyRunnable();
        pool.execute(target);//提交第1个任务   创建线程  自动启动线程处理这个任务
        pool.execute(target);//提交第2个任务   创建线程  自动启动线程处理这个任务
        pool.execute(target);//提交第3个任务   创建线程  自动启动线程处理这个任务
        pool.execute(target);//线程复用,重新利用了以上三个线程,因为线程池规定只能有三个线程
        pool.execute(target);

        //3、关闭线程池:一般不关闭线程池
        pool.shutdown();//等所有任务执行完毕后再关闭线程池
        pool.shutdownNow();//不等所有任务执行完毕,直接关闭线程池

第四个意思是老板(主线程main)亲自来执行新的线程

java 复制代码
package com.itheima.demo7executiorService;

import java.util.concurrent.*;

public class ExecutorServiceDemo1 {
    public static void main(String[] args) {
        //目标:创建线程池对象
        //1、请使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3,5,10,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

        //2、使用线程池处理任务,看看会不会复用线程
        Runnable target = new MyRunnable();
        pool.execute(target);//提交第1个任务   创建线程  自动启动线程处理这个任务
        pool.execute(target);//提交第2个任务   创建线程  自动启动线程处理这个任务
        pool.execute(target);//提交第3个任务   创建线程  自动启动线程处理这个任务
        pool.execute(target);//线程复用,重新利用了以上三个线程,因为线程池规定只能有三个线程
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);//三个核心线程满了,三个任务队列也满了,到了临时线程创建的时机
        pool.execute(target);//临时线程继续创建
        pool.execute(target);//到了任务拒绝策略,忙不过来,会抛异常

        //3、关闭线程池:一般不关闭线程池
        pool.shutdown();//等所有任务执行完毕后再关闭线程池
        pool.shutdownNow();//不等所有任务执行完毕,直接关闭线程池
    }
}

//=================分界线=================

package com.itheima.demo7executiorService;

//1、定义一个线程任务类实现Runnable接口
public class MyRunnable implements Runnable{
    //2、重写run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+"输出:" + i);
            try {
                Thread.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

处理Callable任务

java 复制代码
package com.itheima.demo7executiorService;

import java.util.concurrent.*;

public class ExecutorServiceDemo2 {
    public static void main(String[] args) {
        //目标:处理Callable任务
        //1、请使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
        ExecutorService pool = new ThreadPoolExecutor(3,5,10,
                TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());

        //2、使用线程池处理Callable任务
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));

        try {
            System.out.println(f1.get());
            System.out.println(f2.get());
            System.out.println(f3.get());
            System.out.println(f4.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

//=================分界线=================

package com.itheima.demo7executiorService;

import java.util.concurrent.Callable;

//1、定义一个实现类实现Callable接口
//这个类的泛型是什么类型重写的call方法就要是什么类型,返回的也就是什么类型的数据
public class MyCallable implements Callable<String> {
    private int n;
    public MyCallable(int n) //把循环值通过有参构造器在具体对象中获取
    {
        this.n = n;
    }
    //2、重写call方法,将线程任务封装成字符串返回
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            System.out.println(i);
            sum += i;
        }
        return Thread.currentThread().getName()+"计算1-" + n +"的和是:"+ sum;
    }
}

并发/并行

线程是属于进程的

并发的含义:只是执行速度过快让我们认为在同时执行这就是并发

并行的含义:在同一时刻同时有多个线程被CPU调度

CPU是并行和并发同时执行的

并发/并行小结

相关推荐
liuc03172 小时前
Java项目关于不同key的读取
java·开发语言
yaoxin5211232 小时前
296. Java Stream API - 二元操作符与“单位元“
java·服务器·windows
罗伯特_十三2 小时前
Spring AI ChatModel 使用记录
java·人工智能·spring
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 基于SpringBoot的律师事务所管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
菜宾2 小时前
java-seata基础教学
java·开发语言·adb
毕设源码-朱学姐3 小时前
【开题答辩全过程】以 基于springboot的日用药品仓库管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
毕设源码-赖学姐3 小时前
【开题答辩全过程】以 基于javaweb的外卖点餐系统的设计与实现为例,包含答辩的问题和答案
java
沛沛老爹3 小时前
从Web到AI:行业专属Agent Skills生态系统技术演进实战
java·开发语言·前端·vue.js·人工智能·rag·企业转型
程农4 小时前
基于Java的报名系统
java·开发语言