首页 > 其他分享 >day11-多线程

day11-多线程

时间:2024-09-12 21:51:11浏览次数:13  
标签:account void Person 线程 day11 new 多线程 public

一、线程安全问题

线程安全问题出现的原因?
    存在多个线程在同时执行
    同时访问一个共享资源
    存在修改该共享资源
线程安全:
    多个线程同时修改同一个资源

取钱案例 小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元 如果小明和小红同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题呢?

实现步骤
    1. 创建1个账户类,在类中定义账户金额和取钱方法
    2. 创建1个取钱线程类,在类中调用取钱方法
    3. 在主线程中创建1个账户对象代表二人的共享账户
    4. 在主线程中创建2个取钱线程,分别代表小明和小红,并将共享账户对象传递给2个线程处理
    5. 启动2个线程
public class Demo {
    public static void main(String[] args) {
        //在主线程中创建1个账户对象代表二人的共享账户
        Account account = new Account();
        //创建2个取钱线程
        Person xiaoming = new Person(account);
        xiaoming.setName("小明");//线程名称
        Person xiaohong = new Person(account);
        xiaohong.setName("小红");//线程名称
        //启动2个线程,开始取钱
        xiaoming.start();
        xiaohong.start();
​
    }
​
}
//账户类
class Account{
    //定义账户金额和取钱方法
    private Integer money = 100000;
​
    public void drawMoney(Integer drawMoney){
        //判断余额是否充足
        if(drawMoney > money){
            throw new RuntimeException(Thread.currentThread().getName() + "余额不足");
        }
        //模拟取钱
        System.out.println(Thread.currentThread().getName() +"ATM吐出" + drawMoney);
        //更新余额
        money -= drawMoney;
        System.out.println("余额是" + money);
    }
}
​
//取钱线程类
class Person extends Thread{
    //线程共享的账户对象,不能创建Account对象,要传入Account对象
    private Account account;
    //构造器
    public Person(Account account){
        this.account = account;
    }
​
    //调用取钱方法
    @Override
    public void run() {
        account.drawMoney(10000);
    }
}
​
运行结果:
    小明100000
    小红100000
    小红余额为-100000
    小明余额为0

二、线程同步方案

线程同步
    线程同步就是让多个线程实现先后依次访问共享资源,这样就解决了安全问题,它最常见的方案就是加锁
    加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。

2.1 同步代码块

同步代码块
    把访问共享资源的核心代码给上锁,以此保证线程安全
​
格式
    synchronized(同步锁){
        访问共享资源的核心代码
    }
​
原理
    每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
​
注意
    1、对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug注意
    2、同步锁不建议随便使用一个唯一的对象,也能锁住,但可能影响无关线程, 建议使用共享资源作为锁对象
        对于实例方法建议使用this作为锁对象规范
        对于静态方法建议使用字码(类名.class) 对象作为锁对象
public class Account {
​
    //账户余额
    private Integer balance = 100000;
​
    //取钱
    public void drawMoney(Integer money) {
        //0.获取线程名称
        String threadName = Thread.currentThread().getName();
​
        synchronized (this){//排他互斥锁
      //this: 当前对象, 当前对象就是锁对象,这里是共享资源,即账户余额
            //1. 判断余额是否充足
            if (money > balance) {
                System.out.println(threadName + "取钱失败,余额不足");
                return;//方法结束
            }
​
            //2. 如果够,出钱
            System.out.println(Thread.currentThread().getName() + "取钱成功");
​
            //3. 更新余额
            balance -= money;
            System.out.println(Thread.currentThread().getName() + "取钱之后余额为:" + balance);
        }
​
    }
}
​
//取钱人
public class Person extends Thread {
​
    //账户
    private Account account;
    public Person(Account account) {
        this.account = account;
    }
​
    @Override
    public void run() {
        //调用取钱的方法
        account.drawMoney(100000);
    }
}
​
/*
测试类
*/
public class Demo {
    public static void main(String[] args) {
        //1. 创建一个账户对象
        Account account = new Account();
​
        //2. 创建两个取钱的人,并把账户交给它
        Person person1 = new Person(account);
        person1.setName("小明");
        Person person2 = new Person(account);
        person2.setName("小红");
​
        //3. 启动2个线程
        person1.start();
        person2.start();
    }
}

2.2 同步方法

同步方法
    把访问共享资源的核心方法给上锁,以此保证线程安全。
​
格式
    修饰符 synchronized 返回值类型 方法名称(形参列表) {
       操作共享资源的代码
    }
​
原理
    每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
    同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
    如果方法是实例方法:同步方法默认用this作为的锁对象。
    如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
public class Account {
​
    //账户余额
    private Integer balance = 100000;
​
    public Integer getBalance() {
        return balance;
    }
    //充值
    public synchronized void setBalance(Integer balance) {
        this.balance = this.balance + balance;
        System.out.println(Thread.currentThread().getName() + "充值之后余额为:" + balance);
    }
    //取钱
    public synchronized void drawMoney(Integer money) {
        //0.获取线程名称
        String threadName = Thread.currentThread().getName();
​
        //1. 判断余额是否充足
        if (money > balance) {
            System.out.println(threadName + "当前余额为:" + balance);
            System.out.println(threadName + "取钱失败,余额不足");
            return;//方法结束
        }
​
        //2. 如果够,出钱
        System.out.println(Thread.currentThread().getName() + "取钱成功");
​
        //3. 更新余额
        balance -= money;
        System.out.println(Thread.currentThread().getName() + "取钱之后余额为:" + balance);
    }
}
​
//取钱人
public class Person extends Thread {
​
    //账户
    private Account account;
    public Person(Account account) {
        this.account = account;
    }
​
    @Override
    public void run() {
        //调用取钱的方法
        account.drawMoney(100000);
        //调用取钱的方法
        account.setBalance(2000);
    }
}
​
/*
测试类
*/
public class Demo {
​
    public static void main(String[] args) {
        //1. 创建一个账户对象
        Account account = new Account();
​
        //2. 创建两个取钱的人,并把账户交给它
        Person person1 = new Person(account);
        Person person2 = new Person(account);
​
        //3. 启动2个线程
        person1.start();
        person2.start();
​
    }
}

是同步代码块好还是同步方法好一点?

范围上:同步代码块锁的范围更小,同步方法锁的范围更大。

可读性:同步方法更好。

2.3 Lock锁

Lock锁概述
    Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大
    Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建锁对象

方法
    public ReentrantLock() 创建锁对象
    public void lock()    上锁
    public void unlock() 释放锁

Lock锁使用规范
    规范1、锁对象创建在成员位置,使用final修饰
    规范2、释放锁的代码写在finally块中
构造器说明
public ReentrantLock()获得Lock锁的实现类对象
Lock常用方法名称说明
void lock()获得锁
void unlock()释放锁
public class Account {

    //账户余额
    private Integer balance = 100000;

    //创建锁对象
   private Lock lock =  new ReentrantLock();
    //取钱
    public void drawMoney(Integer money) {

        //0.获取线程名称
        String threadName = Thread.currentThread().getName();

        //1.上锁
        lock.lock();
        try {
            //2.判断余额是否充足
            //2.1 判断余额是否充足
            if (money > balance) {
                System.out.println(threadName + "取钱失败,余额不足");
                return;//方法结束
            }

            //2.2 如果够,出钱
            System.out.println(Thread.currentThread().getName() + "取钱成功");

            //2.3 更新余额
            balance -= money;
            System.out.println(Thread.currentThread().getName() + "取钱之后余额为:" + balance);
        } catch (Exception e){
            e.printStackTrace();
        }finally {
            //3.释放锁
            lock.unlock();
        }

    }
}

//取钱人
public class Person extends Thread {

    //账户
    private Account account;
    public Person(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        //调用取钱的方法
        account.drawMoney(100000);
    }
}

/*
测试类
*/
public class Demo {

    public static void main(String[] args) {
        //1. 创建一个账户对象
        Account account = new Account();

        //2. 创建两个取钱的人,并把账户交给它
        Person person1 = new Person(account);
        Person person2 = new Person(account);
            // 设置线程的名称
        person1.setName("张三");
        person2.setName("李四");

        //3. 启动2个线程
        person1.start();
        person2.start();

    }
}

三种线程同步方式的对比

同步代码块同步方法lock
语法synchronized (this){ }synchronized 方法(){ }lock.lock(); lock.unlock();
加锁方式自动加锁、释放锁自动加锁、释放锁手动加锁、释放锁
锁粒度代码行方法代码行

三、线程池

3.1 认识线程池

线程池就是一个可以复用线程的技术

它就像一个大的池子一样,里面可以放置一些线程,当需要的时候,就从里面取出来用,用完了就还回去

如此一来,就不必频繁的创建和销毁线程了,大大的提高了线程的利用率,提供系统的性能

3.2 线程池的执行流程

线程池创建后,内部没有线程,当第一个任务提交后,线程工程就创建线程,

1.判断核心线程是否已满,如果未满,则创建一个新的核心线程来执行任务

2.如果核心线程满了,则判断工作队列是否已满,如果没满,则将任务存储到这个工作队列 3.如果工作队列满了,则判断最大线程数是否已满,如果没满,则创建临时线程执行任务

4.如果最大线程已满,则执行拒绝策略 

3.3 创建线程池

JDK5.0起提供了代表线程池的接口:ExecutorService
	ExecutorService接口---ThreadPoolExecutor实现类

 

任务缓冲队列

队列详解
ArrayBlockingQueue基于数组的有界缓存等待队列,可以指定缓存队列的大小
LinkedBlockingQueue基于链表的无界阻塞队列,此时最大线程数无效

任务拒绝策略

策略详解
ThreadPoolExecutor.AbortPolicy丢弃任务并抛出RejectedExecutionException异常。是默认的策略
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy由主线程负责调用任务的run()方法从而绕过线程池直接执行

3.4 线程池处理Runnable任务

线程池如何处理Runnable任务:
	使用ExecutorService的方法:
		void  execute(Runnable target)

public class Demo2 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                3,// 核心线程数量
                5,// 最大线程数量
                10,// 临时线程的存活时间
                TimeUnit.SECONDS,// 存活时间单位
                new ArrayBlockingQueue(5),// 等待队列
                Executors.defaultThreadFactory(),// 线程工厂
                new ThreadPoolExecutor.AbortPolicy()
        );
        System.out.println(threadPoolExecutor);
        //提交多次任务
        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                    System.out.println("执行任务");
                }
            });
        }
        System.out.println(threadPoolExecutor);

    }
}

3.5 线程池处理Callable任务

线程池如何处理Callable任务,并得到任务执行完后返回的结果?
		使用ExecutorService的方法:
			Future<T> submit(Callable<T> command)

public class Demo3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
                3,// 核心线程数量
                5,// 最大线程数量
                10,// 临时线程的存活时间
                TimeUnit.SECONDS,// 存活时间单位
                new ArrayBlockingQueue(5),// 等待队列
                Executors.defaultThreadFactory(),// 线程工厂
                new ThreadPoolExecutor.AbortPolicy()
        );
        System.out.println(poolExecutor);
        //执行自己的任务
        SumTask sumTask1 = new SumTask(5);
        Future<Integer> submit1 = poolExecutor.submit(sumTask1);// 返回未来任务对象,用于获取线程返回的结果
        Integer sum1 = submit1.get();// 获取线程返回的结果
        System.out.println(sum1);

        SumTask sumTask2 = new SumTask(10);
        Future<Integer> submit2 = poolExecutor.submit(sumTask2);
        Integer sum2 = submit2.get();
        System.out.println(sum2);

        //关闭线程池
        List<Runnable> runnables = poolExecutor.shutdownNow();// 立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务
        System.out.println(runnables);// 返回未执行的任务
    }
}

//需求: 编写一个任务类, 可以通过构造器接收n, 计算并返回1~n的和
class SumTask implements Callable<Integer> {
    private int n;
    public SumTask(int n) {// 有参构造
        this.n = n;
    }

    /**
     * 计算1-n的和
     * @return
     * @throws Exception
     */
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return sum;
    }
}

3.6 Executors工具类实现线程池

 

Executors工具类底层是基于什么方式实现的线程池对象?

线程池ExecutorService的实现类:ThreadPoolExecutor

Executors是否适合做大型互联网场景的线程池方案?

不合适。Executors指定了线程的参数,不能自己设置,而且设置的上限很大,可能会导致OOM。

建议使用ThreadPoolExecutor来指定线程池参数,这样可以明确线程池的运行规则

四、线程通信(了解)

线程通信:
	当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。

Object类的等待和唤醒方法(这些方法应该使用当前同步锁对象进行调用)

方法名称说明
void wait()让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法
void notify()唤醒正在等待的单个线程
void notifyAll()唤醒正在等待的所有线程

4.1 进程与线程

进程:正在运行的程序(软件)就是一个独立的进程
线程:线程是属于进程的,一个进程中可以同时运行很多个线程
关系:进程=火车     线程=车厢

4.2 并发与并行

并发:
	 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全	部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我	们的感觉这些线程在同时执行,这就是并发
并行:
	在同一个时刻上,同时有多个线程在被CPU调度执行。

4.3 线程的生命周期和状态

public class Thread{
     ...     
     public enum State {    	
     	NEW, 线程刚被创建,但是并未启动
		RUNNABLE, 线程已经调用了start(),等待CPU调度    	
		BLOCKED, 线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态   				WAITING, 一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法		才能够唤醒
		TIMED_WAITING, 同waiting状态,有几个方法(sleep,wait)有超时参数,调用他们将进入Timed Waiting状态
        TERMINATED; 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
     }
     ...
}

 

新建一个线程,正常进入就绪态,获取CPU时间片就会被执行,执行完就会结束, 而在执行期间若获取锁失败就会进入阻塞态,重新获取锁成功进入就绪态, 执行期间如调用wait进入等待状态,被notify唤醒进入就绪态, 执行器若调用sleep进入计时等待,时间到进入等待状态。

标签:account,void,Person,线程,day11,new,多线程,public
From: https://blog.csdn.net/qq_58478983/article/details/142186657

相关文章

  • Linux环境C语言pthread多线程
    pthread线程库介绍pthread库是POSIX线程(PortableOperatingSystemInterfaceforuniXthreads)库的简称,它提供了一套创建和管理线程、以及线程间同步的机制。pthread库是UNIX系统上实现多线程编程的一个标准接口,也被广泛支持在类UNIX系统(Linux和macOS)中。头文件#in......
  • 【每日刷题】Day119
    【每日刷题】Day119......
  • 视频监控推流助手/极低延迟/支持N路批量多线程推流/264和265推流/监控转网页
    一、前言说明搞视频监控开发除了基本的拉流以外,还有个需求是推流,需要将拉到的流重新推流到流媒体服务器,让流媒体服务做转发和负载均衡,这样其他地方只需要问流媒体服务器要视频流即可。为什么拉了又重新推呢,因为软件这边和可能拉流后做了处理,比如做了人工智能运算,识别到了物体方框......
  • java使用多线程
    importjava.util.concurrent.TimeUnit;importcn.hutool.core.thread.ExecutorBuilder;importcn.hutool.core.thread.ThreadFactoryBuilder;//构造多线程,可修改线程数ExecutorServiceexecutorService=ExecutorBuilder.create().setCorePoolSize(5)//初始线程......
  • C++复习day11
    类型转化C语言中的类型转换在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。隐式类型转化:编译器在编译阶段自动进行,能转就转,......
  • 说下Python中的各个多线程模块之间的区别
    在Python中,涉及多线程的主要模块有threading、thread(在Python2.x中使用)和concurrent.futures。以下是这些模块之间的详细区别:1.threading模块简介:threading是Python的标准库之一,提供了创建和管理线程的高级接口。特点:线程类:提供Thread类,用户可以通......
  • Python中的 GIL是什么?它如何影响多线程?
    GIL(GlobalInterpreterLock)GIL(全局解释器锁)是Python解释器(特别是CPython实现)中的一个机制,用于管理对Python对象的访问。由于Python的内存管理不是线程安全的,GIL确保在任意时刻只有一个线程可以执行Python字节码,从而避免了多个线程同时访问和修改对象造成的数据不一致......
  • 二、并发编程与多线程-2.2、多线程(下)
    2.2、多线程(下)2.2.9、线程池是如何实现线程复用的?答:线程池采用了生产者-消费者模型来实现线程复用。提交任务到线程池里的线程被称为生产者线程,它不断往线程池里传递任务,这些任务会被保存到线程池的阻塞队列里。然后线程池里的工作线程会不断从阻塞队列中获取任务并执行......
  • 并发编程 - NSOperation&NSOperationQueue(多线程)
    ​​​​​​​并发编程-概述-CSDN博客并发编程-GCD的任务和队列-CSDN博客并发编程-NSOperation&NSOperationQueue(多线程)-CSDN博客并发编程-NSThread-CSDN博客引言在上篇博客中我们首先介绍了GCD的多线程方案,NSOperation和NSOperationQueue是Apple为我们提供的......
  • 【高级编程】认识Java多线程 代码举例三种创建线程的方式
    文章目录主线程创建线程方式1:Thread方式2:Runnable方式3:Callable进程:应用程序的执行实例,有独立的内存空间和系统资源线程:CPU调度和分派的基本单位,进程中执行运算的最小单位,可完成一个独立的顺序控制流程多线程:如果在一个进程中同时运行了多个线程,用来完成不同的工......