线程概述:
/* 1.进程:是一个正在执行中的程序 每一个进程的执行都有一个执行顺序.该顺序是一个执行路径 或者叫一个控制单元. 进程用于封装控制单元 2.线程:就是进程中的一个独立的控制单元 线程在控制着进程的执行. 一个进程中至少有一个线程 JVM启动的时候会有一个进程java.exe. 该进程中至少有一个线程负责java程序的执行 而且这个线程运行的代码存在于main方法中 该线程称之为主线程 扩展:其实更细说明JVM,JVM启动不止一个线程,还有负责垃圾回收机制的线程. 这是因为,当某个对象不被引用时,会变成垃圾 多线程存在的意义: 如果没有多线程,只有主线程.不停的执行过程中,当堆内存产生n多垃圾 无法在堆内存分配空间,这时候主程序必须停掉,回头把垃圾处理 反之,一边执行,一边扔垃圾(多线程)-->提高了效率 */ class ThreadDemo { public static void main(String[] args) { System.out.println("Hello World!"); } }
创建线程:
/* 1.如何在自定义代码中,自定义一个线程?? 通过对api的查找,java已经提供了对线程这类事物的描述->Thread类 创建线程的一种方式:继承Thread类. 步骤: 1.定义类继承Thread 2.复写Thread类中的run(); 目的:将自定义代码存储在run方法中,让线程运行 3.调用线程的start方法 作用:①启动线程②JVM调用该线程的 run 方法。 发现每次运行结果均不相同: 因为多个线程都在获取cpu的执行权.cpu执行到谁,谁就运行 明确一点,在某一个时刻,只能有一个线程在运行.(多核除外) cpu在做着快速的切换(实际上cpu在切换进程中的线程),宏观看上去是同时运行的效果 我们可以形象把多线程的运行行为在互相争夺cpu的执行权 这就是多线程的一个特性:随机型.谁抢到谁执行,至于执行多长,cpu说的算. (画一个示意图) */ class Demo extends Thread { public void run() { for(int i=0;i<10;++i) System.out.println("demo run----"+i); } }class ThreadDemo2 { public static void main(String[] args) { Demo d=new Demo();//创建一个线程,还没有执行 d.start(); //d.run();//仅仅是对象调用run方法.线程创建了,并没有运行.(只有主线程) for(int i=0;i<10;++i) System.out.println("Hello world----"+i); } } /* 以上主线程和new Demo()线程 即使主线程先执行完,还有new Demo() java.exe(进程)还在,执行new Demo()线程 */ /* 为什么要覆盖run方法呢?? Thread类用于描述线程 该类就定义了一个功能,用于存储线程要运行的代码.该存储功能就是run方法 也就是说Thread类中的run方法,用于存储线程要运行的代码 主线程要运行的代码存储在main方法中 */
其中一种打印结果:
小练习:
/* 练习: 创建两个线程,和主线程交替执行. 线程都有自己默认的名称 Thread-编号 该编号从0开始 currentThread:返回对当前正在执行的线程对象的引用 static Thread currentThread() getName():获取线程名称 String getName() 设置线程名称:setName()或者利用构造函数 void setName(String name) 均可查API文档 */ class Test extends Thread { //自定义线程名称 Test(String name) { super(name); } public void run() { for(int x=0;x<60;x++) System.out.println(this.getName()+" test run..."+x); //在这里同样可以使用currentThread.getName(),通用方法 } } class ThreadTest { public static void main(String[] args) { Test t1=new Test("one"); Test t2=new Test("two"); t1.start(); t2.start(); for(int x=0;x<60;++x) System.out.println("main"+x); } }
线程的生命周期:
/* 以下均摘自李刚老师的疯狂java(略有改动): 新建和就绪状态: 当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他 java对象一样,仅仅由JVM为其分配了内存,并初始化了其成员变量的值.此时的线程对象 没有表现出任何线程的动态特征,程序也不会执行线程执行体中的代码. 当对象调用了start()方法之后,该线程 处于就绪状态(万事具备,只欠CPU),JVM会为其创建方法调用栈和程序计数器,处于这个状态的 线程并没有开始运行,它只是表示该线程可以运行了.至于该线程何时开始运行,取决于JVM里线程调度 器的调度. 运行和阻塞状态: 如果处于就绪状态的线程获得了CPU,开始执行run方法的线程执行体,则该线程处于运行状态. 如果计算机只有一个CPU,在任何时刻只有一条线程处于运行状态 但当线程数大于处理器数时,依然会有多条线程在同一个CPU上轮换的现象 当一个线程开始运行后,它不可能一直处于运行状态(除非它的线程执行体足够短,瞬间就执行结束了) ,线程在运行过程中需要被中断,目的是使其他线程获得执行的机会,线程调度的细节取决于底层平台所采用 的策略(windows下的JVM会调用windows底层内容) */
由售票例子引出线程第二种创建方式:
/* 需求:简单的卖票程序. 多个窗口买票. 创建线程的第二种方式:实现Runable接口 步骤: 1.定义类实现Runnable接口 2.覆盖Runnable接口中的run方法 将线程要运行的代码存放在该run方法中 3.通过Thread类建立线程对象. 4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数 为什么要将Runnable接口的子类对象传递给Thread的构造函数?? 因为,自定的run方法 所属的对象 是Runnable接口的子类 对象 所以要让线程去执行 指定对象 的run方法.(就必须明确该run方法所属的对象.) 5.调用Thread类的start方法开启线程并调用Runnable接口子类对象 的run方法 */ /* class Ticket extends Thread { //private static int ticket=100;//让四个对象共享100张票 private int ticket=100; //一般不定义static,生命周期过长 public void run() { while(true) if(ticket>0) System.out.println(currentThread().getName()+"Sale :"+ticket--); } } */ class Ticket implements Runnable { private int ticket=100;//ticket此时被共享 public void run() { while(true) if(ticket>0) System.out.println(Thread.currentThread().getName()+" Sale :"+ticket--); } } class TicketDemo { public static void main(String[] args) { //new Ticket().start(); //new Ticket().start(); //new Ticket().start(); //new Ticket().start(); //以上相当于每台卖了100张,不符合实际 /* Ticket t=new Ticket(); t.start(); t.start(); t.start(); t.start(); */ //以上会有运行提示:无效线程状态异常 //这是因为已经启动线程,并且从就绪状态到运行状态 //又启动了线程 Ticket t=new Ticket(); //注意这个对象不是线程对象,和Thread没关系/*
new Thread().start();//执行的为Thread类中的run方法->方法体中无内容->什么也没执行
*/
new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); //四个线程对象,同一个Runnable子类对象,自始至终改变的是该runnable子类对象中的ticket } } /* 实现方式和继承方式有什么区别??? 实现方式好处:避免了单继承的局限性 在定义线程时,建议使用实现方式. 两种方式区别: 继承Thread:线程代码存放Thread子类run方法中. 实现Runnable:线程代码存放在接口的子类的run方法. 例如: person<-student student不能再继承Thread 如果想让student中部分代码在线程中执行,可以实现Runnable,并复写run方法 *//*
运行结果的打印顺序
是由于多核造成的,先出来的不一定被先打印
*/