3分钟入门Java多线程

如何在程序中创建出多条线程?

  1. 继承Thread类
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("MyThread运行了" + i);
        }
    }
}
  1. 实现Runnable接口
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("MyRunnable运行了" + i);
        }
    }
}
  1. 利用Callable接口、FutureTask类来实现
public class MyCallable implements Callable<String> {
    @Override
    public String call() {
        return "base.MyCallable 运行了";
    }
}

测试类

public class TestThread {
    public static void main(String[] args) throws Exception {
        base.MyThread myThread = new base.MyThread();
        myThread.start();

        Runnable myRunnable = new base.MyRunnable();
        new Thread(myRunnable).start();

        Callable<String> myCallable = new MyCallable();
        FutureTask<String> futureTask = new FutureTask<>(myCallable);
        new Thread(futureTask).start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main运行了" + i);
        }
        // 获取线程执行结果
        // 如果说程序执行到这,但是myCallable里面的方法还没有执行完,这里会等待,等待方法结束拿到值。
        String s = futureTask.get();
        System.out.println(s);
    }
}

多线程常用方法

多线程常用方法

线程安全问题

首先,什么是线程安全问题呢?
线程安全问题指的是,多个线程同时操作同一个共享资源的时候,可能会出现业务安全问题。

下面通过一个取钱的案例给同学们演示一下。案例需求如下

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

如下图所示,小明和小红假设都是一个线程,本类每个线程都应该执行完三步操作,才算是完成的取钱的操作。但是真实执行过程可能是下面这样子的

​ ① 小红线程只执行了判断余额是否足够(条件为true),然后CPU的执行权就被小红线程抢走了。

​ ② 小红线程也执行了判断了余额是否足够(条件也是true), 然后CPU执行权又被小明线程抢走了。

​ ③ 小明线程由于刚才已经判断余额是否足够了,直接执行第2步,吐出了10万元钱,此时共享账户月为0。然后CPU执行权又被小红线程抢走。

​ ④ 小红线程由于刚刚也已经判断余额是否足够了,直接执行第2步,吐出了10万元钱,此时共享账户月为-10万。
在这里插入图片描述

线程安全问题的代码演示

先定义一个共享的账户类

public class Account {
    private String cardId; // 卡号
    private double money; // 余额。

    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    // 小明 小红同时过来的
    public void drawMoney(double money) {
        // 先搞清楚是谁来取钱?
        String name = Thread.currentThread().getName();
        // 1、判断余额是否足够
        if(this.money >= money){
            System.out.println(name + "来取钱" + money + "成功!");
            this.money -= money;
            System.out.println(name + "来取钱后,余额剩余:" + this.money);
        }else {
            System.out.println(name + "来取钱:余额不足~");
        }
    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}

在定义一个是取钱的线程类

public class DrawThread extends Thread{
    private Account acc;
    public DrawThread(Account acc, String name){
        super(name);
        this.acc = acc;
    }
    @Override
    public void run() {
        // 取钱(小明,小红)
        acc.drawMoney(100000);
    }
}

最后,再写一个测试类,在测试类中创建两个线程对象

public class ThreadTest {
    public static void main(String[] args) {
         // 1、创建一个账户对象,代表两个人的共享账户。
        Account acc = new Account("ICBC-110", 100000);
        // 2、创建两个线程,分别代表小明 小红,再去同一个账户对象中取钱10万。
        new DrawThread(acc, "小明").start(); // 小明
        new DrawThread(acc, "小红").start(); // 小红
    }
}

运行程序,执行后。你会发现两个人都取了10万块钱,余额为-10万了。

线程同步方案

为了解决前面的线程安全问题,我们可以使用线程同步思想。同步最常见的方案就是加锁,意思是每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动释放锁,然后其他线程才能再加锁进来。
采用加锁的方案,就可以解决前面两个线程都取10万块钱的问题。怎么加锁呢?Java提供了三种方案

1.同步代码块
2.同步方法
3.Lock锁

同步代码块

什么是同步代码块呢?
synchorized关键字修饰的代码块被称为同步代码块

//锁对象:必须是一个唯一的对象(同一个地址)
synchronized(锁对象){
    //...访问共享数据的代码...
}

同步代码块有什么作用呢?
它的作用就是把访问共享数据的代码锁起来,以此保证线程安全。
使用同步代码块,来解决前面代码里面的线程安全问题。我们只需要修改DrawThread类中的代码即可。

// 小明 小红线程同时过来的
public void drawMoney(double money) {
    // 先搞清楚是谁来取钱?
    String name = Thread.currentThread().getName();
    // 1、判断余额是否足够
    // this正好代表共享资源!
    synchronized (this) {
        if(this.money >= money){
            System.out.println(name + "来取钱" + money + "成功!");
            this.money -= money;
            System.out.println(name + "来取钱后,余额剩余:" + this.money);
        }else {
            System.out.println(name + "来取钱:余额不足~");
        }
    }
}

最后,再说一下锁对象如何选择的问题

1.建议把共享资源作为锁对象,不要将随便无关的对象当作锁对象
2.对于实例方法,默认使用this作为锁对象
3.对于静态方法,默认使用类的字节码(类名.class)当作锁对象

同步方法

接下来,学习同步方法解决线程安全问题。其实同步方法,就是把整个方法给锁住,一个线程调用这个方法,另一个线程调用的时候就执行不了,只有等上一个线程调用结束,下一个线程调用才能继续执行。

// 同步方法
public synchronized void drawMoney(double money) {
    // 先搞清楚是谁来取钱?
    String name = Thread.currentThread().getName();
    // 1、判断余额是否足够
    if(this.money >= money){
        System.out.println(name + "来取钱" + money + "成功!");
        this.money -= money;
        System.out.println(name + "来取钱后,余额剩余:" + this.money);
    }else {
        System.out.println(name + "来取钱:余额不足~");
    }
}

Lock锁

Lock锁是JDK5版本专门提供的一种锁对象,通过这个锁对象的方法来达到加锁,和释放锁的目的,使用起来更加灵活。格式如下:

1.首先在成员变量位子,需要创建一个Lock接口的实现类对象(这个对象就是锁对象)
	private final Lock lk = new ReentrantLock();
2.在需要上锁的地方加入下面的代码
	 lk.lock(); // 加锁
	 //...中间是被锁住的代码...
	 lk.unlock(); // 解锁

使用Lock锁改写前面DrawThread中取钱的方法,代码如下:

// 创建了一个锁对象
private final Lock lk = new ReentrantLock();

public void drawMoney(double money) {
        // 先搞清楚是谁来取钱?
        String name = Thread.currentThread().getName();
        try {
            lk.lock(); // 加锁
            // 1、判断余额是否足够
            if(this.money >= money){
                System.out.println(name + "来取钱" + money + "成功!");
                this.money -= money;
                System.out.println(name + "来取钱后,余额剩余:" + this.money);
            }else {
                System.out.println(name + "来取钱:余额不足~");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lk.unlock(); // 解锁
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/580528.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

js的算法-插入排序(折半插入排序)

直接插入排序的步骤 1. 从前面的有序子表中查找出待插入元素应该被插入的位置 2. 给插入位置腾空间 3. 将待插入元素复制到表中的插入位置。 直接插入排序&#xff1a;边比较边移动&#xff1b; 折半插入排序 先折半查找出元素的待插入位置&#xff0c;然后统一地移动待插…

Windows系统中下Oracle 19C数据库超级详细安装、设置教程(自己电脑上安装Oracle学习,保姆级教学,亲测有效)

Oracle 官方提供了一个基于 Java 技术的图形界面安装工具&#xff1a;Oracle Universal Installer&#xff08;Oracle 通用安装器&#xff09;简称 OUI&#xff0c;利用它可以完成在不同操作系统平台上&#xff08;Windows、Linux、UNIX&#xff09;的、不同类型的、不同版本的…

kotlin 编写一个简单的天气预报app (七)使用material design

一、优化思路 对之前的天气预报的app进行了优化&#xff0c;原先的天气预报程序逻辑是这样的。 使用text和button组合了一个输入城市&#xff0c;并请求openweathermap对应数据&#xff0c;并显示的功能。 但是搜索城市的时候&#xff0c;可能会有错误&#xff0c;比如大小写…

Java设计模式 _结构型模式_过滤器模式

一、过滤器模式 1、过滤器模式 过滤器模式&#xff08;Filter Pattern&#xff09;是这一种结构型设计模式。过滤器&#xff0c;顾名思义&#xff0c;就是对一组数据进行过滤&#xff0c;从而最终获取到我们预期的数据。 2、实现思路 &#xff08;1&#xff09;、定义过滤器的…

解决问题:Canal客户端覆盖服务端Subscribe,只有TRANSACTIONBEGIN和TRANSACTIONEND日志,没有ROWDATA日志的问题

一&#xff0c;背景 在整合canal和Spring时&#xff0c;本地使用canal的subscribe方法订阅了需要监听的表&#xff0c;但是获得只有transactionbegin和transactionend两种eventType的日志&#xff0c; 没有rowdata类型的日志&#xff0c;导致无法完成监听数据库数据更新的需求…

提示词优化的自动化探索:Automated Prompt Engineering

编者按&#xff1a; 作者在尝试教授母亲使用 LLM 完成工作任务时&#xff0c;意识到提示词的优化并不像想象中简单。提示词的自动优化对于经验并不丰富的提示词撰写者很有价值&#xff0c;他们没有足够的经验去调整和改进提供给模型的提示词&#xff0c;这引发了对自动化提示词…

C++—DAY2

定义一个矩形类Rec&#xff0c;包含私有属性length&#xff0c;width&#xff0c;有以下成员函数: void set length(int l);//设置长度 void set width(int w); //设置宽度 int get length(); //获取长度 int get_width(); //获取宽度 void show(); //输出…

可见水印去除算法简介

去水印技术简介 进入二十一世纪以来&#xff0c;随着互联网技术和电子技术的飞速发展和进步&#xff0c;电子设备比如智能手机、iPad、个人计算机和智能穿戴设备等的大规模普及使用&#xff0c;各种文字、图像、视频及音频等数据信息借助于互联网实现了人们之间远距离的信息传…

kernel32.dll文件丢失的原因以及相对应的解决办法分享

kernel32.dll丢失是电脑中一个重要的文件&#xff0c;其实想要修复kernel32.dll文件的方法比较简单&#xff0c;今天就和大家说说如何去修复kernel32.dll文件。导致kernel32.dll文件丢失的原因又是什么&#xff1f;一起开看看吧。 kernel32.dll的作用 kernel32.dll是一个重要的…

IntelliJ IDEA 如何启用 JDK 预览特性

IntelliJ IDEA 也可以启用 JDK 的预览特性。 针对项目&#xff0c;选择项目结构。 配置是在语言结构上。 单击语言结构上的 SDK 默认&#xff0c;往下拉&#xff0c;就可以看到针对新版本的选项。 同时还可以看到那些版本是支持新特性预览的&#xff0c;那些版本是不支持新特…

Oracle 19c OCM考试难度如何?

许多人对 Oracle 19c OCM 的考试规则并不熟悉&#xff0c;本文将详细介绍考证所需条件以及具体要求&#xff0c;以帮助大家更顺利地完成考试流程。 首先&#xff0c;考生需具备相匹配的同级别 OCP 证书&#xff0c;如已获得 10g/11g/12c 证书者&#xff0c;则须先完成 083 升级…

UE5 GAS开发P41-43 永久效果,去除永久效果,伤害区域,EnumClass,开始重叠与结束重叠事件

这一部分学习了怎么创建一个伤害性的地形(火焰地形,毒沼泽等都可以用这个方式创建) AuraEffectActor.h // Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h" #include "GameplayEffect.h&q…

【navicat】oracle library is not loaded 问题复现和解决方案

问题原因&#xff1a;客户端oci版本安装错误&#xff0c;navicat需要64位的oci,但是使用32位的oci。 解决方案&#xff1a;官网下载64位oci进行配置。本次演示的解决多了splplus&#xff0c;其实不必要安装也能运行。 首先判断是否数据库已经打开 尝试使用splplus连接数据库 1…

GDPU 算法分析与设计 天码行空5

一、【实验目的】 &#xff08;1&#xff09;熟悉动态规划算法的基本思想. &#xff08;2&#xff09;理解动态规划算法中子问题的划分和递推方程设计的基本方法. &#xff08;3&#xff09;熟悉矩阵链乘法的基本思想并编程实现。 二、【实验内容】 输入:矩阵链Ai…j的输入为…

美国站群服务器的国际网络环境在全球的影响力?

美国站群服务器的国际网络环境在全球的影响力? 美国站群服务器如何通过其技术优势和网络基础设施&#xff0c;塑造国际网络环境并对全球产生影响力? 在当今数字化时代&#xff0c;美国站群服务器在国际网络环境中扮演着至关重要的角色。作为全球互联网发展的领导者之一&…

在Windows 11中NotePad3的安装和配置详细教程

&#x1f4dd; 在Windows 11中NotePad3的安装和配置详细教程 文章目录 &#x1f4dd; 在Windows 11中NotePad3的安装和配置详细教程摘要引言正文1. NotePad3简介 &#x1f4d8;2. 安装前的准备工作 &#x1f6e0;️ 我已经给大家准备了一份安装包&#xff0c;微信搜索公众号&am…

K8S 部署和访问 Kubernetes 仪表板(Dashboard)

文章目录 部署 Dashboard UI浏览器访问登陆系统 Dashboard 是基于网页的 Kubernetes 用户界面。 你可以使用 Dashboard 将容器应用部署到 Kubernetes 集群中&#xff0c;也可以对容器应用排错&#xff0c;还能管理集群资源。 你可以使用 Dashboard 获取运行在集群中的应用的概览…

推荐一款国内超级好用的低代码平台+商业开源低代码MES

一、低代码平台是什么&#xff1f; 低代码平台是一种应用程序&#xff0c;它为编程提供图形用户界面&#xff0c;从而以极快的速度开发代码&#xff0c;减少传统编程工作。 这些工具有助于快速开发代码&#xff0c;最大限度地减少手工编码的工作量。这些平台不仅有助于编码&a…

网络通信安全

一、网络通信安全基础 TCP/IP协议简介 TCP/IP体系结构、以太网、Internet地址、端口 TCP/IP协议简介如下&#xff1a;&#xff08;from文心一言&#xff09; TCP/IP&#xff08;Transmission Control Protocol/Internet Protocol&#xff0c;传输控制协议/网际协议&#xff0…

基础环境:wsl2安装Ubuntu22.04 + miniconda

服务器相关信息&#xff1a; Thinkpad p1 gen5 64G 2T 3080ti&#xff0c;自带的有nvidia-smi显卡驱动。使用wsl2安装Ubuntu22.04 miniconda目标&#xff1a;安装gpu版本的PyTorch2.1.2&#xff08;torch2.1.2/cu117 torchvision0.16.2/cu117&#xff09; 处理器 12th Gen I…