Thread 引入:
█ 单线程程序
☞如果程序只有一条执行路径
█ 多线程程序
☞如果程序有多条执行路径
什么是多线程:
█ 进程
☞是操作系统进行资源分配和调度的基本单位
█ 线程
☞是程序使用cpu的基本单位
☞是进程中单个顺序控制流(执行路径),是一条单独执行的路径。单线程,多线程
█ 举例
☞扫雷,迅雷下载
JAVA程序的运行原理:
█ Java命令会启动JVM,即启动了一个进程
☞该进程会启动一个主线程,然后主线程调用某个类的main方法,所以main方法都是运行在主线程里。(之前我们写的基本都是单线程程序 )
█ 思考 JVM是单线程还是多线程? 多线程,有一个线程是去执行main,还有一个垃圾回收线程,默默的进行不用内存回收;
实现方案:
(eclipse 中ctrl + t 可以查看类的层次结构)
方式一
继承Thread类
重写子类的run方法
创建该子类的对象
启动线程
|
方式二
创建线程的另一种方法是声明实现 Runnable 接口的类
然后实现 run 方法。
然后可以分配该类的实例
在创建 Thread 时作为一个参数来传递并启动。
|
线程调度:
假设只有一个CPU(单核情况)
█ 线程两种调度模型
☞分时调度模型(平均分配)
☞抢占式调度模型(优先级)
Java使用哪种? 抢占式
|
线程优先级:
如何获取当前线程优先级? //getPriority();
如何设置当前线程优先级? //setPriority();
线程优先级范围? 1-10 1最低优先级 默认5
|
线程控制:
线程睡眠
static void sleep(long millis);
线程加入
public final void join();
线程礼让
public static void yield();
|
后台线程
public final void setDaemon(boolean on);
中断线程
public void interrupt();
public final void stop();
|
线程的生命周期:
新建 创建线程对象 |
就绪
有执行资格,等待cpu调度
|
运行
取得执行权,开始执行
阻塞: 无执行资格,无执行权
|
死亡
执行完毕,等待垃圾回收
|
//第一种实现方式
TestMain.java | TestMain2.java |
package com.java.thread;
public class TeseMain {
public static void main(String[] args) {
//3、创建对象
MyThread thread1=new MyThread();
thread1.setName("张三"); //这里默认名称是 Thread-0;后面0是索引号
//4、启动该线程应该使用 start 方法,启动系统会调用线程里的run方法
thread1.start(); //IllegalThreadStateException //线程不能用一个对象调用重复启动,如果已经启动了,再次使用start重新启动,就会报这个异常
//thread1.run(); //和普通的方法调用没有任何区别,不会启动子线程,而且不会将该方法放到子线程里执行
MyThread thread2=new MyThread();
thread2.setName("李四");
thread2.setPriority(10); //设置优先级,数字越大,优先级越高;1~10
//不是cpu中设置的,cpu仅仅将我们的代码里设置的优先级作为参考
thread2.start();
for(int i=0;i<10;++i){
System.out.println(Thread.currentThread().getName()+" : "+i); //获取当前线程,并输出他的名字
}
}
}
//1、继承 Thread 类
class MyThread extends Thread{ //方式一
//2、重写 run 方法
public void run() {
super.run();
String name = getName(); //父类方法,返回调用方法的对象名
int p=getPriority(); //获取当前线程优先级
for(int i=0;i<10;++i){
System.out .println("MyThread.run() :"+name+": "+i+" 优先级:"+p);
try {
Thread.sleep(500); //静态方法,可以直接写;但是推荐类名去调用
//当线程sleep的时候,cpu就会将其挂起,执行其他线程;睡醒后等待cpu调用执行
}catch (InterruptedException e) {
e.printStackTrace();
}
//父类有setName方法,可以修改调用线程的名称;改完后getName到的就是刚设置好的
}
}
}
|
package com.java.thread;
public class TestMain2 {
public static void main(String[] args) {
MyThread2 t1=new MyThread2();
t1.start();
try{
t1.join(); //join方法,等待线程结束
}catch(InterruptedException e){
e.printStackTrace();
}
for(int i=0;i<10;i++){
System.out .println("main thread() :"+i);
}
}
}
class MyThread2 extends Thread{
public void run() {
super.run();
String name = getName();
for(int i=0;i<10;++i){
System.out .println("MyThread2.run() :"+name+": "+i);
}
}
}
部分执行结果
部分结果,main最后执行 |
TestMain3.java | TestMain4.java |
package com.java.thread;
public class TestMain3 {
public static void main(String[] args) {
MyThread3 t1=new MyThread3();
t1.start();
MyThread3 t2=new MyThread3();
t2.setDaemon(true); //true 就是把这个进程设置为守护进程;就是后台进程;等所以线程都执行完了退出,守护线程也就退出了
t2.start();
}
}
class MyThread3 extends Thread{
public void run() {
super.run();
int p=getPriority();
String name = getName();
for(int i=0;i<10;++i){
System.out .println("MyThread3.run() :"+name+": "+i+"优先级 "+p);
Thread.yield(); //放弃cpu让给别人;放弃执行完后进入就绪状态,此时cpu可以再次调度
}
}
}
这里实验看不出什么区别 |
package com.java.thread;
public class TestMain4 {
public static void main(String[] args) {
MyThread4 t1=new MyThread4();
t1.start();
MyThread4 t2=new MyThread4();
t2.start();
}
}
class MyThread4 extends Thread{
public void run() {
super.run();
int p=getPriority();
String name = getName();
for(int i=0;i<10;++i){
System.out .println("MyThread3.run() :"+name+": "+i+"优先级 "+p);
if(5==i){
Thread.currentThread().interrupt(); //中断线程
//Thread.currentThread().stop(); //过时不用了,不安全
}
}
}
}
//线程中断后cpu会挑选其他线程来执行,当没有可挑选的线程的时候,刚才中断的线程又可以接着开始执行;这段代码是最后两个线程都把i加到10 |
//第二种实现方式
TestMain.java | |
package com.java.thread2;
public class TestMain {
public static void main(String[] args) {
MyThread run1=new MyThread();
Thread t1=new Thread(run1); // 这种初始化方式,thread会再启动之后,调用到runable接口里的run方法,将其放在一个新的线程里执行
Thread t2=new Thread(run1);
t1.setName("第一");
t2.setName("第二");
t1.start();
t2.start();
Cat cat=new Cat();
Thread cat_thread=new Thread(cat);
cat_thread.start();
}
}
class MyThread implements Runnable{
String BookName="java";
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+": "+i);
System.out.println("MyThread.run() : "+BookName);
}
}
}
class Animal{
String name;
}
class Cat extends Animal implements Runnable{
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"Cat catch mouse "+i);
}
}
}
|
//线程争夺cpu资源,交替执行;每一句语句都是这样 |
方式一 VS 方式二
方式一的缺陷: 如果某个类已经有父类,则无法再继承Thread类
方式二的优点: 解决了单继承的局限性;还有一个优点,便于多线程共享数据
两种实现方式的对比:
方法1:需要多个对象
方法2:只需要新建一个对象即可,放入三个不同线程;实现了数据和业务模型的分离