多线程
1.简介
在任何时间点,可以有多个程序同时执行,或者有多个程序逻辑同时执行的能力,称为并发执行。
1.1 线程与进程
进程(Process)指操作系统中一个独立运行的程序。进程也称任务,每个进程拥有独立的内存空间等系统资源,进程间的系统不能互用,所以进程间通信较困难。引申出多任务操作系统。cpu采用分时执行,操作系统的进程队列中每个进程执行一个时间片的时间长度(windows为20ms),保存状态后切换到另一个进程,快速切换产生并行执行的错觉。多核时增加几个进程队列。
线程(Thread)指同一个程序(进程)内部每个单独执行的流程。同一个程序中的线程之间变量是共享的,线程间数据交换变得简单。
无论线程还是进程,都是编程从串行编程(依次执行)进入到并行编程(同时执行)的领域,而在cpu内部实现的原理是按照时间片切换。
1.2 线程优势
多线程程序优势有两个:
1.提高界面程序响应速度
通过使用线程,将界面显示处理交给界面流程执行,而网络通讯交给通讯线程执行,前台、后台分别执行,可以加快整个程序执行速度。
2.充分利用系统资源
通过在一个程序内部同时执行多个流程,可以充分利用cpu等系统资源,从而最大限度发挥硬件的性能。
任何事情都有正反两面,多线程带来不便之处是提高编程难度,同时线程过多时会影响程序执行效率。通过线程池技术可以改善线程过多及回收问题。
1.3线程生命周期
线程在程序中从出现到消亡的各个阶段,在程序中统称为线程的生命周期。
Java中线程类是java.lang.Thread。它封装了线程的概念,并实现部分线程控制方法。
线程的生命周期包括如下阶段:
1.新建状态(New)
线程对象创建完成,但还没有启动。
2.运行状态(Run)
运行状态指线程的正常执行状态,处于该状态的线程在cpu内部执行程序,也就是线程正常运行时的状态
3.阻塞状态(Block)
阻塞状态指线程处于执行状态,但没有获得cpu执行时间,处于cpu外部等待线程执行的状态。
4.死亡状态(Dead)
死亡状态指线程执行结束,释放线程占用的系统资源,结束线程执行的状态。
实际使用中,首先创建一个线程对象,这时线程就处于New新建状态;通过线程对象中单start()方法,使线程进入Run执行状态,开始排队进入cpu执行;根据系统的调度,可能需要线程进入Block阻塞状态,即Run与Block的切换,可能多次;线程执行完毕后即进入Dead死亡状态。
线程执行中,可能需要改变线程的状态。使用线程对象的interrupt()方法中断线程执行,直接进入Dead死亡状态;而yield()方法使线程从运行状态进入阻塞状态。
2.线程实现方式
三种线程实现方式,分别是集成Thread类,实现Runnable接口,Time与TimeTask共用。第一种集成Thread类后不能继承其它类,不利于扩展。第二种常用。
2.1继承Thread类
一个类继承Thread类,则该类就具备了多线程的能力,则该类就可以以多线程的方式进行执行。但是由于Java中类的继承是单重继承,所以该方式受到一定的限制,下面演示这种用法。先定义一个线程类继承Thread,线程代码在run()方法中编写。之后在main方法中创建线程对象,然后用start()方法启动,这样就有两个线程:系统线程和类线程。
package Thread;
publicclass ThreadClass {
publicstaticvoid main(String[] args){
ThreadTest tt=new ThreadTest();
tt.start();
for(int i=0;i<10;i++){
//延时1秒
try {
Thread.sleep(1000);
System.out.println("main:_____"+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ThreadTestextends Thread{
publicvoid run(){
try{
for(int i=0;i<10;i++){
//延时1秒
Thread.sleep(1000);
System.out.println("test: "+i);
}
}catch (Exception e) {
}
}
}
2.2实现Runnable接口
通过实现java.lang.Runnable接口,重写run()方法,类也可以实现线程能力。Java中类可以实现多个接口,所以通用性比继承Thread类要好。
package Thread;
publicclass RunnableClass {
publicstaticvoid main(String[] args){
//通过Runnable类生成Thread类
RunnableTest t1=new RunnableTest();
Thread thread=new Thread(t1);
//线程启动
thread.start();
for(int i=0;i<10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("main:"+i);
}
}
}
class RunnableTestimplements Runnable{
publicvoid run(){
try{
for(int i=0;i<10;i++){
Thread.sleep(1000);
System.out.println("thread:"+i);
}
}catch (Exception e) {
}
}
}
2.3使用Timer和TimerTask组合
Timer类实现的是类似闹钟的功能,也就是定时或者每隔一定时间触发一次线程,它用来调用其它线程。
TimerTask类是抽象类,实现了Runnable接口,该类具备多线程能力。
这种方式中,建立继承TimerTask的线程类,从而间接实现Runnable接口,将多线程执行放在run()方法内。然后通过Timer对象的schedule(a,b)方法启动。
package Thread;
import java.util.Timer;
import java.util.TimerTask;
publicclass Timer_TimerTask_test {
publicstaticvoid main(String[] args){
//create Timer
Timer t=new Timer();
//create TimerTask
MyTimerTask mtt=new MyTimerTask("thread 1 :");
//launch thread,no delay
t.schedule(mtt, 0);
}
}
class MyTimerTaskextends TimerTask{
String string;
public MyTimerTask(String string){
this.string=string;
}
@Override
publicvoid run() {
try{
for(int i=0;i<10;i++){
Thread.sleep(1000);
System.out.println(string+i);
}
}catch (Exception e) {
}
}
}
该类汇总,MyTimerTask类实现了多线程,通过Timer对象t启动。如果想启动多个实例,应该建立多个Timer对象,每个对象启动一个线程类。schedule()方法可以定时启动,可以哪天哪时,延迟多少时间启动都可以。
3.线程实例
3.1定时炸弹
多线程模拟定时炸弹功能。在程序启动以后进行倒计时,当10秒后程序结束,程序运行时可以在控制台输入q控制炸弹线程停止。
在该例中,开启一个系统线程(main方法所在的线程),该线程的作用是启动模拟定时炸弹的线程,并且在控制台接收用户的输入,并判断输入的内容是否为q,如果是则结束模拟定时炸弹的线程,程序结束。代码如下:
package Thread;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
publicclass TimeBomb1 {
publicstaticvoid main(String[] args){
BombThread r1=new BombThread();
Thread t1=new Thread(r1);
t1.start();
System.out.println("bomb start to ...");
BufferedReader br1=new BufferedReader(new InputStreamReader(System.in));
String str;
try {
while(true){
System.out.println("enter q to end bomb.");
str=br1.readLine();//控制台输入
if(str.endsWith("q")){
r1.setStatus();//结束线程
System.out.println("end Success!");
break;//结束循环
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//bomb
class BombThread implements Runnable{
privateintn; //爆炸时间
private Boolean status; //线程终止标志位
public BombThread(){
this.n=10;
this.status=false;
}
//终止方法
publicvoid setStatus(){
this.status=true;
}
@Override
publicvoid run() {
System.out.println("炸弹要爆炸了,run!");
try{
while(!status){
Thread.sleep(1000);//延迟1秒
System.out.println("剩余时间:"+n);
if(n<=0){
status=true;//结束线程
System.out.println("炸了!");
break;
}
n--;//时间减1
}
}catch (Exception e) {
}
}
}
系统线程启动炸弹线程,并接收用户控制台输入,而IO中readLine()是阻塞方法,阻止系统线程的执行,等待用户输入。炸弹类每个1秒输出剩余时间,时间为0时结束线程。这样两个线程就同时工作了,系统线程等待用户输入的同时,模拟定时炸弹的线程继续执行,这样程序就包含了两个同时执行的线程。
关于线程的结束,这里用到线程自然死亡的方式。实际控制线程时,当线程的run()方法执行结束则线程自然死亡,这里通过控制status变量使得线程可以自然结束。
3.2模拟网络数据发送
实际的网络程序开发中,由于网络通讯一般都需要消耗时间,所以网络通讯的内容一般都启动专门的线程进行处理。简单的网络通信程序包括两个进程:处理界面绘制和接收用户输入的系统线程,网络通讯线程。
package Thread;
import java.io.BufferedReader;
import java.io.InputStreamReader;
publicclass netdatasend {
publicstaticvoid main(String[] args){
BufferedReader br2;
String info2;
datasend ds;
try {
//循环读取并启动线程发送,直到quit放弃输入
while(true){
br2=new BufferedReader(new InputStreamReader(System.in));
info2=br2.readLine();
//放弃输入,跳出循环
if(info2.equals("quit")){
break;
}
//启动线程类,发送数据
ds=new datasend(info2);
Thread thread=new Thread(ds);
thread.start();
}
} catch (Exception e) {
}
System.out.println("quit++++++++++++++++++++quit");
}
}
class datasend implements Runnable{
String string;
public datasend(String string){
this.string=string;
}
publicvoid run(){
try {
//模拟的发送延迟,时间长的话,可以多次输入,共同发送
Thread.sleep(1000);
System.out.println("start to send"+string+"______end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
netdatasend类实现接收控制台输入,并在接收到用户输入后启动网络通信线程datasend发送数据,当输入为quit时,结束程序。
4.多线程问题:同步、死锁和优先级
多线程虽然简化程序开发,但也带来了不便之处。核心是如果多个线程同时访问一个资源,例如变量、文件等,如何保证访问安全的问题。在多线程中,会被多个线程同时访问的资源叫做临界资源。
下例演示多个线程访问临界资源时产生的问题。启动两个线程类DataThread的对象,该线程每隔200ms输出临界资源Data类的变量n的值,并将n的值减1。即两个线程类都访问Data对象,并对其属性n进行输出和减1操作。
package Thread;
publicclass multiThread_sameData {
publicstaticvoid main(String[] args){
Data data=new Data();
DataThread dataThread=new DataThread(data,"线程1:");
DataThread dataThread2=new DataThread(data, "线程2:");
Thread thread=new Thread(dataThread);
Thread thread2=new Thread(dataThread2);
thread.start();
thread2.start();
}
}
class Data{
privateintn;
public Data(){
this.n=20;
}
//同步区块,防止线程抢夺资源。
publicsynchronizedvoid show(String name){
System.out.println(name+": "+n);
n--;
}
}
class DataThread implements Runnable{
private Data data;
private String name;
public DataThread(Data data,String name){
this.data=data;
this.name=name;
}
publicvoid run(){
try {
for(int i=0;i<10;i++){
Thread.sleep(200);
data.show(name);
}
} catch (Exception e) {
}
}
}
4.1同步
4.2死锁
4.3优先级
4.4总结