Java线程及多线程技术及应用

Posted 未名胡

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java线程及多线程技术及应用相关的知识,希望对你有一定的参考价值。

1线程基本概念

1、进程和线程的基础知识

  1.  进程:运行中的应用程序称为进程,拥有系统资源(cpu、内存)
  2.  线程:进程中的一段代码,一个进程中可以哦有多段代码。本身不拥有资源(共享所在进程的资源)

         在java中,程序入口被自动创建为主线程,在主线程中可以创建多个子线程。

             区别: 1、是否占有资源问题

                          2、创建或撤销一个进程所需要的开销比创建或撤销一个线程所需要的开销大。

                          3、进程为重量级组件,线程为轻量级组件

 

        3、多进程: 在操作系统中能同时运行多个任务(程序)

        4、多线程: 在同一应用程序中有多个功能流同时执行

2、线程的主要特点

  1. 不能以一个文件名的方式独立存在在磁盘中;
  2. 不能单独执行,只有在进程启动后才可启动;
  3. 线程可以共享进程相同的内存(代码与数据)。

3、线程的主要用途

  1. 利用它可以完成重复性的工作(如实现动画、声音等的播放)。
  2. 从事一次性较费时的初始化工作(如网络连接、声音数据文件的加载)。
  3. 并发执行的运行效果(一个进程多个线程)以实现更复杂的功能

4、多线程(多个线程同时运行)程序的主要优点

  1. 可以减轻系统性能方面的瓶颈,因为可以并行操作;
  2. 提高CPU的处理器的效率,在多线程中,通过优先级管理,可以使重要的程序优先操作,提高了任务管理的灵活性;另一方面,在多CPU系统中,可以把不同的线程在不同的CPU中执行,真正做到同时处理多任务。

2 线程创建与启动

1、与线程编程有关的一些概念

       创建方式: 1 继承java.lang.Thread类    2 实现java.lang.Runnable接口

           线程体:public void run()方法,其内的程序代码决定了线程的行为和功能。

       线程启动: public void start () , 线程启动后,需要获取cpu才能自动调用run()运行。

       线程休眠: public void sleep(long ms), 线程将暂停,放弃cpu

2、利用继承Thread类创建线程的示例

package com.px1987.j2se.thread.base;
/**通过Thread类实现多线程 定义一个Thread的子类并重写其run方法.*/
public class MyThread extends Thread {
	@Override
	public void run() {
		while (true) {
			System.out.println("invoke MyThread run method");
		}
	}	
	public static void main(String[] args) {  // main方法测试线程的创建与启动		
		MyThread myThread = new MyThread();	// 实例化MyThread的对象		
		myThread.start();	// 调用myThread对象的start方法启动一个线程
	}
}
3、利用实现Runable接口创建线程的示例
package com.px1987.j2se.thread.base;
/**通过Runable接口实现多线程 定义MyRunable类实现Runnable接口,并实现接口中的run方法。*/
public class MyRunable implements Runnable {
	public void run() {
		while (true)
			System.out.println("invoke MyRunable run method");
	}	
	public static void main(String[] args) {	// main方法测试线程的创建与启动
		// 建立MyRunable类的对象,以此对象为参数建立Thread类的对象
		Thread thread = new Thread(new MyRunable());		
		thread.start();	// 调用thread对象的start方法启动一个线程
	}
}

3 线程的状态控制

1、新建状态

用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)。

2、就绪状态

处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列,等待系统为其分配CPU。等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。

3、死亡状态

死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个:

      一个是正常运行的线程完成了它的全部工作

      另一个是线程被强制性地终止,如通过执行stop或destroy方法来终止一个线程。

                       Method stop() & destroy() in the class Thread is deprecated。

      当一个线程进入死亡状态以后,就不能再回到其它状态了。 让一个Thread对象重新执行一次的唯一方法,就是重新产生一个Thread对象。

4、体现线程状态转变的代码示例

package com.px1987.j2se.thread.base;
public class MyRunable1 implements Runnable {
	public void run() {
		while (true)
			System.out.println("invoke MyRunable run method");
	}
	public static void main(String[] args) {
		Thread thread = new Thread(new MyRunable());	// 新生状态		
		thread.start();		// 就绪状态,获得CPU后就能运行
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread.stop();		// 死亡状态
	}
}

通过查API可以看到stop方法和destory方法已经过时了,所以不能再用,那要怎样做才能强制的销毁一个线程呢?

 

1、在run方法中执行return 线程同样结束

2、可以在while循环的条件中设定一个标志位,当它等于false的时候,while循环就不在运行,这样线程也就结束了。代码为实现的代码示例:

package com.px1987.j2se.thread.StateControl;
public class MyRunable2 implements Runnable {    
	private boolean isStop;	//线程是否停止的标志位	
	public void run() {
		while (!isStop)
			System.out.println("invoke MyRunable run method");
	}	
	public void stop(){		//终止线程
		isStop=true;
	}
	public static void main(String[] args) {
		MyRunable myRunable=new MyRunable();
		Thread thread = new Thread(myRunable);
		thread.start();
		try {
			Thread.sleep(5000);
		}
catch (InterruptedException e) {
			e.printStackTrace();
		}
		myRunable.stop();	//正确的停止线程的方法
	}
}

5、阻塞状态

处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。

在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。有三种方法可以暂停Threads执行:

(1)sleep方法

可以调用Thread的静态方法:publicstatic void sleep(long millis) throws InterruptedException 使得当前线程休眠(暂时停止执行millis毫秒)。由于是静态方法,sleep可以由类名直接调用:Thread.sleep(…)。下面为代码示例:

package com.px1987.j2se.thread.p5;
import java.util.Date;
import java.text.SimpleDateFormat;
class SleepTest implements Runnable {
	private static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
	public void run() {
		System.out.println("child thread begin");
		int i = 0;
		while (i++ < 5) {
			System.out.println(format.format(new Date()));
			try {
				Thread.sleep(5000);
			}
catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("child thread dead at: " + format.format(new Date()));
	}
	public static void main(String[] args) {
		Runnable r = new SleepTest();
		Thread thread = new Thread(r);
		thread.start();
		try {
			Thread.sleep(20000);
		}
 catch (InterruptedException e) {
			e.printStackTrace();
		}
		thread.interrupt();
		System.out.println("main method dead!");
	}
}

该程序的运行结果如下:

child thread begin

2009-02-0604:50:29

2009-02-0604:50:34

2009-02-0604:50:39

2009-02-0604:50:44

main method dead!

java.lang.InterruptedException:sleep interrupted

    at java.lang.Thread.sleep(Native Method)

    at com.px1987.j2se.thread.p5.Thread4.run(Thread4.java:17)

    at java.lang.Thread.run(Unknown Source)

2009-02-0604:50:49

child thread deadat: 2009-02-06 04:50:54

(2)yield方法

让出CPU的使用权,从运行态直接进入就绪态。下面为代码示例:

package com.px1987.j2se.thread.StateControl;
class Thread5 implements Runnable {
	private String name;
	Thread5(String s) {
		this.name = s;
	}
	public void run() {
		for (int i = 1; i <= 50; i++) {
			System.out.println(name + ": " + i);
			if (i % 10 == 0) {
				Thread.yield();
			}
		}
	}
}
package com.px1987.j2se.thread.StateControl;
public class YieldTest {
	public static void main(String[] args) {
		Runnable r1 = new Thread5("S1");
		Runnable r2 = new Thread5("S2");
		Thread t1 = new Thread(r1);
		Thread t2 = new Thread(r2);
		t1.start();
		t2.start();
		try {
			Thread.sleep(2);
		}
catch (InterruptedException e) {
			e.printStackTrace();
		}
 		System.out.println("main method over!");
	}
}

该程序的部分运行结果如下:

S1: 20

S2: 7

S2: 8

S2: 9

S2: 10

S1: 41

S1: 42

S1: 43

S1: 44

S1: 45

S1: 46

S1: 47

S1: 48

S1: 49

S1: 50

S2: 11

S2: 12

(3)join方法

当某个(A)线程等待另一个线程(B)执行结束后,才继续执行时,使用join方法。A的 run方法调用b.join()。下面为代码示例。

package com.px1987.j2se.thread.join;
class FatherThread implements Runnable {
	public void run() {
		System.out.println("爸爸想抽烟,发现烟抽完了");
		System.out.println("爸爸让儿子去买包红塔山");
		Thread son = new Thread(new SonThread());
		son.start();
		System.out.println("爸爸等儿子买烟回来");
		try {	    //join含义:等待son线程执行完毕,father线程才继续执行
			son.join();
		} 
catch (InterruptedException e) {
			System.out.println("爸爸出门去找儿子跑哪去了");
			System.exit(1);
		}
		System.out.println("爸爸高兴的接过烟开始抽,并把零钱给了儿子");
	}
}
package com.px1987.j2se.thread.join;
class SonThread implements Runnable {
	public void run() {
		String tabs="\t\t\t\t\t\t";
		System.out.println(tabs+"儿子出门去买烟");
		System.out.println(tabs+"儿子买烟需要10分钟");
		try {
			for (int i = 0; i < 10;) {
				Thread.sleep(1000);
				System.out.println(tabs+"儿子出去第" + ++i + "分钟");
			}
		} 
catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(tabs+"儿子买烟回来了");
	}
}
package com.px1987.j2se.thread.join;
public class JoinTest {
	public static void main(String[] args) {
		System.out.println("爸爸和儿子的故事");
      	Thread father = new Thread(new FatherThread());
		father.start();
//		try {
//			Thread.sleep(5000);
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}
//		father.interrupt();
	}
}

