百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

一文搞懂Java多线程 java多线程实战指南

yuyutoo 2024-10-12 01:04 8 浏览 0 评论

Java多线程的运行机制

Java多线程的创建

Java多线程的创建有三种方式:

1.通过继承Thread类创建进程

2.通过Runnable接口创建线程

3.通过Callable接口和Future接口创建线程

1.通过继承Thread类创建进程

run()
start()

Thread常用方法

方法

说明

void run()

线程运行时所执行的代码

void interript

中断进程

void start()

使该线程开始执行

static void yield()

暂停当前正在执行的进程 并执行其他进程

Thread.State getState()

返回该线程的状态

final boolean isAlive()

测试该线程是否处于活动的状态

String getName()

返回该线程的名称

void setName(String name)

改变线程的名称

public class www {
 
    public static void main(String[] args){
 
        for (int i = 0; i < 3; i++) {
 
            Test test = new Test(i);
            test.start();
        }
    }
}
class Test extends Thread{
 
    int name;
    public Test(int name){
 
        this.name = name;
    }
    public void run() {
 
        System.out.println("线程"+name);
    }
}

每次输出的结果可能不同,这是因为子线程执行的进度是不确定的,它们是并发运行的。

缺点 :若该类已经继承一个类,则无法继承 Thread 类。

2.通过Runnable接口创建线程

  • 步骤1:定义 Runnable 接口的实现类,并实现该接口的方法 run()
  • 步骤2:定义 Runnable 实现类的实例,并以此为实例作为 Thread 类的 target 参数,来创建 Thread 线程对象,该 Thread 对象才是真的线程对象。
public class www {
 
    public static void main(String[] args){
 
        for (int i = 0; i < 3; i++) {
 
            Test test = new Test(i);
            Thread t = new Thread(test);
            t.start();
        }
    }
}
class Test implements Runnable{
 
    int name;
    public Test(int name){
 
        this.name = name;
    }
    public void run() {
 
        System.out.println("线程"+name);
    }
}

同样每次输出的结果可能不同,这也是因为子线程执行的进度是不确定的,它们是并发运行的。

3.通过Callable接口和Future接口创建线程

  • 步骤1:创建 Callable 接口的实现类,并实现 call() 方法,该方法将作为线程的执行体,并且有返回值。
  • 步骤2:创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
  • 步骤3:使用 FutureTask 对象作为 Thread 对象的 target ,创建并启动新线程。
  • 步骤4:调用 FutureTask 对象的 get() 方法来获得子线程执行结束的返回值。
public class www {
 
    public static void main(String[] args){
 
        for (int i = 0 ; i < 3 ; i++){
 
            Test test = new Test(i);
            //使用FutureTask来包装Callable对象
            FutureTask result = new FutureTask(test);
            //创建线程对象
            Thread thread = new Thread(result);
            //启动线程
            thread.start();
        }
    }
}
class Test implements Callable{
 
    int name;
    public Test(int name){
 
        this.name = name;
    }
    @Override
    public Object call() throws Exception {
 
        System.out.println("线程"+name);
        return null;
    }
}

同样每次输出的结果可能不同,这也是因为子线程执行的进度是不确定的,它们是并发运行的。

Java线程的生命周期

分为六种状态,具体为 创建(New)状态、可运行(Runnable)状态、阻塞(Blocked)状态(不老科特)、等待状态(Waiting)状态、计时等待(Timed waiting)状态、终止(Terminated) 状态。

Java线程调度

线程睡眠——Sleep

可以让当前正在执行的线程暂停一段时间

public class www extends JFrame implements Runnable {
 
    JLabel jLabel1,jLabel2;
    public www (){
 
        jLabel1 = new JLabel("当前时间:");
        jLabel2 = new JLabel();
        Container containerPane = this.getContentPane();
        containerPane.setLayout(new FlowLayout());
        this.add(jLabel1);
        this.add(jLabel2);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(300,200);
        this.setVisible(true);
    }
    @Override
    public void run() {
 
        while (true){
 
            jLabel2.setText(getTime());
            //获取当前进程 并延时2000ms
            try {
 
                Thread.currentThread().sleep(2000);
            } catch (InterruptedException e) {
 
                throw new RuntimeException(e);
            }
        }
    }
    String getTime(){
 
        Date date =new Date();
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日HH:mm:ss Z");
        return simpleDateFormat.format(date);
    }
    public static void main(String[] args) {
 
        www test1 = new www();
        Thread thread1 = new Thread(test1);
        thread1.start();
    }
}

可以看出时间每隔2S(2000ms)刷新一次

线程让步——yield(youde)

yield()sleep() 都会暂停执行当前的进程,不同的是 yield() 会先判读是否有比该进程优先级相同或高的进程,若是有则停止让相同或高优先级的先运行,若是没有则该进程继续运行。

public class www  implements Runnable {
 
    String str = "";
    @Override
    public void run() {
 
        for (int i = 1; i <= 9; i++) {
 
            str += Thread.currentThread().getName() + "----" + i + "     ";
            if(i%3 == 0){
 
                System.out.println(str);
                str = "";
                Thread.currentThread().yield();
            }
        }
    }
    public static void main(String[] args) {
 
        www test1 = new www();
        www test2 = new www();
        Thread thread1 = new Thread(test1,"线程1");
        Thread thread2 = new Thread(test2,"线程2");
        thread1.start();
        thread2.start();
    }
}

输出的结果线程是交替的,可能输出的结果不是交替的每次运行都不一样,所以通过yield()控制线程的执行方法是不可靠的。

线程协作——join

若一个线程运行到某一个点的时候,等待另一个线程运行结束后才能继续运行,这种情况可以通过调用另一个线程的join()方法来实现。

public class www {
 
    public static void main(String[] args) {
 
        Thread thread = new test();
        thread.start();
        for (int i = 1; i <= 10; i++) {
 
            System.out.println("主线程");
            if(i == 5){
 
                try {
 
                    thread.join();
                } catch (InterruptedException e) {
 
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
class test  extends Thread{
 
    public void run(){
 
        for (int i = 1; i <= 10 ; i++) {
 
            System.out.println("子线程");
        }
    }
}

通常情况下,主线程创建并启动了线程,如果子线程运行过程中需要大量的时间,主线程往往早于子线程运行完之前,这个例子是在主线程运行完之前设置状态为 join() 等待子线程运行完再继续运行。

进程的优先级

Java中每个进程都对应的有优先级,优先级高的获得的运行机会多。线程优先级常用1~10之间的数字进行表示,数值越大优先级越高,线程默认等级为5。

Thread类中定义个三个常量

名称

MIN_PRIORITY

1

MAX_PRIORITY

10

NORM_PRIORITY

5

优先级的设定 setPrioriy() 方法 和 获取优先级的方法 getPrioriy()

守护进程

Java分为两类 用户线程守护线程

守护线程也称为后台线程 为其他线程提供服务 这类线程可以监控其他线程的运行 依赖与其他线程

用户线程是一般线程 负责业务逻辑

可通过 setDaemon() 方法来设置一个线程的类型 true为守护线程 只能在 线程.start() 之前调用 还可以通过 isDaemon() 方法来判断该进程是否是守护进程

线程同步

多线程引发的问题:在进行多线程的设计的时候,有时候需要多个线程共享一个部分代码块,从而实现共享一个私有成员变量或类的静态成员的目的。这时由于线程和线程之间相互争夺CPU资源,线程无序地进行访问这些共享资源,最终也可能无法得到正确的结果,这些问题通常称为线程安全问题。

例如:

public class www {
 
    public static void main(String[] args) {
 
        test test = new test();
        for (int i = 0; i < 10; i++) {
 
            new Thread(test).start();
        }
    }
}
class test  implements Runnable{
 
    public int num = 0;
    //使用temp是为了增加线程的切换几率
    private void add(){
 
        int temp;
        for (int i = 0; i < 1000; i++) {
 
            temp = num;
            temp++;
            num  = temp;
        }
        System.out.println(Thread.currentThread().getName() + "  "+ num);
    }
    public void run(){
 
        add();
    }
}

输出结果几乎没有正确1000倍数的,这是由于多线程的并发执行,多个线程同时对变量 num 进行修改的结果,解决这个问题就必须要有Java的同步机制。

同步代码块

Java为每个对象配备一把锁和一个等候集,这个对象可以是实例对象,也可以是类对象。对实例对象进行加锁,可以保证这个实例对象相关的线程可以互斥的使用对象锁;对类对象进行加锁可以保证这个类相关的线程可以被互斥的使用类对象的锁。通过new关键字创建实例对象,,从而获得对象的引用,要获得类对象的引用。我们可以通过 forName 成员方法。一个类的静态成员变量和静态成员方法隶属于类对象,而一个类的非静态成员变量和非静态成员方法属于类的实例对象。

synchronize(synObject){
 
    //关键code
}

当进程进入关键代码时系统会先检查对象的锁是否被其他线程获取,若没有则JVM把该对象的锁交给当前请求锁的进程,该线程获取锁后即可进入关键代码区域。

例如:

public class www {
 
    public static void main(String[] args) {
 
        test test = new test();
        for (int i = 0; i < 10; i++) {
 
            new Thread(test).start();
        }
    }
}
class test  implements Runnable{
 
    int num = 0;
    private void add(){
 
        int temp;
        for (int i = 0; i < 1000; i++) {
 
            temp = num;
            temp++;
            num  = temp;
        }
        System.out.println(Thread.currentThread().getName() + "  "+ num);
    }
    public void run(){
 
        synchronized (this){
 
            add();
        }
    }
}

这样下来每个线程执行完毕的结果都是整数了。

将add()方法使用synchronized修饰一样的结果,这就是下面要讲的同步方法

private synchronized void add(){
 
        int temp;
        for (int i = 0; i < 1000; i++) {
 
            temp = num;
            temp++;
            num  = temp;
        }
        System.out.println(Thread.currentThread().getName() + "  "+ num);
    }

同步方法

同步方法和同步代码块一样,都是利用互锁实现的代码同步访问。

public class www {
 
    public static void main(String[] args) {
 
        test test = new test();
        for (int i = 0; i < 10; i++) {
 
            new Thread(test).start();
        }
    }
}
class test  implements Runnable{
 
    int num = 0;
    private synchronized void add(){
 
        int temp;
        for (int i = 0; i < 1000; i++) {
 
            temp = num;
            temp++;
            num  = temp;
        }
        System.out.println(Thread.currentThread().getName() + "  "+ num);
    }
    public void run(){
 
            add();
    }
}

同步是一种高消耗的操作,因此尽量减少用 synchronize 设置大的同步方法,一般情况下使用 synchronize 代码块同步关键代码。

线程通讯

有时多线程会有执行过程中的次序问题,Java提供了三个方法来解决线程的通讯问题。分别是 wait()notify()notifyAll() 方法。这三个关键字只能在synchronized关键字作用范围内起作用,并且是在同一个方法中搭配这三个方法才有实际的意义。

wait() 方法:可以使调用该方法的线程释放共享资源的锁,从可运行状态进入等待状态。知道再次被唤醒。

notify() 方法:可以唤醒等待队列中第一个等待同一共享资源的线程,并使该进程退出等待状态,进入可运行状态。

notifyAll() 方法:可以使所有正在等待队列中等待同一共享资源的进程从等待状态退出,进入可运行状态,若有多个进程,哪个优先级最高先运行哪个。在不知道该唤醒哪个进程的时候使用该方法。

知识点补充:

线程的5种状态详解

下面举个例子:

public class www {
 
    public static void main(String[] args) {
 
        //实例化一个ShareStore对象 并创建进程启动
        ShareStore shareStore = new ShareStore();
        new Consumer(shareStore).start();
        new Producer(shareStore).start();
    }
}

/***
 * 生产者
 */
class Producer extends Thread{
 
    private ShareStore shareStore;
    Producer(ShareStore shareStore){
 
        this.shareStore = shareStore;
    }
    @Override
    public void run() {
 
        int num = 1;
        while (true){
 
            shareStore.setShareNum(++num);
            System.out.println("Producer生产了一个数字"+num);
            //睡眠1s
            try {
 
                Thread.sleep(1000);
            } catch (InterruptedException e) {
 
                throw new RuntimeException(e);
            }
        }
    }
}
/***
 * 消费者
 */
class Consumer extends Thread{
 
    private ShareStore shareStore;
    Consumer(ShareStore shareStore){
 
        this.shareStore = shareStore;
    }
    @Override
    public void run() {
 
        int num = 1;
        while (true){
 
            num = shareStore.getShareNum();
            System.out.println("Consumer消费了一个数字"+num);
            //睡眠1s
            try {
 
                Thread.sleep(1000);
            } catch (InterruptedException e) {
 
                throw new RuntimeException(e);
            }
        }
    }
}

/***
 * 用来管理的
 */
class ShareStore{
 
    private int num;
    private boolean writeable = true;
    public synchronized void setShareNum(int num){
 
        if (!writeable){
 
            try {
 
                wait();//等待消费者消费完成
            } catch (InterruptedException e) {
 
                throw new RuntimeException(e);
            }
        }
        this.num = num;
        writeable = false;
        notify(); //通知消费者 生产者已经生产可以消费
    }
    public synchronized int getShareNum(){
 
        if (writeable){
 
            try {
 
                wait(); //等待生产者生产出来
            } catch (InterruptedException e) {
 
                throw new RuntimeException(e);
            }
        }
        writeable = true;
        notify();//通知可以生产
        return this.num;
    }
}

输出的结果都是生产者先生产一个数字,消费者再消费。

死锁是一种场景:当两个或多个进程形成单项等待的环,每个进程都在相互等待对方的资源释放,都无法执行一直持续下去就形成了死锁。

相关推荐

电脑 CMD 命令大全:简单粗暴收藏版

电脑CMD命令大全包括了许多常用的命令,这些命令可以帮助用户进行各种系统管理和操作任务。以下是一些常用的CMD命令及其功能:1、系统信息和管理...

电脑维修高手必备!8个神奇DOS命令,自己动手不求人

我相信搞电脑维修或者维护的基本都会些DOS的命令。就算Windows操作系统是可视化的界面,但很多维护检查是离不开DOS命令的。掌握好这些命令,你不仅能快速诊断问题,还能解决90%的常见电脑故障。下...

一个互联网产品总监的设计技巧总结 - 技术篇

古语:工欲善其事必先利其器。往往在利其器后我们才能事半功倍。从这个角度出发成为一个合格的产品经理你需要的是“利其器”,这样你才能产品的设计过程中如鱼得水,得心应手。有些产品经理刚入职,什么都感觉自己欠...

超详解析Flutter渲染引擎|业务想创新,不了解底层原理怎么行?

作者|万红波(远湖)出品|阿里巴巴新零售淘系技术部前言Flutter作为一个跨平台的应用框架,诞生之后,就被高度关注。它通过自绘UI,解决了之前RN和weex方案难以解决的多端一致性...

瑞芯微RK3568|SDK开发之环境安装及编译操作

1.SDK简介一个通用LinuxSDK工程目录包含有buildroot、app、kernel、device、docs、external等目录。其中一些特性芯片如RK3308/RV1108/R...

且看L-MEM ECC如何守护i.MXRT1170从核CM4

大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是恩智浦i.MXRT1170上Cortex-M4内核的L-MEMECC功能。本篇是《简析i.MXRT1170Cortex-M7F...

ECC给i.MXRT1170 FlexRAM带来了哪些变化?

大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是恩智浦i.MXRT1170上Cortex-M7内核的FlexRAMECC功能。ECC是“ErrorCorrectingCode”...

PHP防火墙代码,防火墙,网站防火墙,WAF防火墙,PHP防火墙大全

PHP防火墙代码,防火墙,网站防火墙,WAF防火墙,PHP防火墙大全资源宝整理分享:https://www.htple.net...

从零开始移植最新版本(2023.10)主线Uboot到Orange Pi 3(全志H6)

本文将从零开始通过一步一步操作来实现将主线U-Boot最新代码移植到OrangePi3(全志H6)开发板上并正常运行起来。本文从通用移植思路的角度,展现是思考的过程,通过这种方式希望能让读者一通百...

可视化编程工具Blockly——定制工具箱

1概述本文重点讲解如何定制Blocklytoolbox上,主要包含如下几点目标:如何为toolbox不同类别添加背景色如何改变选中的类别的外观如何为toolbox类别添加定制化的css如何改变类别...

用户界面干货盘点(用户界面的基本操作方法)

DevExpressDevExpressWPF的DXSplashScreen控件在应用加载的时候显示一个启动界面。添加DXSplashScreen后,会默认生成一个XAML文件,当然,你也可...

Vue3+Bootstrap5整合:企业级后台管理系统实战

简洁而不简单,优雅而不失强大在当今快速发展的企业数字化进程中,高效、美观的后台管理系统已成为企业运营的核心支撑。作为前端开发者,我们如何选择技术栈,才能既保证开发效率,又能打造出专业级的用户体验?答案...

什么?这三款i.MXRT型号也开放了IAP API?

大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是i.MXRT1050/1020/1015系列ROM中的FlexSPI驱动API使用。今天痞子衡去4S店给爱车做保养了,...

OneCode基础组件介绍——表格组件(Grid)

在企业级应用开发中,表格组件是数据展示与交互的核心载体。OneCode平台自研的Grid表格组件,以模型驱动设计...

开源无线LoRa传感器(光照温湿度甲醛Tvoc)

本开源项目基于ShineBlinkC2M低代码单片机实现,无需复杂单片机C语言开发。即使新手也可很容易用FlexLua零门槛开发各种功能丰富稳定可靠的IoT硬件,更多学习教程可参考Flex...

取消回复欢迎 发表评论: