目录
了解程序,进程,线程的概念
在理解创建线程之前,我们应该了解下面程序,进程和线程的基本概念:
- 程序:是为完成特定任务,用某种语言编写的一组指令的集合,既**一段静态代码**,可以理解成是存储在硬盘中的代码,没有加载到内存中。
- 进程:程序一次执行的过程,或是一个正在运行的程序,是动态的。
- 线程:程序内部的一条执行路径。
java中创建线程有两种方式:继承Thread类**和**实现Runnable接口
第一种:继承Thread类:
1. 定义一个继承自Thread的类
2. 重写run方法
3. 创建类对象
4. 调用对象的start方法
//打印100以内能被2整除的整数
class MyThread01 extends Thread{//1.定义一个继承自Thread的类
public void run() {//2. 重写Thread中的run方法
for(int i=0 ; i<=100 ; i++) {
if(i%2==0) {
System.out.println(getName()+":"+i);
//getName()代表当前对象的名字
}
}
}
}
public class MyThreadTest_01 {
public static void main(String[] args) {
MyThread01 mt = new MyThread01();// 3. 创建类对象
mt.start();//4. 调用对象的start方法
//打印100以内能被2整除的整数
for(int i=0 ; i<=100 ; i++) {
if(i%2==0) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
start()方法有两个作用:一:启动了当前线程, 二:调用该线程的run方法.
问题一:能否通过直接调用run方法来启动线程?
答:不能,直接调用run()方法不会启动新的线程,程序执行的顺序和普通的程序一样,先执行run()方法,在执行main方法中的for循环。
问题二:打印0到100内的偶数,能否通过已经调用过start()的线程再此执行start()方法?
答:下面为jdk1.8中Thread的部分源码:
通过源码可知:当一个线程的start()已经被调用后,线程中的threeadStatus将不再等于0,如果再此执行start()方法,那么该方法会抛出IllegalThreadStateException()异常。要想解决这个问题,可以再创建一个类对象,如下:
public class MyThreadTest_01 {
public static void main(String[] args) {
MyThread01 mt = new MyThread01();
mt.start();
//mt.start(),mt不能再次调用start()方法,会抛出异常;
//解决办法:再创建一个MtThread对象
MyThread01 mt1 = new MyThread01();
mt1.start();
for(int i=0 ; i<=100 ; i++) {
if(i%2==0) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
Thread类的常用方法:
run() 通常要重写父类的run方法,将创建的线程要执行的操作声明在此方法中
start() 启动当前线程,并调用线程中的run方法
currentThread() 是静态方法,返回当前执行代码的线程
setName() 为当前的线程设置名字
getName() 获取当前线程的名字
yield() 表示释放当前cpu的执行权
sleep() 是静态方法,强制让当前线程进入阻塞状态,注意:Thread中的run没有抛出异常,所以只能用try...catch,而不能使用throws
join() 在线程a中调用线程b的join(),此时线程a就进入了阻塞状态,知道b完全执行完线程啊a才结束阻塞状态
isAlive() 判断当前线程是否还活着
第二种:实现Runnable接口:
1. 第一步:创建一个实现了Runnable接口的类
2. 第二步:实现Runnable中的run()方法
3. 第三步:创建实现类的对象
4. 第四步:将此类对象作为参数传递到Thread的构造器中去,创建Thread对象
5. 第五步:通过Thread对象调用start()方法,①启动该线程 ②调用该线程的run()方法
class MyThread02 implements Runnable{//第一步:创建一个实现了Runnable接口的类
public void run() {//第二步:实现Runnable中的run()方法
for(int i=0 ; i<=100 ; i++) {//输出100以内被2整除的整数
if(i%2==0) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
public class MyThreadTest_02 {
public static void main(String[] args) {
MyThread02 mythread = new MyThread02();//第三步:创建实现类的对象
Thread t1 = new Thread(mythread);//第四步:将此类对象作为参数传递到Thread的构造器中去,创建Thread对象
t1.start();//第五步:通过Thread对象调用start()方法
}
}
不知你们对于该程序最后一行的t1.start()是否有这样的疑惑:t1.start()启动了当前的线程,并调用该线程中的run方法。调用的run方法应该是Thread类中的run方法,但是为何又能调用到MyThread02这个类中的run方法呢?
要想知道答案,还是得看源码:
原因是:Thread类有一种构造器要接受一个Runnable接口对象,如果这个对象不为空,那么Thread的run()方法就会调用这个接口对象中的run()方法!
Threa与Runnable之间的关系:
通过源码我们可以知道:Thread类实现了Runnable接口。
用图更容易地表示出创建线程的两种方法:
这也就是为什么我们不管用那种方法创建线程,都需要重写run()方法。
比较创建线程的两种方式:
实现Runnable接口的方式相对更好一点
因为java不支持类的多继承,因此没有类的单继承的局限性
实现的方式更适合处理多个线程共享数据的情况(售票问题得以体现)