基本概念
进程:正在进行中的程序
线程:就是进程中一个负责程序执行的控制单元(执行路径)
一个进程中可以有多个执行路径,称之为多线程
一个进程至少要有一个线程
开启多个线程是为了同时运行多部分代码
每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
(之前也概述过多线程相关内容,见http://blog.csdn.net/megustas_jjc/article/details/52205145)
多线程好处:解决了多部分同时运行的问题
弊端:线程太多会让效率降低
其实应用程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的。
JVM启动时就启动了多个线程,至少有两个线程可以分析出来:
1、执行main函数的线程(该线程的代码都定义在main函数中)
2、负责垃圾回收的线程(代码定义在垃圾回收器中(底层))
class Demo extends Object{
public void finalize(){
System.out.println("demo ok");
}
}
public class ThreadDemo {
public static void main(String[] args) {
new Demo();
new Demo();
new Demo();
System.gc();//运行垃圾回收器,告诉垃圾回收器可以回收垃圾了,但是何时运行不一定,垃圾回收器是守护线程
System.out.println("Main Thread");//主线程
}
}
为什么使用多线程的一个demo
我们先看如下的一个例子:
class Demo extends Object{
private String name;
Demo(String name){
this.name = name;
}
public void show(){
for (int i=0;i<99999999;i++){
System.out.println(name+"...i="+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Demo d1 = new Demo("小李");
Demo d2 = new Demo("xiaoqiang");
d1.show();
d2.show();
}
}
程序在运行时,栈内存的状态是,先main()函数进栈,之后d1.show()方法进栈,执行结束之后d1.show()出栈,d2.show()方法进栈执行,执行结束出栈,之后main()出栈,因此d2.show()方法在d1.show()方法之后执行,但是此处等待d1.show()方法执行结束需要等待很久,因此我们可以为d2.show()方法再创建一个线程进行执行,这样cpu就在两个方法之间切换执行了,而不必等待d1.show()方法执行结束才开始。
多线程的创建方式—继承Thread类
创建线程方式一:继承Thread类
步骤:
1、定义一个类继承Thread类
2、覆盖Thread类中的run方法
3、创建线程对象,即直接创建Thread类的子类对象
4、调用start方法开启线程,并调用线程的任务run方法并执行
class Demo extends Thread{
private String name;
Demo(String name){
this.name = name;
}
public void run(){
show();
}
public void show(){
for (int i=0;i<9;i++){
System.out.println(name+"...i="+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
直接创建一个线程对象,创建线程的目的是开启一条执行路径去运行指定代码和其他代码实现同时运行
jvm创建的主线程的任务都定义在主函数中,而自定义的线程它的任务在哪?
Thread类用于描述线程,自定义线程的任务通过Thread类中的run方法来实现,就是说run方法就是封装自定义线程运行任务的函数
run方法中定义的就是线程要运行的任务代码,
开启线程是为了运行指定代码,因此需要继承Thread并覆写run方法,将运行的代码定义在run方法中即可,而不是 Thread t1 = new Thread();
*/
Demo d1 = new Demo("小李");
Demo d2 = new Demo("xiaoqiang");
d1.run();
d2.run();
}
}
注意此时认识按照顺序执行,因为调用run()方法与常规调用方法一样,要通过start()方法来开启线程
start()方法的作用:使线程开始执行,Java虚拟机调用该线程的run方法
class Demo extends Thread{
private String name;
Demo(String name){
this.name = name;
}
public void run(){
show();
}
public void show(){
for (int i=0;i<9;i++){
System.out.println(name+"...i="+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Demo d1 = new Demo("小李");
Demo d2 = new Demo("xiaoqiang");
d1.start();
d2.start();
System.out.println("over"+);
}
}
获取线程名字
实际上是三个线程:主线程,线程d1,线程d2
可以通过Thread类中的getName()方法来获取线程对象的名称,线程创建名称即完成,因此无论是否开启,都是不同名字,例如:
System.out.println(name+"...i="+i+"...name"+getName());
在d1.start();d2.start();与d1.run(); d2.run();的情况下,打印相同名字,都是Thread0与Thread1.
why:
当我们通过new方法创建Thread对象时,实际就已经完成了Thread的命名,通过查看Thread类的构造函数的源码可知:
public Thread(){
init(null,null,"Thread-"+nextThreadNum(),0);
}
currentThread():返回当前正在执行的线程对象的引用
System.out.println(name+"...i="+i+"...name"+Thread.currentThread().getName());
start方法与run方法区别
对于调用start()方法结果:
对于调用run()方法结果:
start方法与run方法区别:
start()方法来启动一个线程,此刻线程处于就绪状态,而非运行状态,意味着这个线程可以被JVM来调度执行。在调度过程中,JVM通过调用线程类的run()方法来完成实际的操作,run()方法结束后,线程终止。
start()方法能够异步的调用run()方法,但是直接调用run()方法是同步的,因此无法达到多线程的目的。
自定义线程名字:调用Thread的带参构造方法Thread(String name)即可,我们可以对Demo类的构造函数做如下修改:
Demo(String name){
super(name);
}
主函数在执行过程中又为创建的新的线程开启新的路径,即都有着自己独立的空间,在哪个线程中运行的,就在哪个线程对应的空间进行进栈出栈,当一个线程执行完,其空间便释放
cpu在三者之间切换执行
main()中进行如下修改:
public static void main(String[] args) {
Demo d1 = new Demo("小李");
Demo d2 = new Demo("xiaoqiang");
d1.start();
d2.start();
//抛出异常
System.out.println(4/0);
System.out.println("over"+Thread.currentThread().getName());
}
主线程出现了异常,但是不影响另外两个线程的运行,因为main线程中出现异常,则main线程结束,释放空间,并不影响其他线程。
线程的状态
(上图不包含阻塞状态)
cpu的执行资格:可以被cpu处理,在处理队列中排队
cpu的执行权:正在被cpu处理
(运行状态的线程具有执行权与执行资格)
(冻结:释放执行权且释放执行资格,“时间一到”便又重新获得)
cpu在某一时刻只能处理一个线程,假设4个线程甲乙丙丁,当执行甲时,乙丙丁处理阻塞状态(具备执行资格,但是不具备执行权,在等待执行权)
创建线程——实现Runnable接口
Runnable接口应该由那些打算通过某一线程执行其实例的类来实现,类必须定义一个称为run的无参数方法。
创建线程方式二:实现Runnable接口
1、实现Runnable接口
2、覆盖接口中的run方法,将线程任务代码封装到run方法中
3、通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递
因为线程的任务都封装在Runnable接口子类对象的run方法中,所以要在线程对象创建时就必须明确要运行的任务(不然执行默认的run)
4、调用线程对象的start方法开启线程
class Demo implements Runnable{
//实现Runnable接口,覆盖run方法
public void run(){
show();
}
public void show(){
for (int i=0;i<9;i++){
System.out.println("i="+i+"...name="+Thread.currentThread().getName());
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Demo d = new Demo();
//Thread的构造函数:public Thread(Runnable target),分配新的Thread对象
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
System.out.println("over...name="+Thread.currentThread().getName());
}
}
两种创建方法的对比
创建形式的本质不同可以通过Thread类的源码得知:
class Thread{
private Runnable r;
Thread(){
}
Thread(Runnable r){
this.r = r;
}
//直接继承Thread创建子类时,run方法直接被覆盖
public void run(){
if (r!=null)
r.run();
}
public void start(){
run();
}
}
(Thread类本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且启动线程的唯一方法就是通过Thread类的start()方法)
其实不管是通过继承Thread类还是通过使用Runnable接口来实现多线程的方法,最终都是通过Thread对象的API来控制线程的。
实现Runnable接口的好处:
- 将线程的任务从线程的子类中分离出来,进行了单独的封装
- 避免了Java单继承的局限性
所以往往Runnable方式比较常用。