第九章、多线程
9-1、进程与线程
DOS系统有一个非常明显的特点,只要一中病毒之后则系统会立刻死机,因为传统的DOS系统是采用单进程的处理方式,所以只能有一个程序独自运行,其他程序无法运行。
Windows系统中,即使出现了病毒,系统照样可以正常使用,因为在Windows中采用的是多进程的处理方式,那么在同一个时间段上会有多个程序同时运行。
对于WORD来说,每次启动一个WORD之后实际上都是在操作系统上分配了一个进程。
线程实际上就是在进程基础之上的进一步划分,从WORD来看,可以把拼写检查当作一个线程进行处理。当然,会同时存在多个线程。
如果一个进程没有了,则线程肯定会消失,而如果线程消失了,进程未必会消失。而且,所有的线程都是在进程的基础之上并发运行的(同时运行)。
如果现在同时运行多个任务,则所有的系统资源将是共享的,被所有线程所公用,但是程序处理需要CPU,对传统的单核CPU来说,在同一个时间段上会有多个程序执行,但是在同一个时间点上只能存在一个程序运行,也就是说,所有的程序都要抢占CPU资源。
但是现在的CPU已经发展到多核状态了,在一个电脑上可能会存在多个CPU,那么这个时候就可以非常清楚的发现多线程操作间是如何进行并发执行的。
9-2、Java的多线程实现
在JAVA中如果要想实现多线程可以采用以下两种方式:
·继承Thread类
·实现Runnable接口
9-2-1、Thread类
Thread类是在java.lang包中定义的一个类,只要继承了Thread类,此类就称为多线程操作类。在Thread子类中,必须明确的覆写Thread类中的run()方法,此方法为线程的主体。多线程的定义语法:
class 类名称 extends Thread{ //继承Thread类
属性…; //类中定义属性
方法…; //类中定义方法
//覆写Thread类中的run()方法,此方法是线程的主体
public void run(){
线程主体;
}
}
java.lang包会在程序运行时自动导入,所以无需手工编写import语句。
一个类继承了Thread类之后,那么此类就具备了多线程的操作功能。
class MyThread extends Thread{ // 继承Thread类,作为线程的实现类
private String name ; // 表示线程的名称
public MyThread(String name){
this.name = name ; // 通过构造方法配置name属性
}
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
}
};
以上已经完成了一个线程的操作类,直接使用此类就可以完成功能。
class MyThread extends Thread{ // 继承Thread类,作为线程的实现类
private String name ; // 表示线程的名称
public MyThread(String name){
this.name = name ; // 通过构造方法配置name属性
}
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
}
};
public class ThreadDemo01{
public static void main(String args[]){
MyThread mt1 = new MyThread("线程A ") ; // 实例化对象
MyThread mt2 = new MyThread("线程B ") ; // 实例化对象
mt1.run() ; // 调用线程主体
mt2.run() ; // 调用线程主体
}
};
观察程序运行效果:
线程A 运行,i = 0
线程A 运行,i = 1
线程A 运行,i = 2
线程A 运行,i = 3
线程A 运行,i = 4
线程A 运行,i = 5
线程A 运行,i = 6
线程A 运行,i = 7
线程A 运行,i = 8
线程A 运行,i = 9
线程B 运行,i = 0
线程B 运行,i = 1
线程B 运行,i = 2
线程B 运行,i = 3
线程B 运行,i = 4
线程B 运行,i = 5
线程B 运行,i = 6
线程B 运行,i = 7
线程B 运行,i = 8
线程B 运行,i = 9
以上的程序是先执行完A之后再执行B,并没有达到所谓的并发执行的效果。
因为以上的程序实际上还是按照老的形式调用的,通过对象方法,但是如果要想启动一个线程必须使用Thread类中定义的start()方法。
一旦调用start()方法,实际上最终调用的就是run()方法。
class MyThread extends Thread{ // 继承Thread类,作为线程的实现类
private String name ; // 表示线程的名称
public MyThread(String name){
this.name = name ; // 通过构造方法配置name属性
}
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
}
};
public class ThreadDemo02{
public static void main(String args[]){
MyThread mt1 = new MyThread("线程A ") ; // 实例化对象
MyThread mt2 = new MyThread("线程B ") ; // 实例化对象
mt1.start() ; // 调用线程主体
mt2.start() ; // 调用线程主体
}
};
程序运行效果:
线程A 运行,i = 0
线程A 运行,i = 1
线程A 运行,i = 2
线程A 运行,i = 3
线程A 运行,i = 4
线程A 运行,i = 5
线程A 运行,i = 6
线程B 运行,i = 0
线程B 运行,i = 1
线程B 运行,i = 2
线程B 运行,i = 3
线程B 运行,i = 4
线程B 运行,i = 5
线程B 运行,i = 6
线程B 运行,i = 7
线程A 运行,i = 7
线程A 运行,i = 8
线程A 运行,i = 9
线程B 运行,i = 8
线程B 运行,i = 9
从运行的效果看,确实是并发执行的,哪个线程先抢占到了CPU资源,哪个线程就先执行。
但是,为什么不直接调用run()方法,而是通过start()调用呢?
如果要想解决这样的难题,则肯定要打开Thread类的定义,在JDK的src.zip中全部都是JAVA的源程序代码,直接找到java.lang.Thread类,就可以打开Thread类的定义:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
native关键字表示的是一个由Java调用本机操作系统函数的一个关键字,在Java中,运行JAVA程序调用本机的操作系统的函数以完成特定的功能。
证明,如果现在要是想实现多线程的话,则肯定需要操作系统的支持,因为多线程操作中牵扯到一个抢占CPU的情况,要等待CPU进行调度,那么这一点肯定需要操作系统的底层支持,所以使用了native调用本机的系统函数。而且在各个操作系统中,多线程的实现底层代码肯定是不同的,所以使用native关键字也可以让JVM自动去调整不同的JVM实现。
threadStatus也表示一种状态,如果线程已经启动了再调用start()方法的时候就有可能产生异常。
class MyThread extends Thread{ // 继承Thread类,作为线程的实现类
private String name ; // 表示线程的名称
public MyThread(String name){
this.name = name ; // 通过构造方法配置name属性
}
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
}
};
public class ThreadDemo03{
public static void main(String args[]){
MyThread mt1 = new MyThread("线程A ") ; // 实例化对象
mt1.start() ; // 调用线程主体
mt1.start() ; // 错误
}
};
9-2-2、Runnable接口
在JAVA中也可以通过实现Runnable接口的方式实现多线程,Runnable接口中只定义了一个抽象方法:
·public void run();
通过Runnable接口实现多线程:
·class 类名称 implements Runnable{ //实现Runnable接口
属性….; //类中定义属性
方法….; //类中定义方法
public void run(){ //覆写Runnable接口里的run()方法
线程主体;
}
}
class MyThread implements Runnable{ // 实现Runnable接口,作为线程的实现类
private String name ; // 表示线程的名称
public MyThread(String name){
this.name = name ; // 通过构造方法配置name属性
}
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
}
};
如果要想启动线程则肯定依靠Thread类,但是之前如果直接继承了Thread类,则可以将start()方法直接继承下来并使用,但是在Runnable接口中并没有此方法。
Thread类的构造:
public Thread(Runnable target)
利用此构造方法启动多线程:
class MyThread implements Runnable{ // 实现Runnable接口,作为线程的实现类
private String name ; // 表示线程的名称
public MyThread(String name){
this.name = name ; // 通过构造方法配置name属性
}
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<10;i++){
System.out.println(name + "运行,i = " + i) ;
}
}
};
public class RunnableDemo01{
public static void main(String args[]){
MyThread mt1 = new MyThread("线程A ") ; // 实例化对象
MyThread mt2 = new MyThread("线程B ") ; // 实例化对象
Thread t1 = new Thread(mt1) ; // 实例化Thread类对象
Thread t2 = new Thread(mt2) ; // 实例化Thread类对象
t1.start() ; // 启动多线程
t2.start() ; // 启动多线程
}
};
9-2-3、Thread类与Runnable接口
Thread定义:
public class Thread
extends Object
implements Runnable
从定义格式上可以发现,Thread类也是Runnable接口的子类。
从类的关系上看,之前的做法非常类似于代理设计模式,Thread类完成比线程主体更多的操作,例如:分配CPU资源、判断是否已经启动等。
Thread类与Runnable接口的区别:
使用Thread类在操作多线程的时候无法达到资源共享的目的,而使用Runnable接口实现的多线程操作可以实现资源共享。
class MyThread extends Thread{ // 继承Thread类,作为线程的实现类
private int ticket = 5 ; // 表示一共有5张票
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<100;i++){
if(this.ticket>0){
System.out.println("卖票:ticket = " + ticket--) ;
}
}
}
};
public class ThreadDemo04{
public static void main(String args[]){
MyThread mt1 = new MyThread() ; // 实例化对象
MyThread mt2 = new MyThread() ; // 实例化对象
MyThread mt3 = new MyThread() ; // 实例化对象
mt1.run() ; // 调用线程主体
mt2.run() ; // 调用线程主体
mt3.run() ; // 调用线程主体
}
};
程序运行结果:
F:\网页\李兴华\030901_【第9章:多线程】_认识多线程\代码>java ThreadDemo04
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
从程序运行结果可以发现,一共卖出了15张票,三个线程各卖各的5张票,也就是说现在并没有达到资源共享的目的。
因为在每一个MyThread对象中都包含各自的ticket属性。
如果现在使用Runnable接口呢?同样启动多个线程,那么所有的线程将卖出共同的5张票。
class MyThread implements Runnable{ // 继承Thread类,作为线程的实现类
private int ticket = 5 ; // 表示一共有5张票
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<100;i++){
if(this.ticket>0){
System.out.println("卖票:ticket = " + ticket--) ;
}
}
}
};
public class RunnableDemo02{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化对象
new Thread(mt).run() ; // 调用线程主体
new Thread(mt).run() ; // 调用线程主体
new Thread(mt).run() ; // 调用线程主体
}
};
程序运行结果:
F:\网页\李兴华\030901_【第9章:多线程】_认识多线程\代码>java RunnableDemo02
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1
从运行效果来看,虽然现在启动了三个线程,但是三个线程一共才卖出了5张票,所以达到了资源共享的目的。
从以上实例中可以看了出:实现Runnable接口比继承Thread类有如下明显优点:
·适合多个相同程序代码的线程去处理同一个资源
·可以避免由于单继承局限所带来的影响
·增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的
综合以上来看,开发中使用Runnable接口是最合适的。
9-3、线程的状态
多线程在操作中也是有一个固定的操作状态的:
·创建状态:准备好了一个多线程的对象:Thread t=new Thread()
·就绪状态:调用了start()方法,等待CPU进行调度
·运行状态:执行run()方法
·阻塞状态:暂时停止执行,可能将资源交给其他线程使用
·终止状态(死亡状态):线程执行完毕,不再使用。
实际上,线程调用start()方法的时候不是立刻启动的,而是要等待CPU进行调度。
9-3、线程的常用操作方法
在多线程中所有的操作方法实际上都是从Thread类开始的,所有的操作基本上都在Thread类之中。
线程操作的主要方法
方法名称
类型
描述
1
public Thread(Runnable target)
构造
接收Runnable接口子类对象,实例化Thread对象
2
public Thread(Runnable target,String name)
构造
接收Runnable接口子类对象,实例化Thread对象,并设置线程名称
3
public Thread(String name)
构造
实例化Thread对象,并设置线程名称
4
public static Thread currentThread()
普通
返回目前正在执行的线程
5
public final String getName()
普通
返回线程的名称
6
public final int getPriority()
普通
发挥线程的优先级
7
public Boolean inInterrupted()
普通
判断目前线程是否被中断,如果是,返回true,否则返回false
8
public final Boolean isAlive()
普通
判断线程是否在活动,如果是,返回true,否则返回false
9
public final void join() throws InterruptedException
普通
等待线程死亡
10
public final synchronized void join(long millis) throws InterruptedException
普通
等待millis毫秒后,线程死亡
11
public void run()
普通
执行线程
12
public final void setName(String name)
普通
设定线程名称
13
public final void setPriority(int newPriority)
普通
设定线程的优先值
14
public static void sleep(long millis) throws InterruptedException
普通
使目前正在执行的线程休眠millis毫秒
15
public void start()
普通
开始执行线程
16
public static void yield()
普通
将目前正在执行的线程暂停,允许其他线程执行
17
public final void setDaemon(Boolean on)
普通
将一个线程设置成后台运行
18
public final void setPriority(int newPriority)
普通
更改线程的优先级
9-3-1、线程名称
取得和设置线程名称:
·在Thread类中,可以通过getName()方法取得线程的名称,通过setName()方法设置线程的名称。
·线程的名称一般在启动线程前设置,但也允许为已经运行的线程设置名称。允许两个Thread对象有相同的名字,但为了清晰,应该避免这种情况的发生。
·如果程序并没有为线程指定名称,则系统会自动的为线程分配一个名称。
线程的名称在启动前设置,避免重名。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class ThreadNameDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
new Thread(mt).start() ; // 系统自动设置线程名称
new Thread(mt,"线程-A").start() ; // 手工设置线程名称
new Thread(mt,"线程-B").start() ; // 手工设置线程名称
new Thread(mt).start() ; // 系统自动设置线程名称
new Thread(mt).start() ; // 系统自动设置线程名称
}
};
程序运行效果:
F:\网页\李兴华\030902_【第9章:多线程】_线程常用操作方法\代码>java ThreadNameDemo
Thread-0运行,i = 0
Thread-0运行,i = 1
Thread-0运行,i = 2
线程-A运行,i = 0
线程-A运行,i = 1
线程-A运行,i = 2
线程-B运行,i = 0
线程-B运行,i = 1
线程-B运行,i = 2
Thread-1运行,i = 0
Thread-1运行,i = 1
Thread-1运行,i = 2
Thread-2运行,i = 0
Thread-2运行,i = 1
Thread-2运行,i = 2
从程序运行效果看,指定的名称会自动出现,如果没有指定,线程使用自动编号的方式完成,这说明在Thread类中肯定存在一个static属性,用于记录编号。
9-3-2、取得当前线程
程序可以通过currentThread()方法取得当前正在运行的线程对象。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class CurrentThreadDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
new Thread(mt,"线程").start() ; // 启动线程
mt.run() ; // 直接调用run()方法
}
};
程序运行结果:
F:\网页\李兴华\030902_【第9章:多线程】_线程常用操作方法\代码>java CurrentThreadDemo
main运行,i = 0
main运行,i = 1
main运行,i = 2
线程运行,i = 0
线程运行,i = 1
线程运行,i = 2
此时发现,程序中由主方法直接通过线程对象调用里面的run()方法,所以输出的结果中包含了一个“main”,此线程就是由“mt.run()”启动,因为调用此语句是由主方法完成的,也就是说实际上主方法本身也是一个线程——主线程。
既然主方法都是以线程的形式出现的,那么JAVA运行时到底启动了多少个线程呢?
从程序运行情况看,至少启动了两个:
·每当JAVA程序执行的时候,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程。JAVA中本身具备了垃圾收集机制,所以JAVA运行时至少启动了两个线程:主线程和GC
9-4、判断线程是否启动
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class ThreadAliveDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
System.out.println("线程开始执行之前 --> " + t.isAlive()) ; // 判断是否启动
t.start() ; // 启动线程
System.out.println("线程开始执行之后 --> " + t.isAlive()) ; // 判断是否启动
for(int i=0;i<3;i++){
System.out.println(" main运行 --> " + i) ;
}
// 以下的输出结果不确定
System.out.println("代码执行之后 --> " + t.isAlive()) ; // 判断是否启动
}
};
程序运行结果:
F:\网页\李兴华\030902_【第9章:多线程】_线程常用操作方法\代码>java ThreadAliveDemo
线程开始执行之前 --> false
线程开始执行之后 --> true
main运行 --> 0
main运行 --> 1
main运行 --> 2
代码执行之后 --> true
线程运行,i = 0
线程运行,i = 1
线程运行,i = 2
9-5、线程的强制运行
在线程操作中,可以使用join()方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class ThreadJoinDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
for(int i=0;i<50;i++){
if(i>10){
try{
t.join() ; // 线程强制运行
}catch(InterruptedException e){}
}
System.out.println("Main线程运行 --> " + i) ;
}
}
};
程序运行结果:
F:\网页\李兴华\030902_【第9章:多线程】_线程常用操作方法\代码>java ThreadJoinDemo
Main线程运行 --> 0
Main线程运行 --> 1
Main线程运行 --> 2
Main线程运行 --> 3
线程运行,i = 0
线程运行,i = 1
线程运行,i = 2
线程运行,i = 3
线程运行,i = 4
Main线程运行 --> 4
9-6、线程的休眠
使用Thread.sleep(long millis)可使进程暂停:
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<10;i++){
try{
Thread.sleep(1000) ; // 线程休眠
}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class ThreadSleepDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
}
};
程序运行效果:程序设定每间隔1000毫秒运行一次,直至程序运行结束。
9-7、线程的中断
一个线程可以被另外一个线程中断其操作的状态,使用intrrupt()方法完成。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
System.out.println("1、进入run()方法") ;
try{
Thread.sleep(10000) ; // 线程休眠10秒
System.out.println("2、已经完成了休眠") ;
}catch(InterruptedException e){
System.out.println("3、休眠被终止") ;
return ; // 返回调用处
}
System.out.println("4、run()方法正常结束") ;
}
};
public class ThreadInterruptDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
try{
Thread.sleep(2000) ; // 线程休眠2秒
}catch(InterruptedException e){
System.out.println("3、休眠被终止") ;
}
t.interrupt() ; // 中断线程执行
}
};
程序运行结果:
F:\网页\李兴华\030902_【第9章:多线程】_线程常用操作方法\代码>java ThreadInterruptDemo
1、进入run()方法
3、休眠被终止
9-8、后台线程
在JAVA程序中,只要前台有一个线程在运行,则整个JAVA进程都不会消失,所以此时可以设置一个后台线程,这样即使JAVA进程结束了,此后台线程依然会继续执行。要想实现这样的操作,直接使用setDaemon()方法。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
int i=0;
while(true){
if (i<30){
System.out.println(Thread.currentThread().getName() + "在运行。") ;
i++;
}else{
return;
}
}
}
};
public class ThreadDaemonDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
t.setDaemon(true) ; // 此线程在后台运行
}
};
9-9、线程的优先级
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<5;i++){
try{
Thread.sleep(500) ; // 线程休眠
}catch(InterruptedException e){}
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
}
}
};
public class ThreadPriorityDemo{
public static void main(String args[]){
Thread t1 = new Thread(new MyThread(),"线程A") ; // 实例化线程对象
Thread t2 = new Thread(new MyThread(),"线程B") ; // 实例化线程对象
Thread t3 = new Thread(new MyThread(),"线程C") ; // 实例化线程对象
t1.setPriority(Thread.MIN_PRIORITY) ; // 优先级最低
t2.setPriority(Thread.NORM_PRIORITY) ; // 优先级中等
t3.setPriority(Thread.MAX_PRIORITY) ; // 优先级最高
t1.start() ; // 启动线程
t2.start() ; // 启动线程
t3.start() ; // 启动线程
}
};
程序运行结果:
F:\网页\李兴华\030902_【第9章:多线程】_线程常用操作方法\代码>java ThreadPriorityDemo
线程C运行,i = 0
线程B运行,i = 0
线程A运行,i = 0
线程C运行,i = 1
线程B运行,i = 1
线程A运行,i = 1
线程C运行,i = 2
线程B运行,i = 2
线程A运行,i = 2
线程C运行,i = 3
线程B运行,i = 3
线程A运行,i = 3
线程C运行,i = 4
线程B运行,i = 4
线程A运行,i = 4
主方法的优先级:
public class MainPriorityDemo{
public static void main(String args[]){
System.out.println("主方法的优先级:" +
Thread.currentThread().getPriority()) ; // 取得主方法的优先级
System.out.println("MAX_PRIORITY = " + Thread.MAX_PRIORITY) ;
System.out.println("NORM_PRIORITY = " + Thread.NORM_PRIORITY) ;
System.out.println("MIN_PRIORITY = " + Thread.MIN_PRIORITY) ;
}
};
程序运行结果:
F:\网页\李兴华\030902_【第9章:多线程】_线程常用操作方法\代码>java MainPriorityDemo
主方法的优先级:5
MAX_PRIORITY = 10
NORM_PRIORITY = 5
MIN_PRIORITY = 1
9-10、线程的礼让
在线程操作中,也可以使用yield()方法将一个线程的操作暂时让给其他线程执行。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<5;i++){
try{
Thread.sleep(500) ;
}catch(Exception e){}
System.out.println(Thread.currentThread().getName()
+ "运行,i = " + i) ; // 取得当前线程的名字
if(i==2){
System.out.print("线程礼让:") ;
Thread.currentThread().yield() ; // 线程礼让
}
}
}
};
public class ThreadYieldDemo{
public static void main(String args[]){
MyThread my = new MyThread() ; // 实例化MyThread对象
Thread t1 = new Thread(my,"线程A") ;
Thread t2 = new Thread(my,"线程B") ;
t1.start() ;
t2.start() ;
}
};
程序运行结果:
F:\网页\李兴华\030902_【第9章:多线程】_线程常用操作方法\代码>java ThreadYieldDemo
线程A运行,i = 0
线程B运行,i = 0
线程A运行,i = 1
线程B运行,i = 1
线程A运行,i = 2
线程礼让:线程B运行,i = 2
线程礼让:线程A运行,i = 3
线程B运行,i = 3
线程A运行,i = 4
线程B运行,i = 4
9-11、线程操作范例
设计要求:设计一个线程操作类,要求可以产生三个线程对象,并可以分别设置三个线程的休眠时间,如下所示:
·线程A,休眠10秒
·线程B,休眠20秒
·线程C,休眠30秒
分析:
线程的实现有两种方式,一种是继承Thread类,另外一种是实现Runnable接口。而且在类中应该存在保存线程名称和休眠时间的两个属性。
第一种操作,继承Thread类:
class MyThread extends Thread{
private int time ;
public MyThread(String name,int time){
super(name) ; // 设置线程名称
this.time = time ; // 设置休眠时间
}
public void run(){
try{
Thread.sleep(this.time) ; // 休眠指定的时间
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(Thread.currentThread().getName() + "线程,休眠"
+ this.time + "毫秒。") ;
}
};
public class ExecDemo01{
public static void main(String args[]){
MyThread mt1 = new MyThread("线程A",10000) ; // 定义线程对象,指定休眠时间
MyThread mt2 = new MyThread("线程B",20000) ; // 定义线程对象,指定休眠时间
MyThread mt3 = new MyThread("线程C",30000) ; // 定义线程对象,指定休眠时间
mt1.start() ; // 启动线程
mt2.start() ; // 启动线程
mt3.start() ; // 启动线程
}
};
第二种:实现Runnable接口
class MyThread implements Runnable{
private String name ;
private int time ;
public MyThread(String name,int time){
this.name = name ; // 设置线程名称
this.time = time ; // 设置休眠时间
}
public void run(){
try{
Thread.sleep(this.time) ; // 休眠指定的时间
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.name + "线程,休眠"
+ this.time + "毫秒。") ;
}
};
public class ExecDemo02{
public static void main(String args[]){
MyThread mt1 = new MyThread("线程A",10000) ; // 定义线程对象,指定休眠时间
MyThread mt2 = new MyThread("线程B",20000) ; // 定义线程对象,指定休眠时间
MyThread mt3 = new MyThread("线程C",30000) ; // 定义线程对象,指定休眠时间
new Thread(mt1).start() ; // 启动线程
new Thread(mt2).start() ; // 启动线程
new Thread(mt3).start() ; // 启动线程
}
};
9-12、同步与死锁
9-12-1、同步
以卖火车票为例,如果现在要是想买火车票的话可以去火车站买或者去各个售票点,但是不管有多少个地方可以买火车票,最终一趟列车的车票数量是固定的,如果把各个售票点理解为各个线程的话,则所有线程应该共同拥有同一份的票数。
class MyThread implements Runnable{
private int ticket = 5 ; // 假设一共有5张票
public void run(){
for(int i=0;i<100;i++){
if(ticket>0){ // 还有票
try{
Thread.sleep(300) ; // 加入延迟
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println("卖票:ticket = " + ticket-- );
}
}
}
};
public class SyncDemo01{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 定义线程对象
Thread t1 = new Thread(mt) ; // 定义Thread对象
Thread t2 = new Thread(mt) ; // 定义Thread对象
Thread t3 = new Thread(mt) ; // 定义Thread对象
t1.start() ;
t2.start() ;
t3.start() ;
}
};
从程序运行结果看,卖出的票数成了负数,说明程序执行时出现了问题。
9-12-2、使用同步问题
要解决资源共享的同步操作问题,可以使用同步代码块及同步方法两种方式完成。
同步代码块:
在代码块上加上“synchronized”关键字的话,则此代码块就称为同步代码块。格式为:
synchronized(同步对象){
需要同步的代码;
}
同步的时候必须指明同步的对象,一般情况下会将当前对象作为同步的对象,使用this表示。
class MyThread implements Runnable{
private int ticket = 5 ; // 假设一共有5张票
public void run(){
for(int i=0;i<100;i++){
synchronized(this){ // 要对当前对象进行同步
if(ticket>0){ // 还有票
try{
Thread.sleep(300) ; // 加入延迟
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println("卖票:ticket = " + ticket-- );
}
}
}
}
};
public class SyncDemo02{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 定义线程对象
Thread t1 = new Thread(mt) ; // 定义Thread对象
Thread t2 = new Thread(mt) ; // 定义Thread对象
Thread t3 = new Thread(mt) ; // 定义Thread对象
t1.start() ;
t2.start() ;
t3.start() ;
}
};
从运行结果可以发现,加入了同步操作后,不会产生负数的情况了,但是,程序的执行效率明显降低很多。
同步方法:
除了可以将需要的代码设置成同步代码块之外,也可以使用synchronized关键字将一个方法声明成同步方法,定义格式为:
synchronized 方法返回值 方法名称(参数列表){
方法体;
}
class MyThread implements Runnable{
private int ticket = 5 ; // 假设一共有5张票
public void run(){
for(int i=0;i<100;i++){
this.sale() ; // 调用同步方法
}
}
public synchronized void sale(){ // 声明同步方法
if(ticket>0){ // 还有票
try{
Thread.sleep(300) ; // 加入延迟
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println("卖票:ticket = " + ticket-- );
}
}
};
public class SyncDemo03{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 定义线程对象
Thread t1 = new Thread(mt) ; // 定义Thread对象
Thread t2 = new Thread(mt) ; // 定义Thread对象
Thread t3 = new Thread(mt) ; // 定义Thread对象
t1.start() ;
t2.start() ;
t3.start() ;
}
};
9-12-3、死锁
1、资源共享的时候需要进行同步操作。
2、程序中过多的同步会产生死锁。
死锁一般情况下就是表示在互相等待,是在程序运行时出现的一种问题。
学习完synchronized关键字后,就可以给出JAVA中方法定义的完整格式了:
访问权限{public | default | protected | private}[final][static][synchronized] 返回值类型 | void 方法名称(参数类型 参数名称,……)[throws Exception1,Exception2]{
[return [返回值 | 返回调用处]];
}
9-13、线程操作案例—生产者与消费者
实例要求:
在线程操作中有一个经典的案例程序——生产者和消费者问题,生产者不断生产,消费者不断取走生产者生产的产品。
在图中非常清楚的表示出,生产者生产出信息之后将其放到一个区域之中,之后消费者从此区域里取出数据。
既然现在的是信息,所以就可以定义一个信息的表示类,生产者和消费者都同时占有这个信息类的引用,那么就可以将生产者和消费者两个线程通过信息类联合在一起。
class Info{
private String name=”lixinhua”;
private String content=”JAVAtecher”;
public void setName(String name){
this.name=name;
}
public void setContent(String content){
this.content=content;
}
public String getName(){
return this.name;
}
public String getContent(){
return this.Content;
}
}
建立生产者类,生产者实现多线程机制。
class Producer implements Runnable{ // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=0;i<50;i++){
if(flag){
this.info.setName("李兴华") ; // 设置名称
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.info.setContent("JAVA讲师") ; // 设置内容
flag = false ;
}else{
this.info.setName("mldn") ; // 设置名称
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.info.setContent("www.mldnjava.cn") ; // 设置内容
flag = true ;
}
}
}
};
生产者生产50次信息,中间为了更好的发现问题,加入了延迟操作。
实现消费者,消费者要不断的取出信息。
class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=0;i<50;i++){
try{
Thread.sleep(90) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.info.getName() +
" --> " + this.info.getContent()) ;
}
}
};
程序运行结果:
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
mldn --> JAVA讲师
李兴华 --> www.mldnjava.cn
李兴华 --> JAVA讲师
以上的代码将之前的两个问题全部暴露出来。
之所以会出现内容不对应的情况,是因为中间加入了延迟操作,所以有可能产生不同步的问题,那么此时,可以使用同步解决设置内容的问题。
class Info{ // 定义信息类
private String name = "李兴华"; // 定义name属性
private String content = "JAVA讲师" ; // 定义content属性
public synchronized void set(String name,String content){
this.setName(name) ; // 设置名称
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.setContent(content) ; // 设置内容
}
public synchronized void get(){
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.getName() +
" --> " + this.getContent()) ;
}
public void setName(String name){
this.name = name ;
}
public void setContent(String content){
this.content = content ;
}
public String getName(){
return this.name ;
}
public String getContent(){
return this.content ;
}
};
class Producer implements Runnable{ // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=0;i<50;i++){
if(flag){
this.info.set("李兴华","JAVA讲师") ; // 设置名称
flag = false ;
}else{
this.info.set("mldn","www.mldnjava.cn") ; // 设置名称
flag = true ;
}
}
}
};
class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=0;i<50;i++){
this.info.get() ;
}
}
};
public class ThreadCaseDemo02{
public static void main(String args[]){
Info info = new Info(); // 实例化Info对象
Producer pro = new Producer(info) ; // 生产者
Consumer con = new Consumer(info) ; // 消费者
new Thread(pro).start() ;
new Thread(con).start() ;
}
};
以上的代码解决了数据的完整性问题,但是依然存在了重复取的问题,既然有重复取,则肯定有重复的设置问题。
并没有达到,设置一个取走一个的功能要求。
如果要想实现以上功能要求,则必须使用Object类完成。
Object类对线程的支持——等待与唤醒
Object类是所有类的父类,在此类中有以下几个方法是对线程操作有所支持的。
方法
类型
描述
1
public final void wait() throws InterruptedException
普能
线程等待
2
public final void wait(long timeout) throws
InterruptedException
线程等待,并指定等待的最长时间,以毫秒为单位
3
public final void wait(long timeout,int nanos) throws InterruptedException
线程等待,并指定等待的最长毫秒及纳秒
4
public final void notify()
唤醒第一个等待的线程
5
public final void notifyAll()
唤醒全部等待的线程
唤醒线程有两个方法:notify()、notifyAll()
一般来说,所有等待的线程会按照顺序进行排列,如果现在使用了notify()方法的话,则会唤醒第一个等待的线程执行,而如果使用了notifyAll()方法,则会唤醒所有的等待线程,哪个线程的优先级高,哪个线程就有可能先执行。
问题解决2——加入等待与唤醒
如果要想让生产者不重复生产,消费者不重复取走,则可以增加一个标志位,假设标志位为boolean型变量,如果标志位的内容为true,则表示可以生产,但是不能取走,如果此时线程执行到了消费者线程则应该等待,如果标志位的内容为false,则表示可以取走,但是不能生产,如果生产者线程运行,则应该等待。
直接修改Info类即可,增加等待与唤醒操作:
class Info{ // 定义信息类
private String name = "李兴华"; // 定义name属性
private String content = "JAVA讲师" ; // 定义content属性
private boolean flag = false ; // 设置标志位
public synchronized void set(String name,String content){
if(!flag){
try{
super.wait() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
}
this.setName(name) ; // 设置名称
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
this.setContent(content) ; // 设置内容
flag = false ; // 改变标志位,表示可以取走
super.notify() ;
}
public synchronized void get(){
if(flag){
try{
super.wait() ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
}
try{
Thread.sleep(300) ;
}catch(InterruptedException e){
e.printStackTrace() ;
}
System.out.println(this.getName() +
" --> " + this.getContent()) ;
flag = true ; // 改变标志位,表示可以生产
super.notify() ;
}
public void setName(String name){
this.name = name ;
}
public void setContent(String content){
this.content = content ;
}
public String getName(){
return this.name ;
}
public String getContent(){
return this.content ;
}
};
class Producer implements Runnable{ // 通过Runnable实现多线程
private Info info = null ; // 保存Info引用
public Producer(Info info){
this.info = info ;
}
public void run(){
boolean flag = false ; // 定义标记位
for(int i=0;i<50;i++){
if(flag){
this.info.set("李兴华","JAVA讲师") ; // 设置名称
flag = false ;
}else{
this.info.set("mldn","www.mldnjava.cn") ; // 设置名称
flag = true ;
}
}
}
};
class Consumer implements Runnable{
private Info info = null ;
public Consumer(Info info){
this.info = info ;
}
public void run(){
for(int i=0;i<50;i++){
this.info.get() ;
}
}
};
public class ThreadCaseDemo03{
public static void main(String args[]){
Info info = new Info(); // 实例化Info对象
Producer pro = new Producer(info) ; // 生产者
Consumer con = new Consumer(info) ; // 消费者
new Thread(pro).start() ;
new Thread(con).start() ;
}
};
此时,才真正的完成了生产者和消费者的正确操作。
9-14、线程生命周期
一个新的线程创建之后通过start()方法进入到运行状态,在运行状态中可以使用yield()进行礼让,但是仍然可以运行,如果现在一个线程需要暂停的话,可以使用suspend()、sleep()、wait(),如果现在线程不需要再执行,则可以通过stop结束(如果run()方法执行完毕也表示结束),或者一个新的线程直接调用stop()方法也可以进行结束。
以上有如下的几个方法:
·suspend():暂时挂起线程
·resume():恢复挂起的线程
·stop():停止线程
因为以上的三个方法都会产生死锁的问题,所以现在已经不建议使用了。
如果现在假设要想停止一个线程的运行该如何操作呢?
通过设置标志位,让线程停止运行。
class MyThread implements Runnable{
private boolean flag = true ; // 定义标志位
public void run(){
int i = 0 ;
while(this.flag){
System.out.println(Thread.currentThread().getName()
+"运行,i = " + (i++)) ;
}
}
public void stop(){
this.flag = false ; // 修改标志位
}
};
public class StopDemo{
public static void main(String args[]){
MyThread my = new MyThread() ;
Thread t = new Thread(my,"线程") ; // 建立线程对象
t.start() ; // 启动线程
try{
Thread.sleep(30) ;
}catch(Exception e){
}
my.stop() ; // 修改标志位,停止运行
}
};