JAVA多线程学习笔记
多线程的概念
程序、进程与线程:
程序:一段静态的代码,它是应用软件执行的蓝本。
进程:程序的一次动态执行过程(动态概念),它对应了从代码加载、执行到执行完毕的完整过程。一个程序可以被多次加载到系统的不同区域分别执行,形成不同的进程。
线程:比进程更小的执行单位(动态概念)。一个进程在执行过程中可产生多个线程,形成多条执行线索。每个进程都有一段专用的内存区,并以PCB(Process Control Block)作为它存在的标志;而一个进程中的所有线程可以共享该进程的同一个地址空间和操作系统资源,并利用这些共享单元实现数据交换、实时通信与必要的同步操作。因此多线程占用系统资源少、线程间通信快。
一、.线程的状态及其转换
1.线程的状态:
2 创建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象属于新建状态。 Thread myThread=new MyThreadClass();
3 就绪:处于创建状态的线程被启动后将进入线程队列等待CPU时间片,此时它已具备了运行条件,一旦被分配了CPU就可脱离创建它的主线程独立开始其生命周期,另外处于阻塞状态的线程被解除阻塞后以及处于运行态的线程时间片到后也将进入就绪态。
4 运行:就绪态的线程被调度并获得CPU时进入运行态,并自动执行run()方法。
5 阻塞:正在执行的线程若被人为挂起或系统原因必须停止运行将进入阻塞状态。如:该线程正等待I/O操作,调用该线程的sleep()方法、wait()方法、suspend()方法等。
6 死亡:正常终止,就是正常运行run()方法后终止;异常终止,如调用stop()方法或destroy()方法终止线程。
二、多线程实现的两种方法
继承Thread类、实现Runnable接口(此方法比较灵活,因为实现Runnable接口的类还可以继承其他类)。
1 继承Thread类
通过定义java.lang包中的Thread类的子类并在子类中重写run()方法。由于java不能多重继承,此方法简单但不灵活。Thread类的构造函数及主要方法如下:
public Thread():产生一个名字自动生成的线程,名字形式为Thread_1、Thread_2、、、;
public Thread(Runnable target):生成一个指定目标对象的线程;
public Thread(String name):生成一个指定名字的线程;
public Thread(ThreadGroup group,Runnable target):生成一个指定线程组和目标对象的线程;
public Thread(ThreadGroup group,String name):生成一个指定线程组和名字的线程。
通过继承Thread类实现多线程示例:ThreadTest.java程序中subThread类继承了Thread类,先定义了一个构造函数调用父类的构造函数给该线程置名,然后重写了run()方法,使线程运行时每输出一个循环变量后休眠一段随机时间,让另一个线程运行,一个线程的run()方法结束前输出该线程的结束信息。
2 实现Runnable接口
Runnable接口只有一个run()方法,要实现此接口就必须定义run()方法的具体内容,方法体内可定义用户要做的操作。然后以这个实现了Runnable接口的类为参数创建Thread类的对象,也就是用Runnable接口的目标对象初始化Thread类的对象,如此就可把用户实现的run()方法继承过来。
程序UseRunnable.java
import java.awt.*;import java.applet.Applet;
public class UseRunnable extends Applet implements Runnable
{Label prompt1=new Label("第一个子线程");
Label prompt2=new Label("第二个子线程");
TextField threadFirst=new TextField(14);
TextField threadSecond=new TextField(14);
Thread thread1,thread2; int count1=0,count2=0;
public void init(){add(prompt1);add(threadFirst);
add(prompt2);add(threadSecond);}
public void start()
{thread1=new Thread(this,"FirstThread");thread2=new Thread(this,"SecondThread");
thread1.start();thread2.start();}
public void stop(){if(thread1.isAlive())thread1.stop();
if(thread2.isAlive())thread2.stop();}
public void run(){String currentRunning;
while(true){try{Thread.sleep((int)(Math.random()*3000));}
catch(InterruptedException e){}
currentRunning=Thread.currentThread().getName();
if(currentRunning.equals("FirstThread"))
{count1++;threadFirst.setText("线程1第"+count1+"次被调度");}
else if(currentRunning.equals("SecondThread"))
{count2++;threadSecond.setText("线程2第"+count2+"次被调度");}
if(count1>10)thread1.stop();
if(count2>10)thread2.stop();}}}
三、 多线程的基本控制
在控制线程从一种状态转入另一种状态时,必须调用正确的方法,否则将产生异常。线程各状态和相互转换时需调用的方法如下:
除了注意状态转换时需调用的方法外,为使多个线程之间能协调工作,必须控制线程的互斥、同步及死锁问题。
互斥:一组并发进程中一个或多个程序段,因共享某一公用资源而导致它们必须以一个不允许交叉执行的单位执行。即不允许两个以上共享该公用资源的并发进程同时进入临界区。
同步:一组并发进程因直接制约而互相发送消息,进行相互合作,互相等待,使得各进程按一定的执行速度执行的过程,称为进程间同步。
死锁:在多个进程并行执行时,当某进程提出资源申请后,使得若干进程在无外力作用下,永远得不到所需要的资源,不能再继续运行的情况。
如生产者与消费者问题:生产的产品存入仓库,消费时从仓库取出产品。当仓库满时再生产将无处可放,而当仓库空时再消费将取不到产品。采用同步策略可协调生产与消费.。
见程序StackTest.java
class stack
{int sip=0; String data[]=new String[6];
public synchronized void push(String strCell)
{while(sip==data.length)
{try
{this.wait();}
catch(InterruptedException e){}
} //while
this.notify();
data[sip]=strCell;
sip++;
}//push()
public synchronized String pop()
{while(sip==0)
{try
{this.wait();}
catch(InterruptedException e){}
}
this.notify();
sip--;
return data[sip];
}
}
class Producer implements Runnable
{stack stackOne;
public Producer(stack s)
{stackOne=s;}
public void run()
{String strTemp=null;
for(int i=0;i<20;i++)
{strTemp=String.valueOf(i+1);
stackOne.push(strTemp);
System.out.println("Produced:"+strTemp);
try
{Thread.sleep((int)(Math.random()*100));}
catch(InterruptedException e){}
}//for
}//run()
}
class Consumer implements Runnable
{stack stackOne;
public Consumer(stack s)
{stackOne=s;}
public void run()
{String strTemp=null;
for(int i=0;i<20;i++)
{strTemp=stackOne.pop();
System.out.println("Consumed:"+strTemp);
try
{Thread.sleep((int)(Math.random()*100));}
catch(InterruptedException e){}
}//for
}//run()
}
public class StackTest
{public static void main(String args[])
{stack s1=new stack();
Runnable producer=new Producer(s1);
Runnable consumer=new Consumer(s1);
Thread p=new Thread(producer);
Thread c=new Thread(consumer);
p.start();c.start();
}
}
程序中对堆栈进行了synchronized处理,其作用是对共享资源加锁,即方法中的所有对象在任何一个时刻只能由一个线程访问。生产前检查堆栈情况,若满则等待(wait())并释放互斥锁,否则可生产;消费前先检查堆栈,若空则等待并释放互斥锁,否则可消费。唤醒wait()采用notify()方法,该方法将随机使等待队列中的一个线程离开等待队列进入就绪状态。也可以在方法体内单独对某个对象加锁。Produser是生产者模型,Consumer是消费者模型,他们实现了Runnable接口,作为目标对象来创建生产者和消费者线程对象。生产者每次随机生产20个数字并压入堆栈,间隔时间为随机间隔0~100ms,靠调用sleep()方法完成。消费者共消费20次,其间隔时间也是随机间隔0~100ms。