多线程详解."/>
多线程详解.
目录:
一、什么是线程
二、分析下列程序有几个线程
三、实现线程的第一种方式
—— 第二种方式(建议使用第二种方式)
—— 采用匿名内部类格式四、线程的生命周期
五、获取线程的名字
—— 获取当前线程对象六、线程的sleep方法
—— 关于Thread.sleep方法的一个面试题
—— 终止线程的睡眠
—— 合理的终止一个线程的执行【重点 常用】
一、什么是线程
1.1、什么是进程?什么是线程?
进程是一个应用程序(1个进程是一个软件)。
线程是一个进程中的执行场景/执行单元。
一个进程可以启动多个线程。
1.2、对于java程序来说,当在Dos命令窗口中输入
java HelloWorld 回车之后。
会先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法。
同时再启动一个垃圾回收线程负责看护,回收垃圾。
最起码,现在的java程序中至少有两个线程并发,
一个是垃圾回收线程,一个是执行main方法的主线程。
1.3、进程和线程是什么关系?举个例子
阿里巴巴:进程
马云:阿里巴巴的一个线程
童文红:阿里巴巴的一个线程
京东:进程
强东:京东的一个线程
妹妹:京东的一个线程
进程可以看做是现实生活当中的公司。
线程可以看做是公司当中的某个员工。注意:
进程A和进程B的内存独立不共享。(阿里巴巴和京东资源不会共享的!)
线程A和线程B呢?
在java语言中:
线程A和线程B,堆内存和方法区内存共享。
但是栈内存独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,
互不干扰,各自执行各自的,这就是多线程并发。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。1.4、思考一个问题:
使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束?
main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在
压栈弹栈。|4.5、分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?
对于多核的CPU电脑来说,真正的多线程并发是没问题的。
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
什么是真正的多线程并发?
t1线程执行t1的。
t2线程执行t2的。
t1不会影响t2, t2也不会影响t1。这叫做真正的多线程并发单核的cPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种"多线程并发"的感觉。
对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于
CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是:多个事情
同时在做!!!!!
线程A:播放音乐
线程B:运行魔兽游戏
线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行 ,
给我们的感觉是同时并发的。
二、分析下列程序有几个线程
代码演示:
package com.bipowernode.javase.thread;
/*分析以下程序,有几个线程,除垃圾回收线程之外有几个线程?1个线程(因为程序只有一个栈)输出结果:main beginm1 beginm2 beginm3 execute~m2 overm1 overmain over一个栈中,自上而下依次逐行执行~*/
public class ThreadTest01 {public static void main(String[] args) {System.out.println("main begin");m1();System.out.println("main over");}private static void m1() {System.out.println("m1 begin");m2();System.out.println("m1 over");}private static void m2(){System.out.println("m2 begin");m3();System.out.println("m2 over");}private static void m3(){System.out.println("m3 execute~");}
}
三、实现线程的第一种方式
代码演示:
package com.bipowernode.javase.thread;
/*
实现线程的第一种方式:编写一个类,直接继承java.lang.Thread, 重写run方法以下程序的输出结果有这样的特点:有先有后有多有少这是咋回事?因为底层主线程和子线程进入到就绪状态(start)的时候:具有抢夺CPU时间片的权力,当一个线程抢到CPU时间片后就开始run()方法进入运行状态 【谁先抢到谁准备进入运行状态】*/
public class ThreadTest02 {public static void main(String[] args) {// 这里是main方法,这里的代码属于主线程,在主栈中运行// 新建一个分支线程对象MyThread t =new MyThread();// 启动线程// start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码认为完成之后,瞬间就结束了(瞬间结束之后开始主线程代码跑动)// 这段代码的任务只是为了开辟一个新的栈空间,只要新的栈空间开辟出来,start()方法就瞬间结束,线程就启动成功了// 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)// run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的t.start();// 这里的代码还是运行在主线程中for (int i =0;i<=1000;i++){System.out.println("主线程----> "+i);}}
}class MyThread extends Thread{// 重写 run方法@Overridepublic void run() {// 编写程序,这段程序运行在分支线程中(分支栈)for (int i=0; i<=1000;i++){System.out.println("分支线程----> "+i);}}
}
输出结果:
注意:
package com.bipowernode.javase.thread;
/*
实现线程的第一种方式:编写一个类,直接继承java.lang.Thread, 重写run方法*/
public class ThreadTest02 {public static void main(String[] args) {// 这里是main方法,这里的代码属于主线程,在主栈中运行// 新建一个分支线程对象MyThread myThread =new MyThread();// 如果这里直接调用分支线程的run方法myThread.run(); // 那么这里就相当于调用了一个类当中的普通方法,没有开启分支线程,只是把run()这个方法通过压栈的方式到了main方法中// 这样写只有一个主线程 // 会先把run()方法当中的程序跑完之后弹栈后再执行main方法当中的代码程序// 这里的代码还是运行在主线程中for (int i =0;i<=1000;i++){System.out.println("主线程----> "+i);}}
}class MyThread extends Thread{ // 缺点该类不能再继承其他的类了// 重写 run方法@Overridepublic void run() {// 编写程序,这段程序运行在分支线程中(分支栈)for (int i=0; i<=1000;i++){System.out.println("分支线程----> "+i);}}
}
内存图演示如下:
第二种方式(建议使用第二种方式)
1、实现线程的第二种方式:编写一个类实现java.lang.Runnable接口
2、建议使用接口方式
因为子线程实现Runnable方法后 还可以去继承其他的类 更加灵活 扩展性强
代码演示如下:
package com.bipowernode.javase.thread;
/*
1、实现线程的第二种方式:编写一个类实现java.lang.Runnable接口
2、建议使用接口方式因为子线程实现Runnable方法后 还可以去继承其他的类 更加灵活 扩展性强*/
public class ThreadTest03{public static void main(String[] args) {// 创建一个可运行对象// MyRunnable r =new MyRunnable();// 将可运行对象封装成一个线程对象// Thread t =new Thread(r);Thread t =new Thread(new MyRunnable()); // 合并代码// 启动线程t.start();for (int i=0;i<=100;i++){System.out.println("主线程===="+i);}}
}// 这里并不是一个线程类,是一个可运行的类,它还不是一个线程
class MyRunnable implements Runnable{ // 该类还可以继续继承其他的类 扩展性强了// 实现该方法@Overridepublic void run() {for (int i=0;i<=100;i++){System.out.println("分支线程===="+i);}}
}
采用匿名内部类格式
代码演示:
package com.bipowernode.javase.thread;
// new个接口 加个实现类
public class ThreadTest04 {public static void main(String[] args) {// 创建线程对象,采用匿名内部类的格式// new Runnable(){} 相当于new了一个匿名类,这个匿名类实现了Runnable接口Thread thread =new Thread(new Runnable(){@Overridepublic void run() {// 分支线程for (int i=0;i<=100;i++){System.out.println("分支进程----> "+i);}}});// 启动线程thread.start();// 主线程for (int i=0;i<=100;i++){System.out.println("子线程----> "+i);}}
}
四、线程的生命周期
五、获取线程的名字
代码演示如下:
package com.bipowernode.javase.thread;
/*
1、怎么获取当前线程对象?static native Thread currentThread(); 方法
2、获取线程对象的名字String name =线程对象.getName();3、修改线程对象的名字线程对象.setName("线程名字");4、当线程没有设置名字的时候,默认的名字有什么规律:Thread-0Thread-1Thread-2Thread-3Thread-4....*/
public class ThreadTest05 {public static void main(String[] args) {// 创建线程对象MyTread1 t =new MyTread1();// 设置线程的名字t.setName("线程t");// 获取线程的名字System.out.println(t.getName()); // 线程t// 创建线程对象MyTread1 tt =new MyTread1();// 设置线程名字tt.setName("tt线程");System.out.println(tt.getName());// 开启线程t.start();tt.start();}
}class MyTread1 extends Thread {@Override // 重写run方法public void run() {for (int i = 0; i <= 100; i++) {System.out.println("分支线程---->" + i);}}
}
5.1、获取当前线程对象
代码演示如下:
package com.bipowernode.javase.thread;
/*
1、怎么获取当前线程对象?static native Thread currentThread(); 方法*/
public class ThreadTest05 {public static void main(String[] args) {// currentThread就是当前线程对象// 这个代码出现在main方法当中,所以当前线程是主线程Thread currentThread =Thread.currentThread();System.out.println(currentThread.getName());// 创建线程对象MyTread1 t =new MyTread1();// 设置线程的名字t.setName("线程t");MyTread1 tt =new MyTread1();// 设置线程名字tt.setName("tt线程");// 开启线程t.start();tt.start();}
}class MyTread1 extends Thread {@Override // 重写run方法public void run() {// currentThread就是当前线程对象,当前线程是谁呢?// 当t1线程执行run方法的时候,那么这个当前线程就是t1// 当tt线程执行run方法的时候,那么这个当前线程就是ttThread currentThread =Thread.currentThread();for (int i = 0; i <= 100; i++) {System.out.println(currentThread.getName() +"===>" + i);}}
}
六、线程的sleep方法
关于线程的sleep方法
static void sleep(long millis)
1、静态方法
2、参数是毫秒
3、作用:让当前线程进入休眠,进入”阻塞状态“,放弃占有的时间片,让给其他线程用
代码演示如下:
package com.bipowernode.javase.thread;
/*
关于线程的sleep方法static void sleep(long millis)1、静态方法2、参数是毫秒3、作用:让当前线程进入休眠,进入”阻塞状态“,放弃占有的时间片,让给其他线程用*/
public class ThreadTest06 {public static void main(String[] args) {// 让当前线程进入休眠,睡眠5s(当前线程是main主线程)try {Thread.sleep(1000*5);} catch (InterruptedException e) {e.printStackTrace();}// 5s后执行这里的代码System.out.println("hello world");for (int i=0;i<=9;i++){// 每输出一次 睡眠1sSystem.out.println(Thread.currentThread().getName() +"---->" +i);// 睡眠1stry {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
输出结果:
关于Thread.sleep方法的一个面试题
package com.bipowernode.javase.thread;/*
关于Thread.sleep()方法的一个面试题:*/
public class ThreadTest07 {public static void main(String[] args) {// 创建线程对象MyThread2 t =new MyThread2();// 设置线程名字t.setName("线程t");// 开启线程t.start();// 调用sleep方法try {// 问题:这行代码会让线程t进入休眠状态吗?t.sleep(1000*5); // 在执行的时候还是会转换成:Thread.sleep(1000*5);} catch (InterruptedException e) { // 因为sleep的作用是让当前线程进入睡眠状态 此代码当中当前线程是main线程 【注意是当前线程进入睡眠状态】e.printStackTrace();}// 主线程代码// 5s后执行主线程System.out.println("我是5s后才被执行的主线程代码~");}
}class MyThread2 extends Thread{@Overridepublic void run() {// 这里跑的是分支线程for (int i=0;i<=1000;i++){System.out.println(Thread.currentThread().getName() +"---->" +i);}}
}
终止线程的睡眠
interrupt()方法 这种终断睡眠的方式依靠了java的异常处理机制
package com.bipowernode.javase.thread;
//sleep睡眠太久了,如果希望半道上醒来,你应该怎么办? 也就是说怎么叫醒一个正在睡眠的线程????public class ThreadTest08 {public static void main(String[] args) {// 创建线程对象Thread t =new Thread(new MyThread0());// 设置线程的名字t.setName("线程t");t.start();// 假设半小时过去了,让t线程醒来(半小时后主线程手里头的活干完了 )// 终止t线程的睡眠t.interrupt(); // 干扰作用// 这种终断睡眠的方式依靠了java的异常处理机制(通过干扰t线程当中的异常报错然后捕获异常 异常分支结束,继续往下走分支"----> end")}
}class MyThread0 implements Runnable{// 实现run()方法// 重点:run()方法当中的异常不能throws,只能try....catch// 因为run()方法在父类Runnable中没有抛任何异常,子类不能比父类抛出更多的异常@Overridepublic void run() {// 这里跑的是子线程的代码System.out.println(Thread.currentThread().getName()+"---> begin~");// 睡眠一小时try {Thread.sleep(1000*60*60*60); // 谁调用start进入run()方法谁就是当前线程} catch (InterruptedException e) {// 打印异常信息(可省略掉 就不会打印异常信息了)// e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"----> end");}
}
例子演示:
package com.bipowernode.javase.thread;public class A {public static void main(String[] args) {// 创建线程对象Thread t =new Thread(new Runnable() {@Overridepublic void run() {// 这里走的是子线程分支// 先睡眠1h 让主线程先完成任务再开启try { // 这里只能try catch 因为父类当中的run方法没有任何异常 子类不能比父类抛出更多的异常Thread.sleep(1000*60*60*60);} catch (InterruptedException e) {// e.printStackTrace();System.out.println("子线程睡眠干扰成功");}for (int i=0;i<=900;i++){System.out.println(Thread.currentThread().getName()+"---->" +i); // 谁调用这就是哪个线程的名字}}});// 设置线程名字t.setName("线程t");// 开启线程t.start();// 主线程for (int i=0;i<=900;i++){System.out.println(Thread.currentThread().getName()+"---->"+ i);}// 主线程结束之后 开启子线程t.interrupt(); // 干扰的是异常}
}
输出结果:
合理的终止一个线程的执行【重点 常用】
package com.bipowernode.javase.thread;// 合理的终止一个线程的执行 这是一个很常用的方式
public class ThreadTest09 {public static void main(String[] args) {MyThread01 m =new MyThread01();// 创建线程对象Thread t =new Thread(m);// 设置线程名字t.setName("线程t");// 开启线程t.start();// 模拟5s 假设5s后终止线程的执行 (这里跑的程序 是主线程)try {Thread.sleep(1000*5);} catch (InterruptedException e) {e.printStackTrace();}m.run =false;}
}class MyThread01 implements Runnable{// 打一个布尔标记boolean run =true;@Overridepublic void run() {for (int i=0;i<=9;i++){if (run){// 睡眠一秒try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"---->"+i); // 每睡眠一秒执行一次}// 程序到这里说明falseelse {// 终止线程程序// return 就结束了 你在结束之前还有什么没保存的// 在return之前可以进行保存return;}}}
}
更多推荐
多线程详解.
发布评论