Thread之创建线程(2)

备注:此文章适合新手入门,其中有些命名可能不太规范

接上一篇:Thread类源码注释(1)

这里总结下创建线程的两种方式(其实是三种):

第一种:

/*
* 创建线程的步骤
* (1).定义类继承Thread
* (2).覆写Thread的run方法
* (3).执行类的start()方法--开启线程,执行run()方法
*/
class demo extends Thread{
	private static int a = 1;//静态变量,优于对象创建,在类装载的时候被初始化,为共享资源,属于全局变量
	private String name;

	public demo(String name) {
		//this.name = name;
		super(name);
	}

	public demo() {
	}

	@Override
	public void run() {
			System.out.println("这是demo开启----------------------------------------------");
		//super.run();
		for (;a<300;a++){
			System.out.println(this.getName()+" run"+a);//this.getName()==Thread.currentThread().getName()
			/*
			* static Thread currentThread()---获取当前正在运行的线程对象
			* getName()---获取当前线程名
			*
			* */
		}
		/*
		* 问题:为什么要覆盖run()?
		* 答;Thread类用于描述线程。该类定义了一个功能,用户存储线程要运行的代码,
		* 	 该存储功能就是run()。目的:将自定义代码存储在run()中,让run去执行
		* */
	}
}

public class threadDemo1 {

	public static void main(String[] args) {
		demo d = new demo("Thread--1");//创建线程
		d.start();//执行线程
		demo d1 = new demo("Thread--2");//创建线程
		d1.start();//执行线程
	}


}

用this.getName()替代Thread.currentThread().getName()在这里是可行的,但我们建议使用Thread.currentThread().getName(),因为这样阅读性更好,而且this.getName()放在这里指的是继承Thread类的这个子类,因为父类中有getNmae(),而放在别处this指向的对象则不一定含有getName()。

运行结果如下(截取部分):

这是demo开启----------------------------------------------
Thread--1 run1
Thread--1 run2
Thread--1 run3
Thread--1 run4
Thread--1 run5
Thread--1 run6
Thread--1 run7
Thread--1 run8
Thread--1 run9
Thread--1 run10
Thread--1 run11
这是demo开启----------------------------------------------
Thread--1 run12
Thread--2 run12
Thread--1 run13
Thread--2 run14
Thread--1 run15
Thread--2 run16
Thread--1 run17
Thread--2 run18
Thread--1 run19
Thread--2 run20
Thread--1 run21
Thread--2 run22
Thread--1 run23
Thread--2 run24

这里我们多运行几次发现结果都是不一样的:那么我们思考下为什么会每次打印的结果都不同呢?

为什么这里this.getName()打印的是我们创建的对象时传的String字符串呢?详情请看后续更新文章哦~

 问题:为什么运行结果每一次都不一样?
答:因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。
假如电脑为单核CPU,在某一时刻,只有一个程序在运行。只是cpu在
做着快速的切换,以达到看上去是同时运行的效果。我们可以先影响
把多线程的运行看做为在互相抢夺cpu的执行权。多线程的特性:随机性。
谁抢到谁执行。

这里呢,还有一个弊端:我们把公共资源a做成static的,而static的生命周期比较长,它伴随着类主动加载的时候就会存在(浪费资源),而类销毁了,在内存中被static修饰的a也不一定会被销毁,因为它不是放在这个类的新建对象信息所属的堆或栈中的。而是放在独立的内存空间中的。

那么我们接下来看第二种实现方式:

/*

* 步骤:
* (1)定义类实现Runnable接口
* (2)覆盖Runnable接口中的run方法
*		将线程要运行的代码存放在run方法中
* (3)通过Thread类简历线程对象
* (4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
* 		为什么将Runnable接口的子类对象传递给Runnable接口的子类对象?
* 			因为自定义的run方法所属的对象是Runnable接口的子类对象,
* 			所以要让线程去指定指定对象的run你方法,就必须明确该run方法所需对象
* (5)调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
*
*/

class demo2 implements Runnable{
	private int a = 1;

	@Override
	public void run() {
		System.out.println("这是demo2开启----------------------------------------------");
		//super.run();
		for (;a<300;a++){
			System.out.println(Thread.currentThread().getName()+" run"+a);
//这里this.getName()!=Thread.currentThread().getName()哦
			/*
			 * static Thread currentThread()---获取当前正在运行的线程对象
			 * getName()---获取当前线程名
			 *
			 * */
		}
		
	}
}

public class ThreadDemo2 {

	public static void main(String[] args) {
		demo2 d = new demo2();//创建线程
		new Thread(d,"Thread-1").start();//执行线程
		//demo2 d1 = new demo2();//创建线程
		new Thread(d,"Thread-2").start();//执行线程
	}


}

运行结果如下(截取部分):

Thread-1 run212
Thread-2 run213
Thread-1 run214
Thread-2 run215
Thread-1 run216
Thread-2 run217
Thread-2 run219
Thread-1 run218
Thread-2 run220
Thread-1 run221
Thread-2 run222
 

那么二者的有什么不一样的地方呢???

1.我们知道java是仅支持单继承的,当然java很聪明的用了实现来解决这个问题,我们知道当在有些项目中一般会抽取一些公共的 实体类的父类,然后让别的实体类去继承该类,那么如果我们继承了Thread类时无法在继承实体类父类的。而通过implements Runnable 这样我们就可以再继承实体类父类了。

2.这里的实现runnable里的main方法内,我们是在调用线程的时候往Thread里传的是同一个对象,让多个线程处理同一资源,也更好的体现了面向对象的思想。这也是很多教程里推荐runnable的原因之一。

另外一个小问题是我们平时在使用main函数时,当它运行时是不是也是一个线程呢,如果是那它和一般我们创建的线程有什么不同的??

这里我们在继承Thread来创建程序的demo上修改下案例:


/*
* 创建线程的步骤
* (1).定义类继承Thread
* (2).覆写Thread的run方法
* (3).执行类的start()方法--开启线程,执行run()方法
*
* 问题:为什么运行结果每一次都不一样?
* 答:因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。
* 假如电脑为单核CPU,在某一时刻,只有一个程序在运行。只是cpu在
* 做着快速的切换,以达到看上去是同时运行的效果。我们可以先影响
* 把多线程的运行看做为在互相强度cpu的执行权。多线程的特性:随机性。
* 谁抢到谁执行。
* */
class demo extends Thread{
	private static int a = 1;//静态变量,优于对象创建,在类装载的时候被初始化,为共享资源
	private String name;

	public demo(String name) {
		//this.name = name;
		super(name);
	}

	public demo() {
	}

	@Override
	public void run() {
			System.out.println("这是demo开启----------------------------------------------");
		//super.run();
		for (;a<300;a++){
			try {
				Thread.sleep(1000L*30);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(this.getName()+" run"+a);//this.getName()==Thread.currentThread().getName()
			/*
			* static Thread currentThread()---获取当前正在运行的线程对象
			* getName()---获取当前线程名
			*
			* */
		}
		/*
		* 问题:为什么要覆盖run()?
		* 答;Thread类用于描述线程。该类定义了一个功能,用户存储线程要运行的代码,
		* 	 该存储功能就是run()。目的:将自定义代码存储在run()中,让run去执行
		* */
	}
}

public class threadDemo1 {

	public static void main(String[] args) {
		demo d = new demo("Thread--1");//创建线程
		d.start();//执行线程
		demo d1 = new demo("Thread--2");//创建线程
		d1.start();//执行线程
        for(int a = 0;a<300;a++){
			try {
				Thread.sleep(1000L*5);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}


}

(注意:这里我只加了一串休眠代码,就是Thread.sleep(),它的作用就是当线程读到这串代码时,自己进入休眠状态,让出CPU资源,但是不会释放资源,类似于占座。。。)

那么我们现在此段代码,然后我们打开命令行界面输入jps---默认查看本机jvm进程

然后找到我们的threadDemo1的ID

输入jconsole 进程ID,进入管理界面(也可以直接在搜索框输入'jconsole'进入管理界面)

查看线程,

这里我们可以清楚的看到线程的执行情况,这些线程又分为守护线程和非守护线程(这里我们后面文章会再说明二者的区别),值得说下的是Finalizer是GC(垃圾回收)的。

有关第三种创建线程的方式:请自行百度--“通过Callable和Future创建线程”,这里我们说下,runnable是一个接口,只是存放Thread所需的执行单元,我们以后的关注点还是会在Thread本身。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值