Java多线程
编辑时间:2021/03/14
读完本节:大概花费28分钟,共2801词
1.程序、进程、线程
- 程序(Program):是为了完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
- 进程(Process):是程序执行的一次过程,或是正在运行的一段程序。是一个动态的过程:有它自身的生命周期即产生、存在、消亡的过程
- 程序是静态的,进程是动态的。
- 进程作为资源的分配单位,系统在运行时会为每一个进程分配不同的内存区域。
- 线程(Thread):进程可以进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间→它们从同一个堆中分配对象,可以访问相同的变量和对象。这就使得线程之间的通信变得简便、高效。但是多个线程操作共享的系统资源可能会带来安全的隐患
- 需要多线程的场景:程序需要同时执行两个或多个任务;程序实现一些需要等待的任务;需要后台运行程序时
- 多线程的优点:提高应用程序的响应;提高CPU的利用率;改善程序结构,将长和复杂的进程分为多个线程独立运行,利于理解和维护
2.并行与并发
- 并行:多个CPU同时执行 多个任务
- 并发:单个CPU(采用时间片)**“同时”**执行多个任务
3.线程的创建和使用
-
Java的JVM允许程序运行多个线程,通过java.lang.Thread类来体现
-
Thread类的特性:
- 每个线程都是通过某个特定的Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
- 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()方法
-
创建多线程的方式一:
- 创建一个继承与Thread类的子类
- 重写Thread类的run()方法
- 创建Thread子类的对象
- 通过Thread子类的对象调用start()方法
package sourse; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/14 9:44 * @Description: * 多线程的创建方式一: * 1. 创建一个继承于Thread类的子类 * 2. 重写Thread类的run()方法 * 3. 创建Thread类子类的对象 * 4. 通过Thread类子类的对象调用start()方法 */ /** 1. 创建一个继承与Thread类的子类 */ class MyThread extends Thread{ /** 2.重写Thread类中的run()方法 */ @Override public void run() { /* 遍历100以内的偶数 */ for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ": " +i); } } } } public class ThreadTest { public static void main(String[] args) { /* 3. 创建子类的对象 */ MyThread test = new MyThread(); /* 4. 通过子类对象调用start()方法 */ test.start(); for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ": " +i); } } } }
-
注意点:不能通过直接调用run()的方式启动线程,因为直接调用run()相当于调用子类当中的重写方法,使得还是通过主线程的方式执行;再启动一个线程不可以还让已经start()的线程去执行,否则会报IllegalThreadStateException
-
创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
方式一:创建Thread的两个子类
package sourse; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/14 10:26 * @Description: */ class MyThread1 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } class MyThread2 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 != 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest2 { public static void main(String[] args) { MyThread1 test1 = new MyThread1(); MyThread2 test2 = new MyThread2(); test1.start(); test2.start(); } }
方式二:使用匿名子类直接重写run()方法
package sourse; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/14 10:32 * @Description: */ public class ThreadTest3 { public static void main(String[] args) { new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } }.start(); new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 != 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } }.start(); } }
注意:线程资源建议通过线程池提供,不建议在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
-
创建多线程的方式二:
- 定义子类,实现Runnable接口;
- 子类中重写Runnable接口中的run()方法;
- 创建Runnable子类对象
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中;
- 调用Thread类的start()方法:开启线程,调用Runnable子类接口的run()方法
package sourse; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/14 10:43 * @Description: * 创建多线程的方式二: * 1. 定义子类,实现Runnable接口 * 2. 子类重写接口中的run()方法 * 3. 创建Runnable子类的对象 * 4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中 * 5. 调用Thread类的start()方法:开启线程,调用子类Runnable子类中run()方法 */ /** 1. 定义子类实现Runnable接口 */ class MyThread4 implements Runnable{ /** 2. 子类中重写Runnable接口中的run()方法 */ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest4 { public static void main(String[] args) { //3. 创建Runnable子类的对象, MyThread4 test = new MyThread4(); //4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中 Thread thread = new Thread(test); //5. 调用Thread类的start()方法:开启线程,调用子类Runnable子类中run()方法 thread.start(); } }
-
Thread类中常用的方法
-
start():启动当前线程,调用当前线程的run()
-
run():通常需要重写Thread类中的方法,将创建的线程要执行的操作声明在此方法中
-
currentThread():静态方法,返回执行当前代码的线程
-
getName():获取当前线程的名字
-
setName():设置当前线程的名字
-
yield():线程让步。暂停当前正在执行的线程,把执行机会让给优先级相同或者更高的线程;若队列中没有同优先级的线程忽略此方法
class MyThread5 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } if(i % 20 == 0){ yield(); } } } } public class ThreadMethodTest { public static void main(String[] args) { MyThread5 test1 = new MyThread5(); test1.setName("线程一"); test1.start(); Thread.currentThread().setName("主线程"); for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } }
-
join():当某个程序执行流中调用其他线程的join方法时,调用线程将被阻塞,直到join()方法加入的线程执行完为止,需要解决join抛出的异常InterruptedException
package sourse; class MyThread5 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadMethodTest { public static void main(String[] args) { MyThread5 test1 = new MyThread5(); test1.setName("线程一"); test1.start(); Thread.currentThread().setName("主线程"); for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } if(i == 20){ //当i = 1 时,主线程阻塞,等待线程一执行完后继续执行 try { test1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
-
sleep(long millitime):令当前活动线程在指定时间段内放弃对CPU的控制,使其他线程有机会被执行,时间到后重新排队;在指定的时间段内当前线程是阻塞状态
package sourse; class MyThread5 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ try { sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadMethodTest { public static void main(String[] args) { MyThread5 test1 = new MyThread5(); test1.setName("线程一"); test1.start(); Thread.currentThread().setName("主线程"); for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } if(i == 20){ //当i = 1 时,主线程阻塞,等待线程一执行完后继续执行 try { test1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
-
isAlive():判断当前线程是否存活,存活返回true,否则返回false
class MyThread5 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadMethodTest { public static void main(String[] args) { MyThread5 test1 = new MyThread5(); test1.setName("线程一"); test1.start(); Thread.currentThread().setName("主线程"); for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } System.out.println(test1.isAlive()); } }
-
stop():强制线程生命周期结束,已过时
-
-
线程的调度策略:①时间片;②抢占式:高优先级的线程抢占CPU
-
Java线程的调度方法
- 同优先级的线程组成先进先出队列(先到先服务),使用时间片策略
- 对于高优先级,使用优先调度的抢占式策略
-
线程的优先级:MAX_PRIORITY:10,MIN_PRIORITY:1,NORM_PRIORITY:5
涉及到的方法:getPriority():返回线程的优先级;setPriority():改变线程的优先级
说明:线程创建时继承父线程的优先级;第优先级只是获得调度的概率低,并非一定是在高优先级之后才被调用
-
比较两种创建线程的方式:
-
开发中优先选择:实现Runnable接口的方式
原因:实现的方式没有类的单继承的局限性;实现的方式更适合来处理多个线程共享数据的情况
-
联系:使用继承于Thread类的子类方法:实际上Thread是Runnable的一个实现类“public class Thread implements Runnable”
相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中
-
-
练习一:窗口售票1:使用继承Thread类的方式实现
package exer; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/14 13:00 * @Description: 三个窗口买票总数为100张,使用继承Thread类的方式进行 * 此时存在线程安全问题,有3个号为“100”的票被售出 */ class Window extends Thread{ private static int ticket = 100; @Override public void run() { while (true){ if(ticket > 0){ System.out.println(Thread.currentThread().getName() + ":" + ticket); ticket--; }else{ break; } } } } public class TicketTest { public static void main(String[] args) { Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); w1.setName("窗口1"); w2.setName("窗口2"); w3.setName("窗口3"); w1.start(); w2.start(); w3.start(); } }
练习二:窗口售票2:使用实现Runnable接口的方式实现
package exer; /** * @Author: xuehai.XUE * @MailBox: xuehai.xue@qq.com * @Date: 2021/3/14 13:12 * @Description: 使用实现Runnable接口的方式实现三个窗口售票 * 此时存在线程安全问题 */ class Window1 implements Runnable{ private int ticket = 100; @Override public void run() { while (true){ if(ticket > 0){ System.out.println(Thread.currentThread().getName() + ":" + ticket); ticket--; }else{ break; } } } } public class WindowTest1 { public static void main(String[] args) { Window1 w1 = new Window1(); Thread t1 = new Thread(w1); Thread t2 = new Thread(w1); Thread t3 = new Thread(w1); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }