最近在使用SOA的模式重构高校平台,在考试系统模块,期望使用上多线程,之前对多线程有过一些了解,不过具体的实现是在.net平台下的。虽然一年前接触过j2se的关于多线程的一些知识,但是感觉还是不够,于是最近,也算是在项目的驱动下,抽时间继续学习了一些java的多线程,这里拿出来分享一下。
一、多线程基础知识:
对于多线程的基础知识,这里不做赘述,只是简单的交代两句。
- 对于单核的cpu来说,多线程程序,本质也是单线程的,只是cpu在不停的切换。
- 线程的优先级代表的是相关线程抢占cpu的频率。
- 多线程编程不同于多核编程(多核编程控制cpu运行)。
- 线程运行在进程中,可以看做是进程的一些个小单元。
二、关于线程的重点:
- 线程的创建
- 线程的五种状态
- 线程同步
- 线程间的通信
- 生产者消费者问题
- jdk5.0以后对线程的优化
三、创建线程的两种方法:
- 继承Thread类
/**
* 方法一:
* 继承Thread类创建线程
* @author LiMin
*
*/
public class CreateThread extends Thread {
private String name;
public CreateThread(){
}
public CreateThread(String name){
/*this.name=name;*/
//调用父类的给线程命名的方法
super(name);
}
public void run(){
for(int x =0;x<60;x++){
/*System.out.println(this.name+ " run -----"+ x);*/
/*System.out.println(this.getName()+ " run -----"+ x);*/
//使用Thread的静态方法获取线程名称Thread.currentThread().getName()是标准通用方式
/*System.out.println(Thread.currentThread().getName()+ " run -----"+ x);*/
System.out.println((Thread.currentThread()==this)+"....."+this.getName()+ " run -----"+ x);
}
}
}
调用:
public class ThreadTest {
/**
* @param args
*/
public static void main(String[] args) {
CreateThread t1 = new CreateThread("one----");
CreateThread t2= new CreateThread("two+++++");
t1.start();
t2.start();
for(int x =0;x<60;x++){
System.out.println("main-----"+ x);
}
}
}
程序比较简单,不多解释,重点注意:
- 虚拟机定义主线程的main方法,Thread类的run方法,只是一个代码存储区。
- start是底层的一个方法,调用了相应线程的run方法
- run方法,并不是多线程的,只是一个对象的调用,主线程会等待这个对象的方法执行完后,再继续。
- 系统会为每一个线程对象创建一个内存空间,所以,这里的for循环中的x都是一个独立的,不同的线程是互不影响的。
-
除了售票以外,jvm还要调用dos的打印窗口,因为是多核的cpu, 2号票已经出来了,但是还没有打印,这时候1号票也已经卖出了,结果先打印的是1号票,后打印1号票。
可以使用下面的程序验证run和start方法的区别:
public class ThreadDemo {
public static void main(String[] args){
CreateThread d = new CreateThread();
//开启线程,并执行该线程的run方法
d.start();
//仅仅是对象调用方法。而线程创建了,并没有运行。
/* d.run();*/
for(int x =0;x<60;x++){
System.out.println("Hello word !-----"+ x);
}
}
}
- 实现Runnable接口 (多个线程卖票)
创建线程:
package TicketRunnable;
public class Ticket2 implements Runnable{ //extends Thread{
//如果是静态的话,所有的线程会共享这100张票
/*private static int ticket =100;*/
//如果不是静态的话,每一个线程对象都会创建这个ticket对象,也即每个线程拥有100张票卖。
//这时候,如果还是采用继承Thread类的方法就不行了,需要使用实现Runnable接口创建线程
private int ticket =1000;
Object obj= new Object();
public void run() {
//在这里声明的对象是无效的
/*Object obj= new Object(); */
while(true){
synchronized (obj) {
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+".....sale : "+ ticket--);
}
}
}
}
}
调用:
package TicketRunnable;
public class TicketDemo2 {
/**
* @param args
*/
public static void main(String[] args) {
Ticket2 t = new Ticket2();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
重点注意:
- 将多线程要运行的代码存放在run方法中。
- 通过Thread类建立线程对象。
- 在创建线程对象的时候,就指定run方法所属的对象。
将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数,这是因为:自定义的run方法所属的对象是Runnable接口的子类对象,所以,要让线程去指定对象的run方法,就必须明确run方法所属对象。
- 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
对比:
java是单类继承,使用Thread类继承,有很大的局限。使用Runnable,既不影响,你要开发的类的继承关系,同时也可以满足多线程的扩展,所以,提倡使用接口实现的方式。
四、线程的五种状态:
一个线程可以有:创建、运行、临时阻塞、冻结(又分为睡眠和等待两种状态)、消亡五种状态。认识这五种状态是认识多线程的基础。看下图:
冻结:又分为睡眠和等待两种状态。
调用了start方法后,还在等待cpu来处理启动,这个时候的线程处于临时状态。
对应的一些常见的方法汇总成一个表格:
线程方法名称 | 是否释放同步锁 | 是否需要在同步的代码块中调用 | 方法是否已废弃 | 是否可以被中断 |
sleep() | 否 | 否 | 否 | 是 |
wait() | 是 | 是 | 否 | 是 |
suspend |
|
| 是 |
|
resume() |
|
| 是 |
|
join() |
|
| 否 | 是 |
总结:
这里使用具体的实例介绍了两种创建线程的方法,并给出了两种策略的优劣,最后得出,一般使用实现Runnable接口的方法。
调用线程的不同方法会在线程的五种状态:创建、运行、临时阻塞、冻结之间切换。了解线程的方法和状态,是线程同步(锁:代码块、函数)以及线程间通信的基础。
下一篇博客会介绍几种线程同步的策略以及相关的锁。