多线程技术主要包括多线程的概念、实现与控制以及互斥与同步这三个方面内容。 1 线程
1.1线程的概念 线程是程序中一个单一的顺序控制流程,它是程序运行的基本执行单元。线程是比进程还小的单位。线程有它自己的入口和出口,以及一个顺序执行的序列。线程不能独立存在,必须存在于进程中。 线程最大的一个特性是并发执行,多个并发执行的线程称为多线程。 使用线程将会从以下5个方面来改善应用程序: (1)充分利用CPU资源。 (2)简化编程模型 (3)简化异步事件的处理 (4)使GUI更有效率 (5)节约成本 1.2 线程与进程 ·线程与进程的主要区别有: (1)线程的划分尺度小于进程;
(2)进程在执行过程中拥有独立的内存单元,而多个线程只能共享进程的内存单元。 (3)在执行过程中,每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存唉应用程序中,由应用程序提供多个线程执行控制。
(4) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。
(5)一个线程可以创建和撤销另一个线程;同一个进程的多个线程之间可以并发执行。
2 线程的生命周期 一个线程从创建、启动到终止的整个过程程做一个生命周期。在此期间的任何时刻,总是处于下面5中状态中的某一中状态。
(1)创建状态。用new运算符创建一个Thread类或子类的实例对象时,形成的新线程就进入创建状态,但此时还未这个线程分配任何资源,没有真正执行它。
(2)就绪状态。 (3)运行状态 (4)阻塞状态 发生以下情况之一,线程进入阻塞状态: ·调用了该线程的sleep()休眠方法。 ·调用了wait()等待方法。 ·调用了suspend()挂起方法。 ·该线程正在等待I/O操作完成。 (5)终止状态
3 Java多线程的实现方式 Java中有两种方法实现多线程:一是通过Thread类的子类来实现,二是通过
Runnable接口来实现。
3.1 继承Thread类 Thread类综合了Java程序中一个线程需要拥有的属性和方法 参考代码: public class Threaddemo_1 {
public static void main(String[] args) throws InterruptedException {
}
class HelloThread extends Thread{ }
public HelloThread(String s){ super(s); }
//构造方法
//调用父类的构造方法,线程名为s
HelloThread ht = new HelloThread(\"Hello \"); ht.start();
//启动线程
Thread.sleep((int)Math.random()*100);//主线程休眠0~100ms的随
机数
System.out.println(\"Java!\"); }
//必须覆盖Thread类的run()方法,线程启动后将执行该方法 public void run(){
System.out.print(getName()); }
//打印线程名
3.2 实现Runnable接口 通过实现Runnable接口来编写多线程程序,只需要重写run()方法,而且实现Runnable接口的类还可以继承其他类,在这一点上它与定义Thread类的子类实现多线程不同,也是它的优势所在。具体实现步骤如下:
(1)定义一个实现Runnabl接口的类
(2)定义方法run().Runnable接口中有一个空的方法run(),实现它的类必须覆盖此方法。
(3)创建该类的一个线程对象,并以该对象为参数,传递给Thread类的构造方法,从而生成Thread类的一个对象。
(4)通过start()方法启动线程。
参考代码:
public class RunnableDemo_1 {
public static void main(String[] args) throws InterruptedException{
RunThread rt1 = new RunThread(); RunThread rt2 = new RunThread();
//定义RunThread对象rt1 //定义RunThread对象rt2
//以实现Runnable接口的类对象为参数,定义Thread对象 Thread t1 = new Thread(rt1,\"线程1\"); Thread t2 = new Thread(rt2,\"线程2\");
System.out.println(\"main 开始!\"); t1.start();//启动线程1
}
t2.start();//启动线程2
System.out.println(\"main结束!\"); }
class RunThread implements Runnable{ }
//必须覆盖Runnable接口的run()方法,线程启动后将执行该方法 @Override
public void run() {
//打印线程运行
System.out.println(Thread.currentThread().getName() + \"运行\"); //打印线程结束
System.out.println(Thread.currentThread().getName() + \"结束\"); }
4 线程的控制 4.1 线程的优先级与调度
在Java语言中,对一个新建的线程,系统会分配一个默认的线程优先级:继
承创建这个线程的主线程的优先级(一般为普通优先级)。Thread类也提供了方法setPriority()来修改线程的优先级。该方法的参数取值范围为1~10。Thread类提供的优先级静态常量有: ·NORM_PRIOROTY:普通优先级(5)。 ·MIN_PRIOROTY:最低优先级(1)。 ·MAX_PRIORITY:最高优先级(10)。
线程调度策略主要有两种:协作式和抢占式。协作式,是指一个执行单元一旦获得某个资源的使用权,别的执行单元即使优先级再高也无法剥夺。而抢占式高优先级线程可以直接抢占正在执行的低优先级线程的控制权。
Java的线程调度采用的是抢占式策略。高优先级线程优先获得系统控制权。
参考代码:
public class C4_1 { }
class HelloThread extends Thread{
public HelloThread(String s){ //构造方法 super(s); }
//调用父类构造方法,线程名为s
public static void main(String[] args) throws InterruptedException{ }
HelloThread ht = new HelloThread(\"Hello\"); ht.start();
//启动线程
//让ht线程执行,其它线程(main主线程)等待,直到ht线程执行完毕为止 ht.join();
System.out.println(\"Java\");
//必须覆盖thread类的run()方法,线程启动后将执行该方法
}
public void run(){
System.out.print(getName());//打印线程名 }
4.2 线程的同步机制 1.互斥
程序中的多个线程一般是独立运行的,各个线程都有自己的数据和方法。但有时需要在多个程序之间共享一些资源对象,这些资源对象的操作在某些时候必须要在线程间很好地协调,以保证她们的正确使用。不考虑协调性,就可能产生错误。
参考代码:
public class C4_2_1 { {
}
C4_2_1 test = new C4_2_1(); private int Share = 0; public C4_2_1(){
AddThread at = new AddThread(); SubThread st = new SubThread(); at.start();//启动打印Share++线程
st.start();//启动打印Share--线程 }
private class AddThread extends Thread{ }
private class SubThread extends Thread{ }
public static void main(String[] args) throws InterruptedException
public void run(){
System.out.println(\"Share--之前的值:\" + Share); //打印Share--前的值 Share--;
System.out.println(\"Share--之后的值:\" + Share ); public void run(){
System.out.println(\"Share++之前的值:\" + Share); //打印Share++前的值 try {
Thread.sleep(1000);//当前线程睡眠时间1000ms,让出线程控制权 } catch (InterruptedException e) {} Share++;
System.out.println(\"Share++之后的值:\" + Share);
//打印Share++后的值 }
//打印Share--后的值 }
}
在Java语言中,为保证线程对共享资源操作的完整性,用关键字synchronized为共享资源加锁来决解这个问题。这个锁使得共享资源对线程是互斥操作的,称为互斥锁。共享资源加锁后,在任何时刻只能被一个线程访问,这个线程是当前操作该共享资源的线程。
Synchronized可修饰一个代码块或一个方法,使修饰符对象在任一时刻只能有一个线程访问,从而提供了程序的异步执行功能。关键字synchronized的使用方法如下:
·修饰语句块,对语句块加锁,语句块执行完毕,释放该锁。 Synchronized(this){ 语句块 }
·修饰方法,对方法加锁,方法执行完毕,释放该锁。 Public synchronized返回类型 方法名(参数){…} ·修饰类,类中所有方法都是互斥资源
参考代码:
public class C4_2_1 {
private int Share = 0; public C4_2_1(){
AddThread at = new AddThread(); SubThread st = new SubThread(); at.start();//启动打印Share++线程
st.start();//启动打印Share--线程 }
private class AddThread extends Thread{
public void run(){ }
//为该语句块加锁
synchronized(C4_2_1.this){
System.out.println(\"Share++之前的值:\" + Share); //打印Share++前的值 try {
Thread.sleep(1000);//当前线程睡眠时间1000ms,让出线程控制
权
}
} catch (InterruptedException e) {} Share++;
System.out.println(\"Share++之后的值:\" + Share);
//打印Share++后的值 }
private class SubThread extends Thread{
public void run(){
//为语句块加锁
{ }
}
}
synchronized(C4_2_1.this){
System.out.println(\"Share--之前的值:\" + Share); //打印Share--前的值 Share--;
System.out.println(\"Share--之后的值:\" + Share );
//打印Share--后的值 }
public static void main(String[] args) throws InterruptedException }
C4_2_1 test = new C4_2_1();
程序分析:
程序运行结果只有两种可能。线程run()方法中的语句由于受到互斥的保护,将作为一个整体,整体执行完后,才会执行另一个线程中的语句。
注意:synchronized(C4_4_1.this)的使用,synchronized中的参数表示共享变量所在的对象。这里不能用this作为参数,因为this指的是线程AddThread或者SubThread,而不是共享变量所在的类对象。
4.2同步
前面提到的是不同线程对共享资源的互斥访问,它只是要求同一时间只能有一个线程访问共享资源,而没有强调不同线程对共享资源的同步访问。
实现同步需要在这些线程之间相互通信。Java提供了方法wait()、notify()和notifyAll()来使线程之间能够互相交谈。一个线程可以利用某个对象的synchronized()方法进去等待状态,直到其他线程显式地将它唤醒。可以有多个线程进入同一个方法并等待同一个唤醒消息。
参考代码:
public class C4_2_2 {
private boolean IsPrintHello = false; //是否打印Hello public C4_2_2(){
HelloThread ht = new HelloThread(); JavaThread jt = new JavaThread(); ht.start();
//启动打印Hello线程 //启动打印Java线程
jt.start(); }
private class HelloThread extends Thread{
public void run(){
synchronized(C4_2_2.this){ try {
sleep(1000);
//为该语句块加锁
//当前线程休眠1000ms
}
}
}
} catch (InterruptedException e) { }
System.out.print(\"Hello \"); IsPrintHello = true;
//设置打印标志
System.out.println(e.toString());
C4_2_2.this.notifyAll(); //唤醒在互斥对象上等待的所有线程 }
private class JavaThread extends Thread{ }
public static void main(String[] args) { }
C4_2_2 test = new C4_2_2(); public void run(){ }
synchronized(C4_2_2.this){ try { }
}
System.out.println(\"Java!\");
//为该语句块加锁
//当前线程等待,直到notify()或notifyAll()来唤醒 while(!IsPrintHello)C4_2_2.this.wait(); System.out.println(e.toString());
} catch (InterruptedException e) {
下面再举一个使用同步访问共享资源的例子——生产者和消费着的问题。这是一个经典问题。问题要求先生产,后消费,消费完了再生产······ 参考代码:
public class CTest { //主类,测试类 public static void main(String[] args) { }
class ShareDate{
//共享数据类 //共享数据
//取数据的同步方法
private int seq;
// TODO Auto-generated method stub
ShareDate c = new ShareDate();//定义共享数据对象 Producer p1 = new Producer(c);//producer线程 Consumer c1 = new Consumer(c);//consumer线程 p1.start();//启动生产者线程
c1.start();//启动消费者线程 }
private boolean available = false; //是否有数据标志 public synchronized int get(){
}
while(!available){ }
available =false; notify();
//置无数据标志
//通知唤醒其他等待共享数据对象的线程
//返回要取出的数值
try {
wait();
} catch (InterruptedException e) {}
return seq; }
//存放数据的同步方法
public synchronized void put(int value){
while(available){ }
seq = value;
//把共享变量修改为要放置的数据
//置有数据标志
available = true ;
try {
wait(); //有数据时,则等待 } catch (InterruptedException e) {}
notify(); }
//通知唤醒其他等待共享数据对象的线程
class Producer extends Thread{ //生产者线程类 }
class Consumer extends Thread{ private ShareDate sd;
public Consumer(ShareDate c){ }
public void run(){
int value = 0; sd = c;
//消费者线程类
private ShareDate sd; //共享数据对象
public Producer(ShareDate c){ }
public void run(){ }
for (int i = 0; i < 5; i++) { //生产5个 }
sd.put(i); //生产数据i
System.out.println(\"Producer put:\" + i); try { }
sleep((int)(Math.random()*100)); } catch (InterruptedException e) { sd = c;
}
}
for (int i = 0; i < 5; i++) { //消费者5个 }
value = sd.get(); //消费者消费数据
System.out.println(\"consumer got:\" + value);
本节java多线程学习完毕。以上内容均我个人学习笔记,深入学习请查看“JDK_API_1_6_zh_CN.CHM”文档,, java爱好者可以加我群交流共享学习资料。 QQ群号码:219774917 本人QQ:916931772 求javaWeb高手。
新建群。
因篇幅问题不能全部显示,请点此查看更多更全内容