【JAVA进阶】多线程

news2024/11/27 22:30:23

📃个人主页:个人主页

🔥系列专栏:JAVASE基础

前言:

什么是线程?

线程(thread)是一个程序内部的一条执行路径。

我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径。

public class ThreadTest {
    public static void main(String[] args) {

        for (int i = 0; i <= 5; i++) {
            System.out.println("主线程Main输出"+i);
        }
        

    }

}

程序中如果只有一条执行路径,那么这个程序就是单线程的程序。

多线程是什么?

多线程是指从软硬件上实现多条执行流程的技术。

多线程用在哪里,有什么好处

再例如:消息通信、淘宝、京东系统都离不开多线程技术。

一、多线程的创建

方式一:继承Thread类

Java是通过java.lang.Thread 类来代表线程的。

按照面向对象的思想,Thread类应该提供了实现多线程的方式。

①定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <= 5; i++) {
            System.out.println("子线程MyThread输出"+i);
        }
    }
}

②创建MyThread类的对象

③调用线程对象的start()方法启动线程(启动后还是执行run方法的)

public class ThreadTest {
    public static void main(String[] args) {
        Thread t=new MyThread();
        t.start();

        for (int i = 0; i <= 5; i++) {
            System.out.println("主线程Main输出"+i);
        }


    }

}

方式一优缺点: 

优点:编码简单

缺点:线程类已经继承Thread,无法继承其他类,不利于扩展。

1、为什么不直接调用了run方法,而是调用start启动线程。

直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。 只有调用start方法才是启动一个新的线程执行。

2、把主线程任务放在子线程之前了。

这样主线程一直是先跑完的,相当于是一个单线程的效果了。

方式二:实现Runnable接口 

①定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 5; i++) {
            System.out.println("子线程Runnable输出"+i);
        }
    }
}

②创建MyRunnable任务对象

③把MyRunnable任务对象交给Thread处理。

④调用线程对象的start()方法启动线程

public class ThreadTest2 {
    public static void main(String[] args) {


        Runnable target=new MyRunnable();
        new Thread(target).start();

        for (int i = 0; i <= 5; i++) {
            System.out.println("主线程出"+i);
        }

    }
}

Thread的构造器 

方式二优缺点:

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。

缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。

多线程的实现方案二:实现Runnable接口(匿名内部类形式)

public class ThreadTest3 {
    public static void main(String[] args) {


        new Thread(()->{
                for (int i = 0; i <= 5; i++) {
                    System.out.println("子线程Runnable输出"+i);
                }
            }
        ).start();

        for (int i = 0; i <= 5; i++) {
            System.out.println("主线程出"+i);
        }

    }
}

方式三:JDK 5.0新增:实现Callable接口 

1、前2种线程创建方式都存在一个问题:

  • 他们重写的run方法均不能直接返回结果。
  • 不适合需要返回线程执行结果的业务场景。

2、怎么解决这个问题呢?

  • JDK 5.0提供了Callable和FutureTask来实现。
  • 这种方式的优点是:可以得到线程执行的结果。

多线程的实现方案三:利用Callable、FutureTask接口实现。

①得到任务对象

  • 定义类实现Callable接口,重写call方法,封装要做的事情。
import java.util.concurrent.Callable;

public class MyCallable implements Callable{


    private int n=0;

    public MyCallable(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum=0;

        for (int i = 0; i <=n; i++) {
            sum+=i;
        }

        return "子线程求和:"+sum;
    }
}
  • 用FutureTask把Callable对象封装成线程任务对象。
        MyCallable myCallable = new MyCallable(101);
        FutureTask<String> task = new FutureTask<String>(myCallable);

②把线程任务对象交给Thread处理。

③调用Thread的start方法启动线程,执行任务

④线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果。

public class ThreadTest4 {
    public static void main(String[] args) throws Exception {
        MyCallable myCallable = new MyCallable(101);
        FutureTask<String> task = new FutureTask<String>(myCallable);
        new Thread(task).start();
        System.out.println(task.get());

    }
}

FutureTask的API

 方式三优缺点:

  • 优点:
  • 线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。
  • 可以在线程执行完毕后去获取线程执行的结果。

  • 缺点:编码复杂一点。

