编程技术是改变世界的力量。
本站
当前位置:网站首页 > 后端语言 > 正文

第十章:Java多线程编程 java多线程实现原理

gowuye 2024-04-02 10:20 8 浏览 0 评论

第十章:Java多线程编程

在本章中,我们将介绍Java中的多线程编程。多线程是现代计算机应用程序中常见的重要概念之一,它可以提高程序的性能和响应能力。在本章中,我们将深入探讨Java多线程编程的概念、原理和实践。我们将学习如何创建和控制线程,以及如何处理线程间的同步和通信。

10.1 Java的线程概念和生命周期

在开始介绍Java多线程编程之前,让我们先来了解一些基本概念。线程是程序执行的最小单位,它代表了一个独立的执行路径。在Java中,线程由Thread类表示。每个Java程序都至少有一个主线程,也可以创建多个额外的线程来执行并行任务。

在本节中,我们将学习线程的生命周期,它包括以下几个状态:

  • 新建状态(New):线程被创建但尚未启动。
  • 就绪状态(Runnable):线程可以开始执行,但尚未获得CPU时间片。
  • 运行状态(Running):线程正在执行任务。
  • 阻塞状态(Blocked):线程暂时停止执行,等待某个条件满足。
  • 终止状态(Terminated):线程执行完成或因异常而终止。

10.2 Java的线程创建方式

在Java中,有两种常见的方式来创建线程:继承Thread类和实现Runnable接口。

10.2.1 继承Thread类

继承Thread类是创建线程的一种方式。为了创建一个新的线程,我们可以定义一个类,并继承自Thread类。然后重写Thread类的run()方法,在该方法中定义线程要执行的任务。

下面是一个简单的示例代码:

public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程要执行的任务
        System.out.println("Hello from MyThread!");
    }
}

创建并启动线程的代码如下所示:

public class Main {
    public static void main(String[] args) {
        // 创建线程实例
        MyThread thread = new MyThread();
        
        // 启动线程
        thread.start();
    }
}

在上面的代码中,我们创建了一个名为MyThread的线程类,并重写了其run()方法。然后,在主线程中创建MyThread的实例,并调用start()方法来启动线程。启动线程后,线程将会执行其run()方法中定义的任务。

10.2.2 实现Runnable接口

另一种创建线程的方式是实现Runnable接口。与继承Thread类不同,实现Runnable接口将线程的任务与线程类本身分离开来,更符合面向对象的设计原则。

下面是一个使用实现Runnable接口创建线程的示例代码:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程要执行的任务
        System.out.println("Hello from MyRunnable!");
    }
}

创建并启动线程的代码如下所示:

public class Main {
    public static void main(String[] args) {
        // 创建线程实例
        MyRunnable runnable = new MyRunnable();
        
        // 创建线程对象
        Thread thread = new Thread(runnable);
        
        // 启动线程
        thread.start();
    }
}

在上面的代码中,我们创建了一个名为MyRunnable的类,并实现了Runnable接口。然后,在主线程中创建了一个Thread对象,并将MyRunnable的实例作为参数传递给Thread的构造函数。最后,调用线程的start()方法来启动线程。

无论是继承Thread类还是实现Runnable接口,最终都会创建一个新的线程,并在该线程中执行定义的任务。

10.3 Java的线程控制方法

Java提供了一些方法来控制线程的执行,包括start()、run()、sleep()、join()和yield()等。

10.3.1 start()方法

start()方法用于启动线程,并让线程进入就绪状态,等待CPU调度执行。当调用start()方法时,系统会为线程分配必要的资源,并在稍后的时间点自动调用线程的run()方法。

10.3.2 run()方法

run()方法是线程的主体,包含线程要执行的任务代码。当线程启动后,系统会自动调用线程的run()方法,并在该方法中执行定义的任务。

需要注意的是,我们不应直接调用线程的run()方法来启动线程,而是应该通过调用start()方法来启动线程。

10.3.3 sleep()方法

sleep()方法使线程暂停执行一段时间,让出CPU时间片给其他线程执行。它接受一个以毫秒为单位的参数,表示线程暂停的时间。

下面是一个使用sleep()方法的示例代码:

