Java核心技术点之多线程

上传人:壹****1 文档编号:511338200 上传时间:2023-03-05 格式:DOC 页数:22 大小:62KB
返回 下载 相关 举报
Java核心技术点之多线程_第1页
第1页 / 共22页
Java核心技术点之多线程_第2页
第2页 / 共22页
Java核心技术点之多线程_第3页
第3页 / 共22页
Java核心技术点之多线程_第4页
第4页 / 共22页
Java核心技术点之多线程_第5页
第5页 / 共22页
点击查看更多>>
资源描述

《Java核心技术点之多线程》由会员分享,可在线阅读,更多相关《Java核心技术点之多线程(22页珍藏版)》请在金锄头文库上搜索。

1、-Java核心技术点之多线程一、为什么使用多线程1. 并发与并行 我们知道,在单核机器上,“多进程并不是真正的多个进程在同时执行,而是通过CPU时间分片,操作系统快速在进程间切换而模拟出来的多进程。我们通常把这种情况成为并发,也就是多个进程的运行行为是“一并发生的,但不是同时执行的,因为CPU核数的限制PC和通用存放器只有一套,严格来说在同一时刻只能存在一个进程的上下文。 现在,我们使用的计算机根本上都搭载了多核CPU,这时,我们能真正的实现多个进程并行执行,这种情况叫做并行,因为多个进程是真正“一并执行的具体多少个进程可以并行执行取决于CPU核数。综合以上,我们知道,并发是一个比并行更加广泛

2、的概念。也就是说,在单核情况下,并发只是并发;而在多核的情况下,并发就变为了并行。下文中我们将统一用并发来指代这一概念。2. 阻塞与非阻塞 UNI*系统内核提供了一个名为read的函数,用来读取文件的内容:typedef ssize_t int;typedef size_t unsigned;ssize_t read(int fd, void *buf, size_t n); 这个函数从描述符为fd的当前文件位置复制至多n个字节到内存缓冲区buf。假设执行成功则返回读取到的字节数;假设失败则返回-1。read系统调用默认会阻塞,也就是说系统会一直等待这个函数执行完毕直到它产生一个返回值。然而我

3、们知道,磁盘通常是一种慢速I/O设备,这意味着我们用read函数读取磁盘文件内容时,往往需要比拟长的时间相对于访问内存或者计算一些数值来说。则阻塞的时候我们当然不想让系统傻等着,我们想在这期间做点儿别的事情,等着磁盘准备好了通知我们一下,我们再来读取文件内容。实际上,操作系统正是这样做的。当阻塞在read这类系统调用中的时候,操作系统通常都会让该进程暂时休眠,调度一个别的进程来执行,以免干等着浪费时间,等到磁盘准备好了可以让我们来进展I/O了,它会发送一个中断信号通知操作系统,这时候操作系统重新调度原来的进程来继续执行read函数。这就是通过多进程实现的并发。3. 多进程 vs 多线程 进程就

4、是一个执行中的程序实例,而线程可以看作一个进程的最小执行单元。线程与进程间的一个显著区别在于每个进程都有一整套变量,而同一个进程间的多个线程共享该进程的数据。多进程实现的并发通常在进程创立以及数据共享等方面的开销要比多线程更大,线程的实现通常更加轻量,相应的开销也就更小,因此在一般客户端开发场景下,我们更加倾向于使用多线程来实现并发。 然而,有时候,多线程共享数据的便捷容易可能会成为一个让我们头疼的问题,我们在后文中会具体提到常见的问题及相应的解决方案。在上面的read函数的例子中,如果我们使用多线程,可以使用一个主线程去进展I/O的工作,再用一个或几个工作线程去执行一些轻量计算任务,这样当主

5、线程阻塞时,线程调度程序会调度我们的工作线程来执行计算任务,从而更加充分的利用CPU时间片。而且,在多核机器上,我们的多个线程可以并行执行在多个核上,进一步提升效率。二、如何使用多线程1. 线程执行模型 每个进程刚被创立时都只含有一个线程,这个线程通常被称作主线程main thread。而后随着进程的执行,假设遇到创立新线程的代码,就会创立出新线程,而后随着新线程被启动,多个线程就会并发地运行。*时刻,主线程阻塞在一个慢速系统调用中比方前面提到的read函数,这时线程调度程序会让主线程暂时休眠, 调度另一个线程来作为当前运行的线程。每个线程也有自己的一套变量,但相比于进程来说要少得多,因此线程

6、切换的开销更小。2. 创立一个新线程1通过实现Runnable接口 在Java中,有两种方法可以创立一个新线程。第一种方法是定义一个实现Runnable接口的类并实例化,然后将这个对象传入Thread的构造器来创立一个新线程,如以下代码所示:class MyRunnable implements Runnable . public void run() /这里是新线程需要执行的任务 Runnable r = new MyRunnable();Thread t = new Thread(r);2通过继承Thread类 第二种创立一个新线程的方法是直接定义一个Thread的子类并实例化,从而创立一

7、个新线程。比方以下代码:class MyThread e*tends Thread public void run() /这里是线程要执行的任务 创立了一个线程对象后,我们直接对其调用start方法即可启动这个线程:t.start();3两种方式的比拟 既然有两种方式可以创立线程,则我们该使用哪一种呢.首先,直接继承Thread类的方法看起来更加方便,但它存在一个局限性:由于Java中不允许多继承,我们自定义的类继承了Thread后便不能再继承其他类,这在有些场景下会很不方便;实现Runnable接口的那个方法虽然稍微繁琐些,但是它的优点在于自定义的类可以继承其他的类。3. 线程的属性1线程的

8、状态 线程在它的生命周期中可能处于以下几种状态之一:New新生:线程对象刚刚被创立出来;Runnable可运行:在线程对象上调用start方法后,相应线程便会进入Runnable状态,假设被线程调度程序调度,这个线程便会成为当前运行Running的线程;Blocked被阻塞:假设一段代码被线程A上锁“,此时线程B尝试执行这段代码,线程B就会进入Blocked状态;Waiting等待:当线程等待另一个线程通知线程调度器一个条件时,它本身就会进入Waiting状态;Time Waiting计时等待:计时等待与等待的区别是,线程只等待一定的时间,假设超时则不再等待;Terminated被终止:线程的

9、run方法执行完毕或者由于一个未捕获的异常导致run方法意外终止会进入Terminated状态。后文中假设不加特殊说明的话,我们会用阻塞状态统一指代Blocked、Waiting、Time Waiting。 2线程的优先级 在Java中,每个线程都有一个优先级,默认情况下,线程会继承它的父线程的优先级。可以用setPriority方法来改变线程的优先级。Java中定义了三个描述线程优先级的常量:MA*_PRIORITY、NORM_PRIORITY、MIN_PRIORITY。 每当线程调度器要调度一个新的线程时,它会首先选择优先级较高的线程。然而线程优先级是高度依赖与操作系统的,在有些系统的Ja

10、va虚拟机中,甚至会忽略线程的优先级。因此我们不应该将程序逻辑的正确性依赖于优先级。线程优先级相关的API如下:void setPriority(int newPriority) /设置线程的优先级,可以使用系统提供的三个优先级常量static void yield() /使当前线程处于让步状态,这样当存在其他优先级大于等于本线程的线程时,线程调度程序会调用那个线程4. Thread类 Thread实现了Runnable接口,关于这个类的以下实例域需要我们了解:private volatile charname; /当前线程的名字,可在构造器中指定private int priority; /

11、当前线程优先级private Runnable target; /当前要执行的任务private long tid; /当前线程的ID Thread类的常用方法除了我们之前提到的用于启动线程的start外还有:sleep方法,这是一个静态方法,作用是让当前线程进入休眠状态但线程不会释放已获取的锁,这个休眠状态其实就是我们上面提到过的Time Waiting状态,从休眠状态“苏醒后,线程会进入到Runnable状态。sleep方法有两个重载版本,声明分别如下:public static native void sleep(long millis) throws InterruptedE*cept

12、ion; /让当前线程休眠millis指定的毫秒数public static native void sleep(long millis, int nanos) throws InterruptedE*ception; /在毫秒数的根底上还指定了纳秒数,控制粒度更加精细join方法,这是一个实例方法,在当前线程中对一个线程对象调用join方法会导致当前线程停顿运行,等那个线程运行完毕后再接着运行当前线程。也就是说,把当前线程还没执行的局部“接到另一个线程后面去,另一个线程运行完毕后,当前线程再接着运行。join方法有以下重载版本:public final synchronized void j

13、oin() throws InterruptedE*ception public final synchronized void join(long millis) throws InterruptedE*ception; public final synchronized void join(long millis, int nanos) throws InterruptedE*ception; 无参数的join表示当前线程一直等到另一个线程运行完毕,这种情况下当前线程会处于Wating状态;带参数的表示当前线程只等待指定的时间,这种情况下当前线程会处于Time Waiting状态。当前线程

14、通过调用join方法进入Time Waiting或Waiting状态后,会释放已经获取的锁。实际上,join方法内部调用了Object类的实例方法wait,关于这个方法我们下面会具体介绍。yield方法,这是一个静态方法,作用是让当前线程“让步,目的是为了让优先级不低于当前线程的线程有时机运行,这个方法不会释放锁。interrupt方法,这是一个实例方法。每个线程都有一个中断状态标识,这个方法的作用就是将相应线程的中断状态标记为true,这样相应的线程调用isInterrupted方法就会返回true。通过使用这个方法,能够终止那些通过调用可中断方法进入阻塞状态的线程。常见的可中断方法有sle

15、ep、wait、join,这些方法的内部实现会时不时的检查当前线程的中断状态,假设为true会立刻抛出一个InterruptedE*ception异常,从而终止当前线程。以下这幅图很好的诠释了随着各种方法的调用,线程在不同的状态之间的切换图片来源:.blogs./dolphin0520/p/3920357.html:5. wait方法与notify/notifyAll方法1wait方法 wait方法是Object类中定义的实例方法。在指定对象上调用wait方法能够让当前线程进入阻塞状态前提时当前线程持有该对象的内部锁monitor,此时当前线程会释放已经获取的那个对象的内部锁,这样一来其他线程

16、就可以获取这个对象的内部锁了。当其他线程获取了这个对象的内部锁,进展了一些操作后可以调用notify方法来唤醒正在等待该对象的线程。2notify/notifyAll方法 notify/notifyAll方法也是Object类中定义的实例方法。它俩的作用是唤醒正在等待相应对象的线程,区别在于前者唤醒一个等待该对象的线程,而后者唤醒所有等待该对象的线程。这么说比拟抽象,下面我们来举一个具体的例子来说明以下wait和notify/notifyAll的用法。请看以下代码转自Java并发编程:线程间协作的两种方式):1 public class Test 2private int queueSize = 10;3

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 建筑/环境 > 施工组织

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号