首页 > 其他分享 >细说多线程,如何解决线程安全问题

细说多线程,如何解决线程安全问题

时间:2023-09-25 22:33:10浏览次数:65  
标签:细说 name Thread money System 线程 println 多线程

关于多线程,首先熟练分清楚线程和进程的关系:
进程:内存中正在运行的一个程序
线程:进程中的一个最小执行单元。一个进程最少得有一个线程(Java程序中一个请求就是一个线程)。
一、创建多线程

的方式有四种:
1.继承Thread类

1.定义一个子类继承Thread类,并重写run方法
2.创建Thread的子类对象
3.调用start方法启动线程(启动线程后,会自动执行run方法中的代码)
public class Test1 extends Thread {
    //2.必须重写Thread类的run方法
    @Override
    public void run() {
//        super.run();
        //描述线程的执行任务
        //任务体
        for (int i = 0; i <= 5; i++) {
            System.out.println("子线程MyThread输出:"+i);
        }
    }
 public static void main(String[] args) {
        //创建线程类的对象代表一个线程
        Test1 test1 = new Test1();
        //启动线程(自动执行run方法)
        test1.start();
        for (int i = 0; i <= 5; i++) {
            System.out.println("主线程MyThread输出:"+i);
        }
    }
}

2.实现Runable接口

public static void main(String[] args) {
        Test21 test21 = new Test21();
        new Thread(test21).start();
        for (int i = 1; i <= 5; i++) {
            System.out.println("主线程main输出 ===》" + i);
        }
        //匿名内部类
         // 1、直接创建Runnable接口的匿名内部类形式(任务对象)
        Runnable target = new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.println("子线程1输出:" + i);
                }
            }
        };
        new Thread(target).start();

        // 简化形式1:
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 5; i++) {
                    System.out.println("子线程2输出:" + i);
                }
            }
        }).start();
    }

3.实现Callable接口

1.先定义一个Callable接口的实现类,重写call方法
2.创建Callable实现类的对象
3.创建FutureTask类的对象,将Callable对象传递给FutureTask
4.创建Thread对象,将Future对象传递给Thread
5.调用Thread的start()方法启动线程(启动后会自动执行call方法)
   等call()方法执行完之后,会自动将返回值结果封装到FutrueTask对象中
   
6.调用FutrueTask对的get()方法获取返回结果