该程序的运行结果如下:

爸爸和儿子的故事

爸爸想抽烟,发现烟抽完了

爸爸让儿子去买包红塔山

爸爸等儿子买烟回来

                     儿子出门去买烟

                     儿子买烟需要10分钟

                     儿子出去第1分钟

                     儿子出去第2分钟

                     儿子出去第3分钟

                     儿子出去第4分钟

                     儿子出去第5分钟

                     儿子出去第6分钟

                     儿子出去第7分钟

                     儿子出去第8分钟

                     儿子出去第9分钟

                     儿子出去第10分钟

                     儿子买烟回来了

爸爸高兴的接过烟开始抽,并把零钱给了儿

当时间来到儿子出去买烟的时候,Father线程调用interrupt方法就会打断son线程的正常执行,从而father线程也就不必等待son线程执行完毕再行动了,运行结果如下:

爸爸和儿子的故事

爸爸想抽烟,发现烟抽完了

爸爸让儿子去买包红塔山

爸爸等儿子买烟回来

                     儿子出门去买烟

                     儿子买烟需要10分钟

                     儿子出去第1分钟

                     儿子出去第2分钟

                     儿子出去第3分钟

                     儿子出去第4分钟

爸爸出门去找儿子跑哪去了

4线程的调度和优先级

1、线程的基本信息

方  法

功        能

isAlive()

判断线程是否还“活”着,即线程是否还未终止。

getPriority()

获得线程的优先级数值

setPriority()

设置线程的优先级数值

setName()

给线程一个名字

getName()

取得线程的名字

currentThread()

取得当前正在运行的线程对象,也就是取得自己本身


2、操作线程的基本信息代码示例

package com.px1987.j2se.thread.priority;
public class ThreadInfoTest {
	public static void main(String[] argc) throws Exception {
		Runnable r = new MyThread();
		Thread t = new Thread(r, "Name test");
		t.start();
		System.out.println("name is: " + t.getName());
		Thread.currentThread().sleep(5000);
		System.out.println(t.isAlive());
		System.out.println("over!");
	}
}
class MyThread implements Runnable {
	public void run() {
		for (int i = 0; i < 100; i++)
			System.out.println(i);
	}
}

该程序的运行结果如下:

name is: Name test

0

1

2

3

. . .

97

98

99

false

over!

 

3、线程的优先级

(1)优先级(共10级):

它们决定线程执行的先后次序(优先级高者先执行)并可以通过Thread类中的setPriority()和getPriority()方法来改变和获取优先级。典型的优先级码

Thread.MIN_PRIORITY (1级)

Thread.MAX_PRIORITY(10级)

Thread.NORM_PRIORITY(5级)

(2)调度规则

Java是不支持线程时间片轮换的调度模型,而采用的是线程优先级高低的抢占调度模型。具有高优先级的线程可以抢占低优先级线程运行的机会。高优先级的线程将始终获得线程执行时间。但是这也不是绝对的,java线程调度器有可能会调用长期处于等待的线程进行执行,所以不要依靠线程的高优先级抢占模型去完成某些功能。

Java线程调度器支持不同优先级线程的抢先方式,但其本身不支持相同优先级线程的时间片轮换。但是如果java运行时系统所在的操作系统(如windows2000)支持时间片的轮换,则线程调度器就支持相同优先级线程的时间片轮换。

(3)代码示例

package com.px1987.j2se.thread.priority;
public class ThreadPriorityTest {
	public static void main(String[] args) {
		Thread t1 = new Thread(new MyThread2(), "t1");
		Thread t2 = new Thread(new MyThread2(), "t2");
		t1.setPriority(1);
		t2.setPriority(10);
		t1.start();
		t2.start();
	}
}
class MyThread2 extends Thread {
	public void run() {
		for (int i = 0; i < 10; i++) {
		    System.out.println(Thread.currentThread().getName() + ": " + i);
		    yield();
		}
	}
}

该程序的运行结果如下:

 

t1: 0

t2: 0

t2: 1

t2: 2

t2: 3

t2: 4

t2: 5

t2: 6

t2: 7

t2: 8

t2: 9

t1: 1

t1: 2

t1: 3

t1: 4

t1: 5

t1: 6

t1: 7

t1: 8

t1: 9


以上是关于Java线程及多线程技术及应用的主要内容,如果未能解决你的问题,请参考以下文章

Java NIO :如何为通道注册多个事件及多线程处理 Accetp 请求

Java NIO :如何为通道注册多个事件及多线程处理 Accept 请求

Java NIO :如何为通道注册多个事件及多线程处理 Accept 请求

Java多线程学习笔记— “如何理解线程及多线程”

python http请求及多线程应用

Linux网络编程基础及多线程并发案例