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

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

yuyutoo 2024-10-12 01:04 1 浏览 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;
    }
}

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

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

相关推荐

jQuery VS AngularJS 你更钟爱哪个?

在这一次的Web开发教程中,我会尽力解答有关于jQuery和AngularJS的两个非常常见的问题,即jQuery和AngularJS之间的区别是什么?也就是说jQueryVSAngularJS?...

Jquery实时校验,指定长度的「负小数」,小数位未满末尾补0

在可以输入【负小数】的输入框获取到焦点时,移除千位分隔符,在输入数据时,实时校验输入内容是否正确,失去焦点后,添加千位分隔符格式化数字。同时小数位未满时末尾补0。HTML代码...

如何在pbootCMS前台调用自定义表单?pbootCMS自定义调用代码示例

要在pbootCMS前台调用自定义表单,您需要在后台创建表单并为其添加字段,然后在前台模板文件中添加相关代码,如提交按钮和表单验证代码。您还可以自定义表单数据的存储位置、添加文件上传字段、日期选择器、...

编程技巧:Jquery实时验证,指定长度的「负小数」

为了保障【负小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【负小数】的方法。HTML代码<inputtype="text"class="forc...

一篇文章带你用jquery mobile设计颜色拾取器

【一、项目背景】现实生活中,我们经常会遇到配色的问题,这个时候去百度一下RGB表。而RGB表只提供相对于的颜色的RGB值而没有可以验证的模块。我们可以通过jquerymobile去设计颜色的拾取器...

编程技巧:Jquery实时验证,指定长度的「正小数」

为了保障【正小数】的正确性,做成了通过Jquery,在用户端,实时验证指定长度的【正小数】的方法。HTML做成方法<inputtype="text"class="fo...

jquery.validate检查数组全部验证

问题:html中有多个name[],每个参数都要进行验证是否为空,这个时候直接用required:true话,不能全部验证,只要这个数组中有一个有值就可以通过的。解决方法使用addmethod...

Vue进阶(幺叁肆):npm查看包版本信息

第一种方式npmviewjqueryversions这种方式可以查看npm服务器上所有的...

layui中使用lay-verify进行条件校验

一、layui的校验很简单,主要有以下步骤:1.在form表单内加上class="layui-form"2.在提交按钮上加上lay-submit3.在想要校验的标签,加上lay-...

jQuery是什么?如何使用? jquery是什么功能组件

jQuery于2006年1月由JohnResig在BarCampNYC首次发布。它目前由TimmyWilson领导,并由一组开发人员维护。jQuery是一个JavaScript库,它简化了客户...

django框架的表单form的理解和用法-9

表单呈现...

jquery对上传文件的检测判断 jquery实现文件上传

总体思路:在前端使用jquery对上传文件做部分初步的判断,验证通过的文件利用ajaxFileUpload上传到服务器端,并将文件的存储路径保存到数据库。<asp:FileUploadI...

Nodejs之MEAN栈开发(四)-- form验证及图片上传

这一节增加推荐图书的提交和删除功能,来学习node的form提交以及node的图片上传功能。开始之前需要源码同学可以先在git上fork:https://github.com/stoneniqiu/R...

大数据开发基础之JAVA jquery 大数据java实战

上一篇我们讲解了JAVAscript的基础知识、特点及基本语法以及组成及基本用途,本期就给大家带来了JAVAweb的第二个知识点jquery,大数据开发基础之JAVAjquery,这是本篇文章的主要...

推荐四个开源的jQuery可视化表单设计器

jquery开源在线表单拖拉设计器formBuilder(推荐)jQueryformBuilder是一个开源的WEB在线html表单设计器,开发人员可以通过拖拉实现一个可视化的表单。支持表单常用控件...

取消回复欢迎 发表评论: