进程:(一段程序的执行过程),是一个应用程序,而线程是一个进程中的执行场景/执行单元,一个进程可以启动多个线程。进程相当于一个公司,线程相当于一个公司的员工。
**多线程 **,是指从软件或者硬件上实现多个线程并发执行的技术。能够在同一时间执行多于一个线程。在一个程序中,这些独立运行的程序片段叫作“ 线程”(Thread),利用它编程的概念就叫作“多线程处理”
进程A和进程B的内存独立不共享;线程A和线程B的堆内存和方法区内存共享,但栈内存独立,一个线程一个栈。
java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。
并发执行机制原理:简单地说就是把一个处理器划分为若干个短的时间片,每个时间片依次轮流地执行处理各个应用程序,由于一个时间片很短,相对于一个应用程序来说,就好像是处理器在为自己单独服务一样,从而达到多个应用程序在同时进行的效果。
(1)编写一个类,继承java.lang.Thread , 重写run方法
package 多线程; /*1. 怎么创建线程对象? 怎么启动线程?】 * new一个线程对象 调用线程对象的start方法 *2. start方法的作用: * 启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务(开辟一个新的栈空间)完成以后,start方法就结束了,线程启动成功了!!!! * 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈) * run方法在分支栈的栈底部,main方法在主栈的栈底部 *3. 分支栈和主栈 会“并发”执行 */ public class T1 { public static void main(String[] args) {//此处是main方法,属于主线程(在主栈中执行) //新建一个分支线程对象 MyThread myThread = new MyThread(); //启动线程: myThread.start();//启动线程成功,瞬间结束 for(int i=1;i<=100;i++) { System.out.println("主线程"+i+"执行"); } } //方法体中的代码永远都是自上到下的顺序执行!!!! } class MyThread extends Thread{ //必须重写run方法 @Override public void run() {//在此处编写程序,会运行在分支线程中(分支栈) for(int i=1;i<=100;i++) { System.out.println("分支线程"+i+"执行"); } } } /*主线程1执行 分支线程1执行 主线程2执行 分支线程2执行 主线程3执行 分支线程3执行 主线程4执行 分支线程4执行 主线程5执行 分支线程5执行 主线程6执行 分支线程6执行 主线程7执行 分支线程7执行 主线程8执行 分支线程8执行 主线程9执行 主线程10执行 主线程11执行 主线程12执行 主线程13执行 主线程14执行 主线程15执行 主线程16执行 主线程17执行 主线程18执行 分支线程9执行 分支线程10执行 分支线程11执行 分支线程12执行 主线程19执行 主线程20执行 主线程21执行 ....*/
(2)编写一个类,实现java.lang.Runnable接口,实现run方法
package 多线程; public class T1 { public static void main(String[] args) { // 创建一个可运行对象 MyThread myThread = new MyThread(); // 调用Thread类中的构造方法: Thread(Runnable target) 实现将一个可运行对象封装程一个线程对象 Thread thread = new Thread(myThread); // 启动线程 thread.start(); for (int i = 1; i <= 100; i++) { System.out.println("主线程" + i + "执行"); } } } //这并不是一个线程类,是一个可运行的类,还不是一个线程 class MyThread implements Runnable { @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println("分支线程" + i + "执行"); } } } /*主线程1执行 分支线程1执行 主线程2执行 分支线程2执行 主线程3执行 分支线程3执行 主线程4执行 分支线程4执行 主线程5执行 主线程6执行 分支线程5执行 主线程7执行 分支线程6执行 主线程8执行 分支线程7执行 分支线程8执行 分支线程9执行 分支线程10执行 分支线程11执行 分支线程12执行 分支线程13执行 分支线程14执行 ...*/
建议使用第二种:这种面向接口编程,一个类除了实现接口外,还可以继承实现别的类。
也可以采用:匿名内部类的方式
public class T1 { public static void main(String[] args) { //创建线程对象 Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 100; i++) { System.out.println("分支线程" + i + "执行"); } } }); //启动线程 thread.start(); for (int i = 1; i <= 100; i++) { System.out.println("主线程" + i + "执行"); } } }
(1)刚new出来的线程对象,称为“新建状态”
(2)调用start方法,转为“就绪状态”(又叫做可运行状态,表示当前线程具有抢夺CPU时间片的权利,时间片即执行权)
(3)当线程抢夺到时间片之后,就开始执行run方法,run方法的执行标志着线程进入“运行状态”。
(4)当之前占有的时间片用完之后,会重新回到“就绪状态”,继续抢夺时间片,当抢到时间片后,会接着上一次的代码继续往下执行,接下来会重复2,3,4步骤
(5)当run方法执行结束,标示着“死亡状态”
当在run方法执行过程中,遇到阻塞事件例如用户输入,就会由“运行状态”进入“阻塞状态”。进入“阻塞状态”的线程会放弃之前占有的时间片。阻塞解除,会进入“就绪状态,抢夺时间片。
根据以上,就可以解释在并发执行主线程和分支线程时,有先有后,有多有少;
线程的时间片较短,“就绪状态”和“运行状态”频繁地交替,给人的错觉就是并发执行
package 多线程; /*1. 线程默认的名字是Thread-0, Thread-1 ..... * 修改线程的名字:线程对象.setName(String name) * 获取线程的名字: 线程对象.getName() *2.获取当前线程对象:public static native Thread currentThread(); * */ public class T1 { public static void main(String[] args) { // 新建一个分支线程对象 MyThread t1 = new MyThread(); // 启动线程: t1.start(); // 设置线程的名字 t1.setName("t1"); // 获取线程的名字 String string = t1.getName(); System.out.println(string); // 获取当前线程对象 Thread t = t1.currentThread();// 这个方法出现在main方法当中,所以当前线程就是主线程 System.out.println("线程" + t.getName() + "在执行");// 主线程的名字就是main MyThread t2 = new MyThread(); t2.setName("t2"); t2.start(); } } class MyThread extends Thread { // 必须重写run方法 @Override public void run() { for (int i = 1; i <= 5; i++) { // 关键是看那个线程对象取调用start方法去启动此线程,它就是当前线程对象 Thread thread = Thread.currentThread(); System.out.println("分支线程" + thread.getName() + "第" + i + "次" + "执行"); } } } /*t1 分支线程t1第1次执行 线程main在执行 分支线程t1第2次执行 分支线程t1第3次执行 分支线程t1第4次执行 分支线程t2第1次执行 分支线程t2第2次执行 分支线程t1第5次执行 分支线程t2第3次执行 分支线程t2第4次执行 分支线程t2第5次执行 */
package 多线程; /*线程的sleep方法: * 1. static void sleep(long mills) * 静态方法,参数是毫秒; 作用是让当前线程进入休眠,进入阻塞状态,放弃占有的CPU时间片,让给其他线程使用 特别注意:sleep方法是静态的,与是谁调用它无关,在调用时,会自动转为Thread.sleep * 此代码出现在a线程中,a线程进入休眠。 * 1秒 == 1000 毫秒 * 2. public static native void sleep(long millis) throws InterruptedException; * InterruptedException extends Exception * 此处属于编译时异常,要么throws,要么try catch * 3. Thread.sleep(时间) 实现间隔特定的时间,去只执行一段特定的代码 */ public class T1 { public static void main(String[] args) { // try { // Thread.sleep(1000*5);//让当前线程休眠5秒 当前线程就是此处的主线程 // } catch (InterruptedException e) { // e.printStackTrace(); // } // // System.out.println("5秒之后代码执行"); //实现每隔2秒输出一次 for(int i=0;i<5;i++) { System.out.println(Thread.currentThread().getName()+"线程执行第"+i+"次"); try { Thread.sleep(2000);//让当前线程休眠2秒 } catch (InterruptedException e) { e.printStackTrace(); } } } } /*main线程执行第0次 main线程执行第1次 main线程执行第2次 main线程执行第3次 main线程执行第4次*/
package 多线程; public class T2 { public static void main(String[] args) { Thread thread = new Thread(new MyThread2()); thread.setName("t"); thread.start(); // 主线程休眠3秒,3秒后希望唤醒t线程 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 唤醒t线程 thread.interrupt(); // 这种中断睡眠的方式,依靠了java的异常处理机制 // run()中的sleep就会抛异常,catch抓住异常,打印异常信息,整个try catch语句结束,程序继续往下执行了 } } class MyThread2 implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "线程start"); // 此处只能try catch 不可以throws,因为子类重写不可以抛出比父类更多的异常 // 所以,run方法中的异常不可以throws,因为run方法在父类中没有抛出任何异常 try { Thread.sleep(7000); } catch (InterruptedException e) { // 打印异常信息 e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "线程end"); } } /*t线程start java.lang.InterruptedException: sleep interrupted t线程end at java.base/java.lang.Thread.sleep(Native Method) at 多线程.MyThread2.run(T2.java:33) at java.base/java.lang.Thread.run(Thread.java:831) */
若不想显示异常信息,直接将catch语句中的打印异常信息的语句注释掉就可以了
package 多线程; public class T2 { public static void main(String[] args) { MyThread2 my = new MyThread2(); Thread thread = new Thread(my); thread.setName("t"); thread.start(); // 模拟5秒 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // 终止线程 my.run = false; } } class MyThread2 implements Runnable { boolean run = true;// 此处设置布尔标记,直接在外面改变此标记 @Override public void run() { for (int i = 1; i <= 10; i++) { if (run) { System.out.println(Thread.currentThread().getName() + "线程start" + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } else { // 终止当前线程 return; } } } } /*t线程start1 t线程start2 t线程start3 t线程start4 t线程start5*/