二、Thread的常用方法

Thread常用API说明

  • Thread常用方法:获取线程名称getName()、设置名称setName()、获取当前线程对象currentThread()。
  • 至于Thread类提供的诸如:yield、join、interrupt、不推荐的方法 stop 、守护线程、线程优先级等线程的控制方法,在开发中很少使用,这些方法会在高级篇以及后续需要用到的时候再为大家讲解。 

 注意:

1、此方法是Thread类的静态方法,可以直接使用Thread类调用。  

2、这个方法是在哪个线程执行中调用的,就会得到哪个线程对象。

 ·

public class ThreadTest5 {
    public static void main(String[] args) {


        Thread thread1 = new MyThread("子线程1");//Thread-0
        thread1.start();
        System.out.println(thread1.getName());


        Thread thread2 = new MyThread("子线程2");//Thread-1
        thread2.start();

        System.out.println(thread2.getName());

        Thread thread = Thread.currentThread();//main
        thread.setName("主线程");
        System.out.println(thread.getName());

        for (int i = 0; i <= 5; i++) {
            System.out.println(thread.getName()+"输出:"+i);
        }


    }
}
public class MyThread extends Thread {

    public MyThread(String name) {
        super(name);
    }

    public MyThread() {
    }

    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        for (int i = 0; i <= 5; i++) {
            System.out.println("子线程"+thread.getName()+"输出:"+i);
        }
    }
}

在Java中,Thread.sleep()方法用于使当前线程暂停执行指定的时间。这个方法通常用于实现多线程之间的协作,例如等待其他线程完成某些操作。

Thread.sleep()方法接受一个表示毫秒数的参数,表示当前线程应该暂停执行的时间。例如,以下代码将使当前线程暂停5秒钟:

try {  
    Thread.sleep(5000); // 5000毫秒 = 5秒  
} catch (InterruptedException e) {  
    // 处理中断异常  
}

当线程调用Thread.sleep()方法时,它将被阻塞,直到指定的时间过去。在阻塞期间,线程不会执行任何代码,也不会消耗CPU资源。但是,需要注意的是,Thread.sleep()方法可能会抛出InterruptedException异常,因此需要在调用时捕获该异常。

在使用Thread.sleep()方法时,需要注意以下几点:

  1. Thread.sleep()方法不会释放任何锁资源,如果当前线程持有锁,则其他线程无法访问被锁定的资源。
  2. Thread.sleep()方法的参数是一个整数,表示毫秒数。如果需要暂停更长的时间,可以考虑使用TimeUnit类来避免计算错误。
  3. 在使用Thread.sleep()方法时,需要注意线程安全问题。如果多个线程同时访问共享资源,可能会导致竞争条件。为了避免这种情况,可以考虑使用synchronized关键字或其他同步机制来确保线程安全。

在Java中,Thread.join()方法用于等待该线程终止。在调用join()方法时,当前线程将被阻塞,直到该线程终止。这通常用于确保在主线程中执行某些操作之前,其他线程已经完成它们的任务。

例如,假设有两个线程A和B,线程B必须在线程A完成后才能开始执行。在这种情况下,可以使用join()方法来实现同步:

ThreadA.start();  
ThreadA.join(); // 等待ThreadA终止  
ThreadB.start();

这将确保线程B在线程A终止之前不会开始执行。

需要注意的是,join()方法可能会抛出InterruptedException异常,因此需要在调用时捕获该异常。

三、线程安全

线程安全问题是指在多线程环境下,多个线程同时访问和修改共享数据时可能导致的问题。

取钱模型演示:

需求:有一对夫妻,他们有一个共同的账户,余额是一千元。

如果2人同时来取钱,而且2人都要取600元,可能出现什么问题呢?

class BankAccount {
    private double balance;

    public BankAccount(double balance) {
        this.balance = balance;
    }

    public  void withdraw(double amount){
        if (balance >= amount) {
            System.out.println(Thread.currentThread().getName() + "来取钱" + amount );
            balance -= amount;
            System.out.println(Thread.currentThread().getName() + "成功取出" + amount + "元,当前余额为:" + balance);

        } else {
            System.out.println(Thread.currentThread().getName() + "取款失败,余额不足!");
        }
    }
}

class WithdrawThread extends Thread {
    private BankAccount account;
    private double amount;

    public WithdrawThread(BankAccount account, double amount) {
        this.account = account;
        this.amount = amount;
    }

    @Override
    public void run() {

            account.withdraw(amount);

    }
}

public class WithdrawDemo {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);
        WithdrawThread thread1 = new WithdrawThread(account, 600);
        WithdrawThread thread2 = new WithdrawThread(account, 600);

        thread1.start();

        thread2.start();

    }
}

结果:2人都取钱600,银行亏了200。 

四、线程同步

同步思想概述

取钱案例出现问题的原因?

多个线程同时执行,发现账户都是够钱的。

如何才能保证线程安全呢?

让多个线程实现先后依次访问共享资源,这样就解决了安全问题

线程同步的核心思想

加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。

方式一:同步代码块

作用:把出现线程安全问题的核心代码给上锁。

原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。

在Java中,可以使用synchronized关键字定义一个同步代码块。这个关键字可以与任何对象一起使用,而这个对象就被当作锁对象。当一个线程进入同步代码块,它会锁住这个锁对象,直到它离开这个代码块时才会释放这个锁。在这个线程持有锁的期间,其他任何尝试获取这个锁的线程都会被阻塞,直到锁被释放。

以下是Java中使用synchronized关键字的一个例子:

public class MyClass {  
    private Object lock = new Object();  
  
    public void myMethod() {  
        synchronized(lock) {  
            // 在这里的代码只能由一个线程同时执行  
            // 对共享数据的访问和操作都在这里进行  
        }  
    }  
}

 用在上述取钱案例中:

    public  void withdraw(double amount){
        synchronized (this){
            if (balance >= amount) {
                System.out.println(Thread.currentThread().getName() + "来取钱" + amount );
                balance -= amount;
                System.out.println(Thread.currentThread().getName() + "成功取出" + amount + "元,当前余额为:" + balance);

            } else {
                System.out.println(Thread.currentThread().getName() + "取款失败,余额不足!");
            }

        }

    }

同步代码块的同步锁对象有什么要求?     

  • 对于实例方法建议使用this作为锁对象。
  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象。

方式二:同步方法

作用:把出现线程安全问题的核心方法给上锁。

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

同步方法是指在多线程编程中,使用synchronized关键字修饰的方法。它可以确保在任何时候只有一个线程可以访问该方法,从而避免多线程并发操作导致的数据不一致和其他问题。

在Java中,定义同步方法有两种方式:

1.在方法声明中使用synchronized关键字,例如:

public synchronized void myMethod() {  
    // 在这里的代码只能由一个线程同时执行  
    // 对共享数据的访问和操作都在这里进行  
}

2.使用静态synchronized方法,在方法名前加上static关键字,例如:

public static synchronized void myMethod() {  
    // 在这里的代码只能由一个线程同时执行  
    // 对共享数据的访问和操作都在这里进行  
}

静态同步方法只能同步静态方法,而不能同步非静态方法。和非静态同步方法一样,静态同步方法也可以用锁来控制多线程的访问,避免并发问题。需要注意的是,静态同步方法的锁对象和实例对象的锁对象是不同的。如果一个类中有多个静态同步方法,它们之间共享的锁对象是同一个,而不同实例对象的锁对象是不同的。

同步方法底层原理

  • 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
  • 如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!
  • 如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

方式三:Lock锁

Lock锁是Java中用于控制多个线程对共享资源访问的工具。与synchronized方法和语句相比,Lock锁提供了更广泛的锁定操作和更灵活的结构。Lock锁允许完全不同的属性,并且可能支持多个关联的Condition对象。

Lock锁接口的实现允许在不同范围内获取和释放锁,并允许以任何顺序获取和释放多个锁,从而允许使用此类技术。 Lock锁的底层实现有多种方式,例如ReentrantLock和ReentrantReadWriteLock,其中ReentrantLock是可重入的,允许线程在完成任务后继续占用锁,直到锁的变量为0。

使用Lock锁时,需要注意以下几点:

  1. Lock锁的使用和释放应该手动进行,比synchronized更加灵活。
  2. Lock锁可以中断获取锁和超时获取锁。
  3. Lock锁可以支持公平和非公平锁,其中公平锁按照线程请求锁的顺序分配,而非公平锁则允许其他线程插队。
  4. Lock锁的使用可能会带来额外的责任,例如需要处理死锁等问题。
  5. Lock锁的实现可以提供与隐式监视器锁完全不同的行为和语义,例如保证排序、不可重入使用或死锁检测等。
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  
  
public class Counter {  
    private int count = 0;  
    private Lock lock = new ReentrantLock();  
  
    public void increment() {  
        lock.lock(); // 获取锁  
        try {  
            count++;  
        } finally {  
            lock.unlock(); // 释放锁  
        }  
    }  
  
    public int getCount() {  
        return count;  
    }  
}

在这个例子中,我们使用了ReentrantLock,它是一种可重入的互斥锁。我们用它来保护对count的并发访问,这样多个线程就不会同时修改它。我们在increment()方法中获取和释放锁,确保在这个方法中的代码块在任何时候只能由一个线程执行。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用HHDBCS管理Redis

Redis是一款内存高速缓存数据库&#xff0c;可用于缓存&#xff0c;事件发布或订阅&#xff0c;高速队列等场景。 因此&#xff0c;根据需要&#xff0c;HHDBCS在主页设置了“发布窗口”及“订阅窗口”。 1 连接redis 打开HHDBCS&#xff0c;在数据库类型中选择Redis&#…

MATLAB实战 | 粮食储仓的通风控制问题

粮食储仓的通风控制问题 01、应用实战 【例1】粮食储仓的通风控制问题。在粮食储备中&#xff0c;合适的湿度是保证粮食质量的前提。一般来说&#xff0c;若粮食水分的吸收和蒸发量相等&#xff0c;这个湿度称为平衡点湿度。只有实际湿度处于平衡点湿度以下&#xff0c;粮食质…

SpringMVC-请求与相应

一、环境准备 <dependencies><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.1.0</version><scope>provided</scope> //确定范围避免与tomcat冲突</de…

iOS-自定义Intent及ShortCut,能通过快捷指令唤醒APP并跳转到指定页面

1.Xcode->New->File->搜索Intent 2.在新建的intent文件中中New intent 3.完善资料&#xff0c;内容可自定义 4.创建Extension&#xff0c;file -> new -> target , 选择 Intents Extension 即可 创建完成后&#xff0c;在intents文件中勾选以下target&#xff0…

独步全球:为何TikTok成为创业者的首选平台?

在当今数字时代&#xff0c;社交媒体平台的崛起已经成为了创业者们开展业务、推广产品和建立品牌的关键方式。 然而&#xff0c;在众多社交媒体平台中&#xff0c;TikTok正以惊人的速度崭露头角&#xff0c;成为越来越多创业者的首选平台。本文将深入探讨为何TikTok如此吸引创…

双指数移动平均线DEMA指标公式,减少传统均线的滞后性

双指数移动平均线DEMA由Patrick Mulloy发明的&#xff0c;对指数移动平均线EMA进行了改进&#xff0c;用于减少传统均线的滞后性&#xff0c;于1994年发表在美国金融类月刊《Technical Analysis of Stocks & Commodities》。 双指数移动平均线DEMA是通过两个指数移动平均线…

MyBatis 执行流程分析

文章目录 1. MyBatis 执行流程概述2. MyBatis 配置文件详解3. Mappers 映射器 1. MyBatis 执行流程概述 上篇文章讲到 MyBatis入门 MyBatis 的基本入门案例我们实现了通过 MyBatis 去获取数据库的数据&#xff0c;那么他的基本流程如下&#xff1a; 第一步&#xff1a;是从配置…

linux 归档和压缩文件和目录

打包&#xff1a; tar 是 Unix 和 Linux 系统中非常常用的命令之一。它可以将多个文件和目录打包成一个归档文件&#xff0c;并且支持压缩和解压缩功能。 将文件或&#xff08;和&#xff09;目录打包成一个归档文件 tar -cvf Arithmetic_Ghost.tar file1 file2 directory/…

如何在 Spring MVC 中处理表单提交

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

校物联网智慧安全用电平台的设计和运用-安科瑞黄安南

一.前言 安全用电历来都是学校安全工作的一个重点&#xff0c;然而每年因此发生的人身伤害以及火灾事故却在继续着&#xff0c;究其原因&#xff0c;主观上是我们的防患意识淡薄&#xff0c;客观上则是由于学生在宿舍使用违规电器、乱拉电线造成的。 现代的大学生宿舍&#x…

第二章 计算机系统基础知识笔记

计算机划分为硬件和软件 二、硬件部分 2.1 处理器 CISC&#xff1a;x86结构的复杂指令集 RISC&#xff1a;arm和power的精简指令集 2.2 存储器 片上缓存&#xff1a;在CPU里的集成缓存&#xff0c;SRAM&#xff0c;16KB~512KB。不同性能划分成一级或二级片外缓存&#xf…

LeetCode力扣020:有效的括号

有效的括号 实现思路 设立判定条件遍历的范围 代码实现 class Solution(object):def isValid(self, s):""":type s: str:rtype: bool"""nlen(s)for i in range(0,n-1):if s[i]( and s[i1]!):return Falseif s[i][ and s[i1]!]:return Falseif s…

macOS 中 Apple Distribution 与 Apple Development 证书不受信任解决方法

造成这个现象的原因是 Worldwide Developer Relations 中间关系证书缺失 我们只需要将相关证书下载并导入到「系统」级钥匙串即可 首先访问网站&#xff1a;https://www.apple.com/certificateauthority 下载右侧 Apple Intermediate Certificates 下方的如下证书 Develope…

正则表达式的应用(前端写法)

文章目录 1、匹配字符串中&#xff0c;a标签的href值2、校验邮箱3、校验手机号码3、待添加... 1、匹配字符串中&#xff0c;a标签的href值 (1) 代码 /*** description 匹配字符串中&#xff0c;a标签的href值* param {string} str 匹配的字符串* return {Array} 返回href值*/…

【算法练习Day6】四数相加赎金信三数之和四数之和

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 四数相加赎金信三数之和…

驱动开发---基于gpio子系统编写LED灯的驱动

一、GPIO子系统相关API 1.解析GPIO相关的设备树节点 struct device_node *of_find_node_by_path(const char *path) 功能&#xff1a;根据设备树节点路径解析设备树节点信息 参数&#xff1a; path&#xff1a;设备树所在的节点路径 /mynode0X12345678 返回值&#xff1a;成…

【Aurora 8B/10B IP(1)--初步了解】

Aurora 8B/10B IP(1)–初步了解 1 Aurora 8b/10b IP的基本状态: •通用数据通道吞吐量范围从480 Mb/s到84.48 Gb/s •支持多达16个连续粘合7GTX/GTH系列、UltraScale™ GTH或UltraScale+™ GTH收发器和4绑定GTP收发器 •Aurora 8B/10B协议规范v2.3顺从的 •资源成本低(请参…

环保电商:可持续发展在跨境电子商务中的崭露头角

近年来&#xff0c;环保意识的崛起和可持续发展的重要性日益凸显&#xff0c;已成为全球关注的焦点。在这个背景下&#xff0c;电子商务行业也逐渐加入了可持续发展的浪潮&#xff0c;形成了新的商业机会。 跨境电子商务领域&#xff0c;一股环保电商的潮流正崭露头角&#xf…

推荐一个AI人工智能技术网站(一键收藏,应有尽有)

1、Mental AI MentalAI&#xff08;https://ai.ciyundata.com/&#xff09;是一种基于星火大模型和文心大模型的知识增强大语言模型&#xff0c;专注于自然语言处理&#xff08;NLP&#xff09;领域的技术研发。 它具备强大的语义理解和生成能力&#xff0c;能够处理各种复杂的…

SRM系统一键查询:简化采购流程

一、SRM系统一键查询的意义和功能 1. 统一数据源&#xff1a;SRM系统将企业的供应商信息、采购订单、交易记录等数据整合到一个统一的平台&#xff0c;方便用户进行查询和分析。 2. 快速访问供应商信息&#xff1a;一键查询功能使用户能够快速访问和查找特定供应商的详细信息…