public class MyCallable2 implements Callable<BigDecimal> {
    private int start1;
    private int end;
    public MyCallable2(int start1, int end) {
        this.start1 = start1;
        this.end = end;
    }
    @Override
    public BigDecimal call() throws Exception {
        BigDecimal sum= BigDecimal.valueOf(0);
        for (int i = start1; i <= end; i++) {
            sum=sum.add(BigDecimal.valueOf(i));
        }
        return sum;
    }
public static void main(String[] args) throws ExecutionException, InterruptedException {
        //3.创建一个Callable对象
        Callable<BigDecimal> c1 = new MyCallable2(1,2000);
        Callable<BigDecimal> c2 = new MyCallable2(2001,4000);
        //4.把Callable的对象封装成一个FutureTask对象(任务对象)
        //未来任务对象的作用?
        //4.1、是一个任务对象,实现了Runnable对象
        //4.2、可以在线程执行完毕之后,用未来任务调用get方法获取线程执行完毕后的结果。
        FutureTask<BigDecimal> bdft1 = new FutureTask<>(c1);
        FutureTask<BigDecimal> bdft2 = new FutureTask<>(c2);
        //5.把任务交给一个THread对象
        new Thread(bdft1).start();
        new Thread(bdft2).start();
        //6.获取线程执行完毕后返回的结果
        // 注意:如果执行到这儿,假如上面的线程还没有执行完毕
        // 这里的代码会暂停,等待上面线程执行完毕后才会获取结果。
        BigDecimal b1 = bdft1.get();
        BigDecimal b2 = bdft2.get();

        String s = b1.add(b2).toString();
        System.out.println("线程计算的累加和"+s);

4.创建线程池
线程池的核心参数
核心线程数,最大线程数,临时线程的存活时间,阻塞队列,线程工厂,任务丢弃策略(拒绝策略);
拒绝策略分为四种:
AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 (默认)
DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
CallerRunsPolicy:由调用线程处理该任务

 关于创建的线程池的工作执行流程,当有任务提交进来的时候,
(1)我们要判断线程池是否达到核心线程数最大了,没有达到,就利用核心线程来创建一个工作线程来执行任务。
(2)如果核心线程都在执行任务了,就判断工作队列是否都满了,没满则将新提交的任务存储在工作队列中。
(3)当队列满了,判断线程数是否达到最大线程,如果没有则创建新的线程来执行任务。
(4)如果达到最大线程数了,就执行拒绝策略。
(5)执行拒绝策略

线程状态

新建,就绪,运行,阻塞,死亡

图解释的很详细了。
***关于经常被问到的
1,线程池创建了,里面有线程吗?  没有
2、继承Thread类和实现Runnable接口,有什么区别?你一般选择哪个?为什么?
根据单一原则,选择实现Runable接口
3,实现Runable接口和实现Callable接口有什么区别?
Callable可以有返回结果

关于解决线程安全问题

在共享的环境中,线程往往是不安全的,解决多线程中的安全问题,我们一般就是加锁

Synchronized 在jvm层面,是关键字,出异常的时候会释放锁,不会出现死锁

不会手动释放锁,只能等同步代码块和方法结束的时候释放锁。

Lock 在API层面,是一个接口  ,出异常的时候不会释放锁,会出现死锁,需要在finally中手动释放,

可以调用api手动释放

 

 

//Lock加锁
public class Account {
    private String nameId;
    private double money;
//。。。构造器

//定义锁Lock
    private final Lock lk=new ReentrantLock();
    //对执行方法枷锁 用try-finally进行闭锁
    public void DrawMoney(double money){
        //Lock方法
        lk.lock();//加锁
        try {
            //谁来取钱
            String name = Thread.currentThread().getName();
            //现在的钱大于余额是
            if (this.money>=money){
                System.out.println(name+"来取钱"+money+"成功!");
                this.money-=money;
                System.out.println(name+"取钱后,余额还有"+this.money);
            }else {
                System.out.println(name+"来取钱:余额不足");
            }
        } finally {
        //关锁
            lk.unlock();
        }
        }
// 。。get、set方法
}

Synchronized

同步方法

public synchronized void DrawMoney(double money){
        //同步方法 在方法中直接加,沈括乃日,对象方法。synchronized
            //谁来取钱
            String name = Thread.currentThread().getName();
            //现在的钱大于余额是
            if (this.money>=money){
                System.out.println(name+"来取钱"+money+"成功!");
                this.money-=money;
                System.out.println(name+"取钱后,余额还有"+this.money);
            }else {
                System.out.println(name+"来取钱:余额不足");
            }
        }

同步代码块,锁是括号里面的对象【必须共享】

public  void DrawMoney(double money) {
//          synchronized (Account.class){//针对类
        synchronized (this) {
            //谁取钱
            String name = Thread.currentThread().getName();
            if (this.money >= money) {
                System.out.println("恭喜"+name + ",获得红包" + money + "元");
                this.money -= money;
//                System.out.println(name + "剩余" + this.money);
            } else {
                System.out.println("抱歉,"+name + ",红包不足");
            }
        }
    }

同步代码块在底层的执行原理其实就是

使用 monitorenter 和 monitorexit  指令实现的,一个执行,一个释放

关于锁的定义

 

标签:细说,name,Thread,money,System,线程,println,多线程
From: https://www.cnblogs.com/liu1234567890/p/17729033.html

相关文章

  • 多线程
    首先先说一下什么是进程什么事线程,进程就是在内存中正在运行的程序,线程是进程的最小执行单位,一个进程最少得有一个线程,线程是指软件中的每一个功能。    线程的创建方式有三种,继承Thread类,实现Runable接口,实现Callable接口,继承Thread类和实现Runable接口的区别在于,java是单......
  • 多线程
    学习多线程我们要先明白进程与线程进程就是在内存中正在运行的程序,就跟我们手机上一个个正在运行的软件一样.线程:线程是进程的最小执行单元,一个进程中最少拥有一个线程,线程就相当于手机软件中的一个个软件线程创建的方式(共四种)第一种是继承Thread类,重写run......
  • 多线程基本
    线程与进程得关系一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程线程则是计算机中最小执行单元.比如电脑管家这个软件就是一个进程而里面的杀毒清理垃圾全局守护这些功能就是一个个线程.一个进程最少拥有一个线程.线程的创建方......
  • WebRTC C++ 线程和线程管理剖析
    线程管理实例化代码(单例)ThreadManager*ThreadManager::Instance(){staticThreadManager*constthread_manager=newThreadManager();returnthread_manager;}初始化位置WebRTC中启动新线程的标准方法是通过创建Thread对象,然后调用Thread.Start()方法来启用......
  • 线程有哪些常用的调度方法?
    一、线程等待在Object类中有一些函数可以用于线程的等待:1.1wait()当一个线程A调用一个共享变量wait()方法时,线程A会被阻塞挂起,发生以下情况才会返回:(1)线程A调用了共享对象的notify()或者notifyAll()方法。(2)其它线程调用了线程A的interrupt()方法,线程A抛出InterruptedException异常......
  • HarmonyOS使用多线程并发能力开发
     一、多线程并发概述1、简介并发模型是用来实现不同应用场景中并发任务的编程模型,常见的并发模型分为基于内存共享的并发模型和基于消息通信的并发模型。Actor并发模型作为基于消息通信并发模型的典型代表,不需要开发者去面对锁带来的一系列复杂偶发的问题,同时并发度也相对......
  • 进程和线程之间区别
    进程和线程是计算机科学中重要的概念,用于描述程序执行的不同方式和组织形式。进程(Process):进程是程序的一次执行过程,是计算机系统分配资源的基本单位。每个进程拥有独立的内存空间,包括代码、数据、堆栈等,使得进程间的数据不共享,相互隔离。进程可以包含多个线程,共享该进程的......
  • 【技术研究】线程和进程
    有一句概念“进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。”分上下句去理解他吧,首先贴一张图所看到是一个进程的内存空间,分为一些栈区,堆区,一些资源区等等。“进程是操作系统资源分配的基本单位”,进程包含一个程序的执行实例,说的具体点就是他有......
  • Java中的线程池的线程数量如何确定?
    可能很多人都看到过一个线程数设置的理论:CPU密集型的程序-核心数+1I/O密集型的程序-核心数*2不会吧,不会吧,真的有人按照这个理论规划线程数?线程数和CPU利用率的小测试抛开一些操作系统,计算机原理不谈,说一个基本的理论(不用纠结是否严谨,只为好理解):一个CPU核心,单......
  • 线程一些常用功能总结
    c++11通常单例模式如下:template<typenameT>classSingleton{protected:Singleton()=default;Singleton(constSingleton<T>&)=delete;Singleton&operator=(constSingleton<T>&st)=delete;staticstd::shared_ptr&......