public class Main {
    public static void main(String[] args) {
        System.out.println("Thread 1 started.");
        
        try {
            // 线程1暂停执行500毫秒
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("Thread 1 finished.");
    }
}

在上面的代码中,线程1在执行到Thread.sleep(500)时,暂停执行500毫秒,然后继续执行。

10.3.4 join()方法

join()方法用于等待一个线程的完成。当一个线程调用其他线程的join()方法时,它将会被阻塞,直到被调用的线程执行完成后才继续执行。

下面是一个使用join()方法的示例代码:

public class Main {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            System.out.println("Thread 1 started.");
            
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            System.out.println("Thread 1 finished.");
        });
        
        Thread thread2 = new Thread(() -> {
            System.out.println("Thread 2 started.");
            
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            System.out.println("Thread 2 finished.");
        });
        
        // 启动线程1
        thread1.start();
        
        // 等待线程1执行完成后再启动线程2
        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        // 启动线程2
        thread2.start();
    }
}

在上面的代码中,我们创建了两个线程:线程1和线程2。在主线程中,首先启动线程1,然后调用线程1的join()方法,等待线程1执行完成后再启动线程2。这样可以确保线程2在线程1执行完成后才开始执行。

10.3.5 yield()方法

yield()方法是一种线程让步的机制,它使当前线程暂停执行,让出CPU时间片给其他具有相同优先级的线程。通过调用yield()方法,可以实现线程之间的合理调度,提高系统的整体性能。

需要注意的是,yield()方法只是提供了一种提示,不保证当前线程会立即让出CPU时间片,而是依赖于系统的具体实现。

10.4 Java的线程同步机制

在并发编程中,多个线程可能同时访问共享的资源,如果没有适当的同步机制,可能会导致数据不一致或并发错误。Java提供了一些机制来实现线程的同步,包括synchronized关键字、wait/notify机制和锁(Lock)等。

10.4.1 synchronized关键字

synchronized关键字用于保护共享资源,确保在同一时间只有一个线程可以访问被保护的代码块或方法。

同步代码块

我们可以使用synchronized关键字来创建同步代码块,如下所示:

public class Counter {
    private int count = 0;
    
    public void increment() {
        synchronized (this) {
            count++;
        }
    }
}

在上面的代码中,使用synchronized关键字将代码块count++标记为同步代码块,其中的参数this表示当前对象,也就是锁定的资源。这样,每次只有一个线程可以进入该代码块,确保了对count变量的安全访问。

同步方法

我们也可以使用synchronized关键字来创建同步方法,如下所示:

public class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
}

在上面的代码中,使用synchronized关键字修饰了方法increment(),这样整个方法体都被视为同步代码块,只有一个线程可以同时执行该方法。

无论是同步代码块还是同步方法,都是通过获取对象的锁来实现线程的同步。当一个线程进入同步代码块或同步方法时,它会尝试获取锁。如果锁已经被其他线程占用,那么当前线程将被阻塞,直到获取到锁才能继续执行。

10.4.2 wait/notify机制

wait/notify机制是基于对象的等待/通知机制,用于实现线程间的协作和通信。它允许一个线程暂停执行,直到满足某个条件才继续执行,同时允许其他线程发出通知,以唤醒等待的线程。

wait()方法

wait()方法使当前线程进入等待状态,释放对象的锁,并等待其他线程调用相同对象的notify()或notifyAll()方法来唤醒它。

wait()方法可以有两种形式:

  • wait(): 使当前线程无限期地等待,直到其他线程唤醒它。
  • wait(long timeout): 使当前线程等待一定的时间,如果在指定的时间内没有被唤醒,将自动苏醒。

wait()方法必须在同步代码块或同步方法中调用,否则会抛出IllegalMonitorStateException异常。

notify()和notifyAll()方法

notify()方法用于唤醒正在等待对象锁的某个线程,而notifyAll()方法用于唤醒所有正在等待对象锁的线程。

notify()方法只会唤醒等待队列中的一个线程,而notifyAll()方法会唤醒所有等待的线程。被唤醒的线程将重新进入对象锁的竞争。

notify()和notifyAll()方法也必须在同步代码块或同步方法中调用,否则同样会抛出IllegalMonitorStateException异常。

下面是一个使用wait/notify机制的示例代码:

public class Message {
    private String content;
    private boolean empty = true;
    
    public synchronized String read() {
        while (empty) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        empty = true;
        notifyAll();
        return content;
    }
    
    public synchronized void write(String message) {
        while (!empty) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        empty = false;
        this.content = message;
        notifyAll();
    }
}

在上面的示例代码中,我们创建了一个Message类,其中包含一个content字段表示消息内容,和一个empty字段表示消息是否为空。read()方法用于读取消息,write()方法用于写入消息。

在read()方法中,使用while循环判断消息是否为空,如果为空,则调用wait()方法进入等待状态,释放对象锁。等待期间,其他线程可以调用write()方法写入消息并唤醒等待的线程。当被唤醒后,再次检查消息是否为空,如果不为空,则返回消息内容。

在write()方法中,同样使用while循环判断消息是否为空,如果不为空,则调用wait()方法进入等待状态,释放对象锁。等待期间,其他线程可以调用read()方法读取消息并唤醒等待的线程。当被唤醒后,再次检查消息是否为空,如果为空,则写入消息内容。

需要注意的是,在使用wait()方法时,要将其放在循环中,并且检查等待条件。这是为了防止虚假唤醒(spurious wake-up),即线程在没有收到通知的情况下被唤醒。通过将wait()方法放在循环中,可以在唤醒后再次检查等待条件,确保只在满足条件时才继续执行。

10.4.3 Lock和Condition

除了使用synchronized关键字和wait/notify机制外,Java还提供了Lock和Condition接口来实现线程的同步和协作。

Lock接口提供了与synchronized关键字相似的功能,用于保护临界区代码。相比于synchronized关键字,Lock提供了更灵活的锁定机制,例如可重入锁、公平锁、读写锁等。

Condition接口则提供了比wait/notify更强大的线程等待和通知机制。一个Lock对象可以关联多个Condition对象,每个Condition对象可以控制线程的等待和唤醒。

使用Lock和Condition的示例代码如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Message {
    private String content;
    private boolean empty = true;
    private Lock lock = new ReentrantLock();
    private Condition notEmpty = lock.newCondition();
    private Condition notFull = lock.newCondition();
    
    public void read() {
        lock.lock();
        try {
            while (empty) {
                try {
                    notEmpty.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            empty = true;
            notFull.signalAll();
            System.out.println("Read: " + content);
        } finally {
            lock.unlock();
        }
    }
    
    public void write(String message) {
        lock.lock();
        try {
            while (!empty) {
                try {
                    notFull.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            empty = false;
            this.content = message;
            notEmpty.signalAll();
            System.out.println("Write: " + message);
        } finally {
            lock.unlock();
        }
    }
}

在上面的代码中,我们首先创建了一个Lock对象和两个Condition对象,分别用于控制非空和非满的条件。

在read()方法中,首先调用lock()方法获取锁,然后使用while循环判断消息是否为空。如果为空,调用await()方法进入等待状态,并释放锁。等待期间,其他线程可以调用write()方法写入消息并唤醒等待的线程。当被唤醒后,再次检查消息是否为空,如果不为空,则输出消息内容。

在write()方法中,同样首先调用lock()方法获取锁,然后使用while循环判断消息是否为空。如果不为空,调用await()方法进入等待状态,并释放锁。等待期间,其他线程可以调用read()方法读取消息并唤醒等待的线程。当被唤醒后,再次检查消息是否为空,如果为空,则写入消息内容。

最后,需要在适当的地方调用lock.unlock()方法释放锁,以确保资源的正确释放。

使用Lock和Condition相比于synchronized关键字和wait/notify机制更加灵活,可以更精确地控制线程的等待和唤醒,并提供更多的同步和并发控制选项。

以上是关于Java多线程编程的基本内容和常用技术。通过合理地使用线程同步和协作机制,可以编写出安全、高效的多线程程序。然而,在实际开发中,还有更多复杂的多线程场景和问题需要进一步研究和解决。深入学习和理解多线程编程的原理和技术,将有助于编写更健壮、高性能的并发应用。

希望本篇博客对您理解和掌握Java多线程编程有所帮助。如有任何问题或疑惑,请随时提问。谢谢!

相关推荐

PHPMailer远程命令执行漏洞分析

摘要:PHPMailer是一个强大的PHP编写的邮件发送类,但近日被爆出远程命令执行漏洞,该漏洞实际上是什么,有何种影响,本文对该漏洞进行了分析及验证方法,并给出防护方案。0x00漏洞概要PHPMa...

「安全漏洞」DedeCMS-5.8.1 SSTI模板注入导致RCE

漏洞类型SSTIRCE利用条件影响范围应用漏洞概述2021年9月30日,国外安全研究人员StevenSeeley披露了最新的DedeCMS版本中存在的一处SQL注入漏洞以及一处SSTI导致的RCE...

回顾使用PHP原生发送电子邮件(终)文件附件

FileAttachments文件附件Fileattachmentsworkjustlikemixedemail,exceptthatadifferentcontenttyp...

php-fpm.conf & php.ini 安全优化实践

0x01关于php其历史相对已经比较久远了,这里也就不废话了,属弱类型中一种解释型语言除了web开发以及写些简单的exp,暂未发现其它牛逼用途,暂以中小型web站点开发为主另外,低版本的php自身...

linux 安全配置 ossec 开源检测

linux安全配置ossec开源检测一:介绍主要功能有日志分析、完整性检查、rootkit检测、基于时间的警报和主动响应。除了具有入侵检测系统功能外,它还一般被用在SEM/SIM(安全事件管理(...

PHP使用PHPMailer发送验证码邮件的方法与调用逻辑

首先我们需要下载PHPMailer:https://github.com/PHPMailer/PHPMailer一般情况下我们只需要压缩包中的src文件夹中的文件,并保存至根目录即可:设置一个文件,如...

回顾使用PHP原生发送电子邮件(一)

IwishIcouldremembertheveryfirstemailmessageIeversent.Unfortunately,thetruthisthatI...

PHPMAILER实现PHP发邮件功能php实例

这篇文章主要为大家详细介绍了PHPMAILER实现PHP发邮件功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下本文实例为大家分享了PHPMAILER实现PHP发邮件功能的具体代码,供大家参考,具...

500道网络安全面试题集锦(附答案)

本篇文章内容为网络安全各个方向涉及的面试题,星数越多代表问题出现的几率越大,但是无论如何都无法覆盖所有的面试问题,更多的还是希望由点达面,查漏补缺,然后祝各位前程似锦,都能找到自己满意的工作!一、We...

网站放家里,随处看电影「Apache+php+ssl 安装和配置」

  使用5G网络,随处都可以看到放自己家里电脑的视频。这个功能很容易实现,不需要太多的专业知识,也不需要额外花钱。如果确实需要,最多花不到两百块钱买一台旧电脑放家里,做个网站,就能解决全部问题,Fre...

Windows2008中 Magic Winmail Server提权

MagicWinmailServer是安全易用全功能的邮件服务器软件,不仅支持SMTP/POP3/IMAP/Webmail/LDAP(公共地址簿)/多域/发信认证/反垃圾邮件/邮件过滤/邮件组...

利用PHPmailer发送邮件

早上帮朋友做一个收集客户联系方式的页面,要求能实时推送信息给管理员。刚开始想到做后台管理,因为时间紧,做后台是赶不上了。想过通过短信发送,成本太高,否决了。。。灵机一动,客户提交时直接把信息发送到邮箱...

phpmailer发送邮件

phpmailer发送邮件PHP内置的mail函数使用起来不够方便,另外受其他语言的影响,博主更偏好面向对象的包管理模式,因此phpmailer成为了我用PHP发送邮件的首选,这里分享给大家。库导入这...

PHP初级教程:读取输入

PHP读取输入:Form:?formaction="welcome.php"method="post">Name:(inputtype="text...

php filter 验证Email,Url,Ip格式

今天发现一个非常好用的函数东西,filter过滤器,用于验证和过滤来自非安全来源的数据,比如用户的输入。验证Email:$email='1234567@qq.com';if(!filter_v...

取消回复欢迎 发表评论: