线程基础

1/线程与进程

 (1)线程的概念:

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。

(2)进程的概念:

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。一个进程至少包含一个线程。

(3)给大家推荐一个讲述进程和线程的图画形式的网址:

http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html。

2/线程的状态

(1)创建(new)状态:准备好了一个多线程的对象
(2)就绪(runnable)状态:调用了start()方法,等待CPU进行调度
(3)运行(running)状态:执行run()方法
(4)阻塞(blocked)状态:暂时停止执行,可能将资源交给其它线程使用
(5)终止(dead)状态:线程销毁

 

如图:

 

 

3/什么是线程安全

(1)当多个线程访问run的方法时,以排队的方式进行处理(这里排队是按照CPU分配的先后的顺序而定的),一个线程想要执行synchronized修饰的方法里的代码,首先是尝试获得锁,如果拿到锁,执行synchronized代码体的内容;拿不到锁,这个线程就会不断的尝试得这把锁,直到拿到为止,而且是多个线程同时去竞争这把锁(也就是会有所的竞争问题)

4/上下文切换的概念

(1)对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。

(2)由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。举个简单的例子:比如一个线程A正在读取一个文件的内容,正读到文件的一半,此时需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取。

(3)因此需要记录线程A的运行状态,那么会记录哪些数据呢?因为下次恢复时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。

(4)说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。

(5)虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。

 

5/java实现多线程的方式

(1)继承Thread类

public class MyThread {

 

public static void main(String[]args) {

Thread1 myThread =new Thread1();

myThread.start();

}

 

}

 

class Thread1extends Thread {

public void run() {

try {

Thread.sleep(100L);

 

} catch (InterruptedExceptione) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

(2) 实现Runnable

public class MyThread1 {

 

public static void main(String[]args) {

Thread thread2 =new Thread(new Thread2());

thread2.start();

}

 

}

 

class Thread2implements Runnable {

@Override

public void run() {

try {

Thread.sleep(100L);

} catch (InterruptedExceptione) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。

如图:

 

 

这2种方式都可以用来创建线程去执行子任务,具体选择哪一种方式要看自己的需求。直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,但是由于Java只允许单继承,所以如果自定义类需要继承其他类,则只能选择实现Runnable接口。

还有一点最重要的就是使用实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源的共享.(例子如:卖火车票)

6/实现Runnable接口相对于继承Thread类来说,有如下显著的好处:

(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,很好的体现了面向对象的设计思想。
(2)可以避免单继承带来的局限性。
(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的

7/线程的常用方法

 

(1)start()方法

start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

(2)run()方法

run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

(3)join()方法

在很多情况下,主线程创建并启动了线程,如果子线程中要进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。这里有一个很值得注意的问题,join的底层调用的是wait方法,而且是循环调用,源码如图所示:

 

我们可以看到源码中join方法会在while循环中一直调用wait方法,假如wait的时间是1000ms,在500ms的时候另外一个线程调用了notifyAll方法时,线程就会苏醒。

(4)setDaemon()方法

用来设置线程是否成为守护线程和判断线程是否是守护线程。

守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。

(5)sleep()方法

此方法调用的是native的方法,sleep方法是使当前线程休眠,讲cpu占用权交给其他任意优先级的线程。但是我们应该注意:sleep方法并不会释放对象锁。

(6)yield()方法

调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

(7)isAlive()方法

方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。需要注意的地方是,在不调用sleep()方法时,isAlive()方法为true,在执行sleep(1000)的方法后,IsAlive()方法结果为false。是因为线程已经在1秒之内执行完毕。

 (8)currentThread()方法

currentThread()方法可以返回代码段正在被哪个线程调用的信息。

(9)setPriority()和getPriority()方法

用来获取和设置线程优先级,尽管jdk有10哥优先级,但它与多数操作系统都不能映射的很好。比如,Winfdows有7哥优先级且都不是固定的。所以这种映射关系也是不确定的。唯一可移植的方法是当调整优先级的时候,使用MAX_PRIORITY,NORM_PRIORITY和MIN_PRIORITY三种级别。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实验目的 (1)掌握Windows系统提供的线程创建与撤销系统调用 (2)掌握Windows系统环境下线程的创建与撤销方法 2 实验准备知识 (1)线程创建 CreateThread()完成线程的创建。它在调用进程的地址空间上创建一个线程,执行指定的函数,并返回新建立线程的句柄。 原型: HANDLE CreateThread(   LPSECURITY_ATTRIBUTES lpThreadAttributes,   DWORD dwStackSize,   LPTHREAD_START_ROUTINE lpStartAddress,   LPVOID lpParameter,   DWORD dwCreationFlags,   LPDWORD lpThreadId);   参数说明:   lpThreadAttributes:指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,它被设为NULL,表示使用缺省值。   dwStackSize,线程堆栈大小,一般=0,在任何情况下,Windows根据需要动态延长堆栈的大小。   lpStartAddress,指向线程函数的指针,形式:@函数名,函数名称没有限制,但是必须以下列形式声明:   DWORD WINAPI ThreadProc (LPVOID pParam) ,格式不正确将无法调用成功。   lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。   dwCreationFlags :线程标志,可取值如下   CREATE_SUSPENDED: 创建一个挂起的线程   0 :创建后立即激活。   lpThreadId:保存新线程的id。   返回值:   函数成功,返回线程句柄;函数失败返回false。 (2)撤销线程 ExitThread()用于撤销当前线程 原型: VOID ExitThread( DWORD dwExitCode ); 参数说明: DwExitCode:指定线程返回码 返回值: 该函数没有返回值 用法举例: ExitThread(0); (3)挂起线程 Sleep()用于挂起当前正在执行的线程 原型: VOID Sleep( DWORD dwMilliseconds ); 参数说明: dwMilliseconds:指定挂起时间,单位为ms(毫秒)。 返回值: 该函数没有返回值。 (4)关闭句柄 函数CloseHandle()用于关闭已打开对象的句柄,其作用与释放动态申请的内存空间类似,这样可以释放系统资源,使进程安全运行。 原型: BOOL CloseHandle( HANDLE hObject ); 参数说明: hObject:已打开对象的句柄。 返回值: 成功,返回值为非0值;失败,则返回值为0.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值