对于一个java程序来说,当开始执行后,会先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责看护,回收垃圾。故一个java程序起码有两个线程并发。
举个栗子:
进程可看作是一个公司。
线程可看作是公司里的一名员工。
注意:
假设启动10个线程,就会开辟10个栈空间,每个栈之间互不干扰,各自执行各自的,这就是多线程并发。
学校食堂可以看作是一个进程,
食堂里每一个窗口可以看作是一个线程,
我在A窗口打饭,你可以在B窗口打饭,你不需要等我,我也不需要等你。
故多线程并发可以提高效率。
Java中之所以有多线程机制,目的就是为了提高程序的处理效率。
注意:
使用了多线程机制后,main方法结束,有可能程序也不会结束。
main方法结束只代表主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈。
对于单核的cpu来说,可以做到真正多线程并发吗?
什么是真正的多线程并发?
t1线程执行t1的,t2执行t2的,t1不会影响到t2,t2也不会影响t1。
这就是真正的多线程并发。
对于单核的cpu来说只有一个大脑,无法做到真正的多线程并发,但可以给人一种"多线程并发的感觉"。
在某一个时间点上,单核的cpu只能执行一个线程,但是由于cpu执行速度极快,通过频繁切换不同的线程,给人一种同时处理多个线程的感觉。
就好比你在玩炉石,同时后台用网易云播放着"蜜雪冰城甜蜜蜜"。
线程A:炉石
线程B:网易云"蜜雪冰城甜蜜蜜"
线程A和线程B频繁切换运行,你就感觉游戏一直在运行,音乐一直在播放。
以下程序中有几个线程?
/** * @Author: TSCCG * @Date: 2021/06/18 17:41 */ public class ThreadDemo01 { public static void main(String[] args) { System.out.println("main begin"); t1(); System.out.println("main over"); } private static void t1() { System.out.println("t1 begin"); t2(); System.out.println("t1 over"); } private static void t2() { System.out.println("t2 begin"); t3(); System.out.println("t2 over"); } private static void t3() { System.out.println("t3执行!"); } }
答:除垃圾回收外,只有一个main主线程,主栈
理由:没有启动分支栈,没有启动分支线程,故只有一个主线程,一个栈
结果:
main begin t1 begin t2 begin t3执行! t2 over t1 over main over
若要开启一条新的线程,必须要用到start()方法和run方法
start方法:
run()方法:
实现线程的第一种方法:
写一个继承Thread类的类,重写run方法。
使用时创建一个线程对象,然后调用start()方法。
/** * @Author: TSCCG * @Date: 2021/06/18 20:02 */ public class ThreadDemo02 { //main方法,属于主线程,运行在主栈中 public static void main(String[] args) { //创建一个分支线程对象 MyThread01 myThread = new MyThread01(); //启动线程,启动完后立即结束start方法 myThread.start(); //此处如果没有调用start方法,直接调用run方法,程序仍然只有一个线程 //因为没有开启新的线程,相当于是调用了一个普通方法 //myThread.run(); for (int i = 0; i < 10; i++) { System.out.println("主线程---->" + i); } } } class MyThread01 extends Thread{ /** * 此处重写的run方法相当于主线程里的main */ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("分支线程---->" + i); } } }
结果:
主线程---->0 分支线程---->0 分支线程---->1 分支线程---->2 主线程---->1 分支线程---->3 分支线程---->4 主线程---->2 分支线程---->5 主线程---->3 主线程---->4 分支线程---->6 分支线程---->7 分支线程---->8 分支线程---->9 主线程---->5 主线程---->6 主线程---->7 主线程---->8 主线程---->9
创建线程的第二种方法:
写一个实现Runnable接口的类,叫可执行类
使用时将该类的实例对象作为参数传入Thread类里,转换成线程对象,然后通过该线程对象调用start()方法
/** * @Author: TSCCG * @Date: 2021/06/19 09:29 */ public class ThreadDemo03 { public static void main(String[] args) { //创建一个可运行类 MyRunnable r = new MyRunnable(); //将可运行类的对象作为参数传入Thread类的构造方法里,转换成线程对象 Thread t = new Thread(r);//也可以这样写:Thread t2 = new Thread(new MyRunnable()); //开辟一个分支栈,开启分支线程 t3.start(); } } class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("分支线程---->" + i); } } }
也可以采用匿名内部类方式
Thread t3 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("分支线程---->" + i); } } }); t3.start();
一个线程的周期一共分为五个状态:
新建状态----线程对象刚被new出来
就绪状态----调用start方法后,开辟了栈空间,可以抢夺CPU时间片(执行权)
运行状态----抢夺到CPU时间片后的线程,开始执行各自的程序,主线程执行main方法,分支线程执行run方法。当执行完时间片的时间后,会重新回到就绪状态
阻塞状态----当遇到sleep、Scanner等事件时,线程就会进入阻塞状态,放弃占有的CPU时间片,当结束阻塞状态时,再次回到就绪状态
死亡状态----当线程的main或run方法执行完后,就会进入死亡状态
import java.util.Scanner; /** * @Author: TSCCG * @Date: 2021/06/19 09:29 */ public class ThreadDemo03 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); //创建线程对象 Thread t3 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("分支线程---->" + i); } } }); //开启分支线程 t3.start(); for (int i = 0; i < 10; i++) { System.out.println("主线程---->" + i); //放置一个阻塞事件,当i == 5时,主线程阻塞,分支线程继续执行, //当输入一个整数时,阻塞事件结束,主线程继续运行 if(i == 5){ System.out.println("遭遇阻塞事件"); int j = sc.nextInt(); System.out.println("阻塞事件结束"); } } } }
结果:
主线程---->0 主线程---->1 主线程---->2 主线程---->3 主线程---->4 分支线程---->0 分支线程---->1 分支线程---->2 分支线程---->3 分支线程---->4 分支线程---->5 主线程---->5 遭遇阻塞事件 分支线程---->6 分支线程---->7 分支线程---->8 分支线程---->9 3 阻塞事件结束 主线程---->6 主线程---->7 主线程---->8 主线程---->9
静态方法,通过Thread来调用
Thread t = Thread.currentThread();
String name = 线程对象.getName();
默认情况下,线程的名字默认为:
Thread-0
Thread-1
Thread-2
线程对象.setName("线程名字");
案例:
/** * @Author: TSCCG * @Date: 2021/06/19 10:27 */ public class ThreadDemo04 { public static void main(String[] args) { //创建线程对象t1 MineThread t1 = new MineThread(); //设置线程t1的名字 t1.setName("t1"); //开启线程t1 t1.start(); //创建线程对象t2 MineThread t2 = new MineThread(); t2.setName("t2"); t2.start(); for (int i = 0; i < 10; i++) { //获取当前线程对象 Thread t = Thread.currentThread(); //获取当前线程对象 System.out.println(t.getName() + "---->" + i); } } } class MineThread extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { //获取当前线程对象 Thread t = Thread.currentThread(); //获取当前线程对象 System.out.println(t.getName() + "---->" + i); } } }
结果:
main---->0 main---->1 main---->2 main---->3 t2---->0 t1---->0 t1---->1 t2---->1 t2---->2 main---->4 t2---->3 t1---->2 t2---->4 main---->5 t2---->5 t1---->3 t2---->6 main---->6 t2---->7 t1---->4 t2---->8 t2---->9 main---->7 t1---->5 t1---->6 main---->8 t1---->7 main---->9 t1---->8 t1---->9
static void sleep(long millis)
1.静态方法:Thread.sleep(1000);
2.单位是1毫秒
3.作用:让当前线程进入休眠,进入"阻塞状态",放弃占有CUP时间片,让给其他线程使用
注意:
Thread.sleep(1000);出现在哪个线程,哪个线程就会进入休眠
例:
/** * @Author: TSCCG * @Date: 2021/06/19 10:55 */ public class ThreadDemo05 { public static void main(String[] args) { System.out.println("让子弹飞一会儿"); try { Thread.sleep(1000 * 5);//让主线程休眠5s } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 10; i++) { try { Thread.sleep(1000);//每次休眠1s } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("砰!---->" + i); } } }
结果:
让子弹飞一会儿 砰!---->0 砰!---->1 砰!---->2 砰!---->3 砰!---->4 砰!---->5 砰!---->6 砰!---->7 砰!---->8 砰!---->9
判断以下程序中t1线程是否会休眠2s?
/** * @Author: TSCCG * @Date: 2021/06/27 15:43 * sleep()面试题 */ public class ThreadDemo06 { public static void main(String[] args) { Thread t1 = new MineThread02(); t1.start(); t1.setName("t1"); try { //这里使用线程对象t1调用sleep()方法 t1.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("HelloWorld!"); } } class MineThread02 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { Thread t = Thread.currentThread(); System.out.println(t.getName() + "---->" + i); } } }
结果:
t1---->0 t1---->1 t1---->2 t1---->3 t1---->4 t1---->5 t1---->6 t1---->7 t1---->8 t1---->9 HelloWorld!
从结果可见,通过线程对象t1调用sleep()方法后,t1线程并没有休眠,而是主线程休眠2s。
为什么呢?
这是因为sleep()是静态方法,不应该通过类对象来调用,只能通过类'Thread'直接访问。
t1.sleep(2000);这句代码实际执行的是Thread.sleep(2000);
通过调用interrupt()方法可以中断程序的休眠
/** * @Author: TSCCG * @Date: 2021/06/27 11:23 */ public class ThreadDemo07 { public static void main(String[] args) { Thread t = new Thread(new MyRunnable02()); t.setName("t"); t.start(); try { Thread.sleep(1000 * 5); } catch (InterruptedException e) { e.printStackTrace(); } //在主线程休眠5s后,将t线程唤醒 t.interrupt(); } } class MyRunnable02 implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName() + "----> begin"); //在run()方法中,只能通过try...catch处理异常,而不能用throws将异常抛出 //因为run()方法在父类中没有抛出任何异常,而子类不能抛出更多的异常 try { //使分支线程休眠1年 Thread.sleep(1000 * 60 * 60 * 24 * 365); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "----> end"); } }
结果:
在打印"t----> begin" 五秒后打印线程休眠中断异常信息,然后打印"t----> end"
t----> begin java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at MyRunnable02.run(ThreadDemo07.java:28) at java.lang.Thread.run(Thread.java:745) t----> end