在正式介绍线程创建的第二种方法之前,我们接着多线程详解(一),讲一下:对线程的内存图、线程的状态,为下面的学习打下基础,小伙伴们不要急哟!!
一、多线程运行的内存图(ps.博主没有找到合适的画图工具,欢迎大神们贡献啊)
class person extends Thread
{
int i;
private String name;
person(String name)
{
//给线程命名,使用super
super(name);
this.name=name;
}
public void run()
{
show();
}
public void show()
{
for(i=0;i<=10;i++)
{
System.out.println(name+"x="+i+"...name="+Thread.currentThread().getName());
}
}
}
public class demo1{
public static void main(String[] args){
person p1=new person("zhangsan");
person p2=new person("lisi");
p1.start();
p2.start();
System.out.println("出来吧,name="+Thread.currentThread().getName());
}
}
每开启一条线程就多一条运行路径,所以以上述代码为例——共有三条路径
- (1)main线程路径
- (2)run线程路径
- (3)另一条run线程路径
1、每一条路径上独立的进行压栈弹栈,每一条线程上都要自己的栈区,所以互相不影响
2、当一条路劲上的代码运行完,该线程自动结束
3、当main线程提前运行结束后,其他两条线程依旧运行。所以可以看出每条线程之间是完全独立的,当一条线程有异常时,其他的线程继续运行不受影响。
4、为多线程安全分析埋下伏笔(这里不详细讲多线程安全了,以后会更行的哦)
二、线程的状态(重点)
1、运行(使用start方法):具备执行资格和执行权
2、消亡(任务运行完了,自动消亡OR使用stop方法强制消亡):既不具备执行资格也不具备运行资格
3、冻结:
《1》使用sleep(time)方法———时间到了,自动恢复————既不拥有执行权也不拥有执行资格(释放执行权的同时释放执行资格)
《2》使用wait()方法————使用notify()方法唤醒————既不拥有执行权也不拥有执行资格(释放执行权的同时释放执行资格))
解释:如果一个线程处于运行状态,那么CPU就具备执行资格和拥有CPU的执行权,那么什么是执行权?什么又是执行资格呢??
《1》CPU执行资格:CPU可以处理,在CPU的队列中排队等待处理
《2》CPU执行权:正在被CPU处理
下面我们通过举一个例子,解释一下什么是临时阻塞
假设A、B、C、D四个线程都处于执行状态,当A运行的时候,A具备执行资格和执行权,此时其他三个具备执行资格但正在等待执行权,这种状态就是临时阻塞状态
所以,临时阻塞状态———具备执行资格但是不具备执行权
在一个时刻,只有一个线程拥有执行权,而其他运行的线程都是临时阻塞的。
辨析:临时阻塞状态和冻结状态的区别
- (1)执行资格和执行权的不同
- (2)冻结是由程序员控制的而临时阻塞是cpu运行控制的
- (3)临时阻塞的时间往往是极短的,但是冻结的时间是自定义的,相比较而言是长的。
- (4)临时阻塞是运行时的一种状态,所以冻结唤醒后,必须经过运行才能有时间阻塞。
三、创建线程的第二种方式
1、继承Thread类创建方法的局限性
如果原来的类已经继承了其他类,使用第一种方式继承Thread,就存在了多继承,在java中是不允许的
例如:
class person extends fu
{ int i;
private String name;
person(String name)
{
super(name);
this.name=name;
}
public void run()
{
show();
}
public void show()
{
for(i=0;i<=10;i++)
{
System.out.println(name+"x="+i+"...name="+Thread.currentThread().getName());
}
}
}
public class demo1{
public static void main(String[] args){
person p1=new person("zhangsan");
person p2=new person("lisi");
p1.start();
p2.start();
System.out.println("出来吧,name="+Thread.currentThread().getName());
}
}
但是在这种情况下,如果你依旧要使用继承Thread的方法创建线程也是可以的,就是利用再增加一层继承的方法来实现。示例如下:
class fu extends Thread
{
int i;
private String name;
person(String name)
{
super(name);
this.name=name;
}
public void run()
{
show();
}
public void show()
{
for(i=0;i<=10;i++)
{
System.out.println(name+"x="+i+"...name="+Thread.currentThread().getName());
}
}
}
class person extends fu
{
}
public class demo1{
public static void main(String[] args){
person p1=new person("zhangsan");
person p2=new person("lisi");
p1.start();
p2.start();
System.out.println("出来吧,name="+Thread.currentThread().getName());
}
}
从上面的示例我们可以看出,多增加一层继承关系,其实是开发者强制加上去的,而且实现过程也很繁琐,所以我们应该抛弃此方法,重新思考……..
2、使用接口的方法创建线程
我们现在的目的是让person类创建一个线程,但是person类不可以再继承。由于需要扩展person类功能,所有我们很自然的想到使用————接口
创建步骤:
- 《1》定义类实现Runnable接口
- 《2》覆盖接口中的run方法,将线程的任封装到run方法中
- 《3》通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数进行传递。
- 《4》调用线程对象的start方法开启线程
为什么第三步中,要将Runnable接口的子类对象作为Thread类的构造函数进行传递呢?
因为,线程的任务都是封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就明确要运行的任务。
示例代码如下:
//实现Runable接口,
class person implements Runnable
{ int i;
private String name;
person(String name)
{
this.name=name;
}
// 覆盖run方法
public void run()
{
show();
}
public void show()
{
for(i=0;i<=10;i++)
{
System.out.println(name+"x="+i+"...name="+Thread.currentThread().getName());
}
}
}
public class demo1{
public static void main(String[] args){
//这个不是线程对象
// person p1=new person("zhangsan");
// person p2=new person("lisi");
//这个是线程对象
person p=new person("zhangsan");
Thread t1=new Thread(p);
Thread t2=new Thread(p);
t1.start();
t2.start();
System.out.println("出来吧,name="+Thread.currentThread().getName());
}
}
运行结果: