从屌丝到架构师的飞越(多线程篇)-线程

2019-07-12  本文已影响0人  走着别浪

一.介绍

在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程。当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通使用的,需要在实践中不断积累。由于并发肯定涉及到多线程,因此在进入并发编程主题之前,我们先来了解一下进程和线程的由来。

多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理(Chip-level multithreading)或同时多线程(Simultaneous multithreading)处理器。

在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理(Multithreading)”。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

二.知识点介绍

1、线程

2、单线程

3、多线程

4、线程的生命周期

5、二种多线程区别

6、匿名线程

三.上课对应视频的说明文档

1、线程

(1)进程与线程

现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。

A、进程

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

用进程来对应一个程序,每个进程对应一定的内存地址空间,并且只能使用它自己的内存空间,各个进程间互不干扰。并且进程保存了程序每个时刻的运行状态,这样就为进程切换提供了可能。当进程暂时时,它会保存当前进程的状态(比如进程标识、进程的使用的资源等),在下一次重新切换回来时,便根据之前保存的状态进行恢复,然后继续执行。

B、线程:

在出现了进程之后,操作系统的性能得到了大大的提升。虽然进程的出现解决了操作系统的并发问题,但是人们仍然不满足,人们逐渐对实时性有了要求。因为一个进程在一个时间段内只能做一件事情,如果一个进程有多个子任务,只能逐个地去执行这些子任务。比如对于一个监控系统来说,它不仅要把图像数据显示在画面上,还要与服务端进行通信获取图像数据,还要处理人们的交互操作。如果某一个时刻该系统正在与服务器通信获取图像数据,而用户又在监控系统上点击了某个按钮,那么该系统就要等待获取完图像数据之后才能处理用户的操作,如果获取图像数据需要耗费10s,那么用户就只有一直在等待。显然,对于这样的系统,人们是无法满足的。

为了解决这一问题人们就发明了线程,让一个线程去执行一个子任务,这样一个进程就包括了多个线程,每个线程负责一个独立的子任务,这样在用户点击按钮的时候,就可以暂停获取图像数据的线程,让UI线程响应用户的操作,响应完之后再切换回来,让获取图像的线程得到CPU资源。从而让用户感觉系统是同时在做多件事情的,满足了用户对实时性的要求。

线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

“同时”执行是人的感觉,在线程之间实际上轮换执行

(2)同步与异步

A、同步

通常,同步传输是以数据块为传输单位。每个数据块的头部和尾部都要附加一个特殊的字符或比特序列,标记一个数据块的开始和结束,一般还要附加一个校验序列(如16位或32位CRC校验码),以便对数据块进行差错控制。所谓同步传输是指数据块与数据块之间的时间间隔是固定的,必须严格地规定它们的时间关系。

比如:

我们去购物,如果你去商场实体店买一台空调,当你到了商场看中了一款空调,你就想售货员下单。售货员去仓库帮你调配物品。这天你热的实在不行了。就催着商家赶紧给你配送,于是你就等在商场里,候着他们,直到商家把你和空调一起送回家,一次愉快的购物就结束了。

B/S:提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事 

B、 异步

通常,异步传输是以字符为传输单位,每个字符都要附加 1 位起始位和 1 位停止位,以标记一个字符的开始和结束,并以此实现数据传输同步。所谓异步传输是指字符与字符(一个字符结束到下一个字符开始)之间的时间间隔是可变的,并不需要严格地限制它们的时间关系。起始位对应于二进制值 0,以低电平表示,占用 1 位宽度。停止位对应于二进制值 1,以高电平表示,占用 1~2 位宽度。一个字符占用 5~8位,具体取决于数据所采用的字符集。

比如:

不过,如果我们赶时髦,就坐再家里打开电脑,在网上订购了一台空调。当你完成网上支付的时候,对你来说购物过程已经结束了。虽然空调还没有送到家,但是你的任务都已经完成了。商家接到你的订单后,就会加紧安排送货,当然这一切已经跟你无关了,你已经支付完成,想什么就能去干什么了,出去溜达几圈都不成问题。等送货上门的时候,接到商家电话,回家一趟签收即可。

AJAX:请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

2、单线程

在java中单线程就是我们的主线程,每一个程序都有一个唯一的主线程

案例:

public class TestThread{

public void run(){

while(true){

//显示当前线程的名字

System.out.println("run: "+Thread.currentThread().getName());

}

}

}

public class ThreadDemo{

publicc static void main(String args[]){

while(true){

System.out.println("main: "+Thread.currentThread().getName());

}

}

}

3、多线程

实现多线程有二种方式:

(1)继承Thead类

格式:

class 类名 extends Thread{

public void run(){

}

}

new 类名().start();

代码示例:

TestThread.java类

class TestThread extends Thread{

public void run(){//run()是线程的主体,运行线程时直接运行run()。

while(true){

System.out.println("run:"+Thread.currentThread().

getName());

}

}

}

ThreadDemo.java类

public class ThreadDemo{

public static void main(String []args){

new TestThread().start();//启动线程

while(true){

System.out.println("main():"+Thread.currentThread()

.getName());

}

}

}

(1) 实现Runnable接口

格式:

class 类名 implements Runnable{

public void run(){

}

}

new Thread(new 类名()).start();

代码示例:

ThreadDemo.java类

class ThreadDemo implements Runnable {

public void run(){

while(true){

System.out.println("ThreadDemo:"+Thread.currentThread()

.getName());

}

}

}

TestThread.java类

public class TestThread{

public static void main(String []args){

//产生线程对象

Thread tt= new Thread(new ThreadDemo());

tt.start();

while(true){

System.out.println("TestThread:"+Thread.currentThread()

.getName());

}

}

}

4、线程生命周期

要想控制好线程,就必须了解线程的五大状态:生、死、可运行、运行、等待/阻塞/睡眠

(1)生状态:

线程对象已经创建,还没有在其上调用start()方法。

(2)可运行状态:

当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。

(3)运行状态:

线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

(4)等待/阻塞/睡眠状态:

这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。

(5)死亡状态:

当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

案例:

当主线程结束时,其他线程不受任何影响,并不会随之结束。一旦子线程启动起来后,它就拥有和主线程相同的地位,它不会受主线程的影响。为了测试某个线程是否已经死亡,可以调用线程对象的isAlivc()方法,当线程处于就绪、运行、阻塞了种状态时,该方法将返回true;当线程处于新建、死亡状态时,该方法将返回false。

不要试图对一个已经死亡的线程调用start()方法使它重新启动,死亡就是死亡,该线程将不可再次作为线程执行。

下面程序尝试对处于死亡状态的线程再次调用start()。

代码示例:

public class StartDead extends Thread {

private int i;

// 重写run方法,run方法的方法体就是线程执行体

public void run() {

for (; i < 100; i++) {

System.out.println(getName() + "" + i);

}

}

public static void main(String[] args) {

// 创建线程对象

StartDead sd = new StartDead();

for (int i = 0; i < 300; i++) {

// 调用Thread的currentThread方法获取当前线程

System.out.println(Thread.currentThread().getName() + "" + i);

if (i == 20) {

// 启动线程

sd.start();

// 判断启动后线程的isAlive()值,输出true

System.out.println(sd.isAlive());

}

// 只有当线程处于新建、死亡两种状态时isAlive()方法返回false。

// 当i > 20,则该线程肯定已经启动过了,如果sd.isAlive()为假时,

// 那只能是死亡状态了。

if (i > 20 && !sd.isAlive())

{

// 试图再次启动该线程

sd.start();

}

}

}

}

5、两种方式区别

(1)直接自定义了一种线程。同时规定了线程中要执行的逻辑

(2)直接创建了普通的Thread线程,自定义了线程要执行的目标,将线程与线程执行目标分离。方便了多线程共享数据、躲避了不能多继承的业务实现,提高了代码复用性,优化了代码。

6、线程的匿名内部类使用

使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。

(1)创建线程对象时,直接重写Thread类中的run方法

new Thread() {

public void run() {

for (int x = 0; x < 40; x++) {

System.out.println(Thread.currentThread().getName()

+ "...X...." + x);

}

}

}.start();

(2)使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法

Runnable r = new Runnable() {

public void run() {

for (int x = 0; x < 40; x++) {

System.out.println(Thread.currentThread().getName()

+ "...Y...." + x);

}

}

};

new Thread(r).start();

上一篇下一篇

猜你喜欢

热点阅读