# Java高级之多线程的使用 **Repository Path**: fpfgitmy_admin/java-high-concurrent-used ## Basic Information - **Project Name**: Java高级之多线程的使用 - **Description**: java多线程使用 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2021-04-28 - **Last Updated**: 2022-03-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README #### 线程的创建和使用 ##### 使用Thread类方式 1. 创建一个继承于Thread类的子类 1. 重写Thread类的run() 2. 创建Thread类的子类对象 3. 通过此对象调用start()方法:含义----①:启动当前线程。②调用run方法。 + 问题一:不能他通过run的方法启动线程 + 问题二:再启动一个线程,遍历的同时,不能再次使用t1.start()的线程去执行,需要再次new t2的线程对象 4. 代码如下 ``` class MyThread extends Thread { @Override public void run() { printStr(); } public void printStr() { 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) { MyThread myThread = new MyThread(); myThread.start(); for (int i = 0; i < 100; i++) { if ((i % 2) == 0) { System.out.println(Thread.currentThread().getName() + "-hello"); } } } } ``` 2. 线程的常用方法 ``` /** * @describle Thread类常用方法 */ class MyThreadOne extends Thread { // start();启动当前线程,调用当前线程的run();方法 // run();通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中 // currentThread();静态方法,返回当前执行的线程。 // getName();获取当前线程的名称 // setName();设置当前线程的名称 // 调用super();的构造方法,设置当前线程名称 // yield();释放当前cpu的执行权(可能被别的线程所占用) // join();在线程a中调用线程b的join();方法,此时线程a就进入阻塞状态,知道线程b完全执行完成以后,线程a才结束阻塞状态 // stop();当执行此方法时,强制结束当前线程(已过时) // sleep();当调用此方法时,当前线程暂停对应时间,时间结束后等待cpu分配资源,在暂停时间内当前线程是阻塞状态。单位:毫秒 // isAlive();判断当前线程是否存活 public MyThreadOne(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { if ((i % 2) == 1) { System.out.println(MyThreadOne.currentThread().getName() + "名称" + i); } /*if (i % 2 == 0) { yield(); }*/ } } } public class ThreadCuseMethod { public static void main(String[] args) { MyThreadOne myThreadOne = new MyThreadOne("线程1"); myThreadOne.start(); for (int i = 0; i < 100; i++) { if ((i % 2) == 1) { System.out.println(Thread.currentThread().getName() + "名称" + i); } if (i == 20) { try { myThreadOne.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println(myThreadOne.isAlive()); } } ``` ##### 线程的调度 + 调度策略: 1. 时间片的方式 2. 抢占式:高优先级的线程抢占CPU + Java的调度方法 1. 同优先级线程组成先进先出的队列(先到先服务),使用时间片策略 2. 对高优先级,使用优先调度的抢占式策略 + 线程的优先级 1. MAX_PRIORITY:10 MIN_PRIORITY:1 NORM_PRIORITY:5 2. 如何获取和设置当前线程的优先级 ``` class MyThreadOne extends Thread { // getPriority(); 获取线程的优先级 // setPriority(); 设置线程的优先级 // 注:高优先级的线程要抢占低优先级线程的cpu执行权,但是只是从概率上讲,高优先级的线程高概率的情况下被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。 public MyThreadOne(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { if ((i % 2) == 1) { System.out.println(MyThreadOne.currentThread().getName() + " 线程优先级:" + MyThreadOne.currentThread().getPriority() + " 循环次数" + i); } } } } public class ThreadCuseMethod { public static void main(String[] args) { MyThreadOne myThreadOne = new MyThreadOne("分线程"); myThreadOne.setPriority(Thread.MAX_PRIORITY); //设置优先级为10 myThreadOne.start(); Thread.currentThread().setPriority(Thread.MIN_PRIORITY); // 设置优先级为1 Thread.currentThread().setName("主线程"); for (int i = 0; i < 100; i++) { if ((i % 2) == 1) { System.out.println(Thread.currentThread().getName() + " 线程优先级:" + Thread.currentThread().getPriority() + " 循环次数" + i); } } } } ``` #### 使用Runnable实现多线程 ``` package com.felixfei.study.threads; /** * @describle 多线程方式二:实现Runnable接口 */ // 1.实现Runnable接口 public class RunnableTest implements Runnable { // 2.重写run方法 @Override public void run() { for (int i = 0; i < 100; i++) { if ((i % 2) == 0) { System.out.println(Thread.currentThread().getName() + i + "次数"); } } } public static void main(String[] args) { // 3.创建实现Runnable接口的对象 RunnableTest runnableTest = new RunnableTest(); // 4.使用Thread类去调用对象 Thread t1 = new Thread(runnableTest); // 5.启动线程并执行run方法 t1.start(); Thread t2 = new Thread(runnableTest); t2.start(); } } ``` #### 使用多线程实现卖票 ##### Thread方式 ``` package com.felixfei.study.threads; /** * @describle 创建多线程实现多窗口卖票 */ public class WindowForThread extends Thread { // 使用静态变量,共享票数 private static Integer num = 100; @Override public void run() { while (true) { if (num > 0) { System.out.println(getName() + "票数:" + num); num--; } else { break; } } } public static void main(String[] args) { WindowForThread w1 = new WindowForThread(); WindowForThread w2 = new WindowForThread(); WindowForThread w3 = new WindowForThread(); w1.setName("窗口1"); w2.setName("窗口2"); w3.setName("窗口3"); w1.start(); w2.start(); w3.start(); } } ``` ##### Runnable方式 ``` package com.felixfei.study.threads; /** * @describle 使用实现Runnable方式实现多窗口卖票 */ public class WindowForRunnable implements Runnable { // 由于使用Runnable方式创建了一个对象,所以不用添加static关键字 private Integer num = 100; @Override public void run() { while (true) { if (num > 0) { System.out.println(Thread.currentThread().getName() + "票号为:" + num); num--; } else { break; } } } public static void main(String[] args) { WindowForRunnable windowForRunnable = new WindowForRunnable(); Thread t1 = new Thread(windowForRunnable); Thread t2 = new Thread(windowForRunnable); Thread t3 = new Thread(windowForRunnable); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } } ``` ##### 两种方式比较 + Runnable方式更优 1. 实现的方式没有类的单继承性的局限性 2. Runnable方式天然共享数据,不用添加static关键字 + 二者联系 1. Thread类实现了Runnable接口 2. 两种方式都需要重写run方法,将线程要执行的逻辑编写在run方法内 ##### 使用同步代码块方式实现线程安全(Runnable方式) ``` package com.felixfei.study.threads; /** * @describle 使用实现Runnable方式实现多窗口卖票 */ public class WindowForRunnable implements Runnable { Object o = new Object(); // 由于使用Runnable方式创建了一个对象,所以不用添加static关键字 private Integer num = 100; @Override public void run() { while (true) { // 使用synchronized 代码块包住共享变量操作的代码实现线程安全 // synchronized (o) { // this指调用当前方法的对象 synchronized (this) { if (num > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "票号为:" + num); num--; } else { break; } } } } public static void main(String[] args) { WindowForRunnable windowForRunnable = new WindowForRunnable(); Thread t1 = new Thread(windowForRunnable); Thread t2 = new Thread(windowForRunnable); Thread t3 = new Thread(windowForRunnable); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } } ``` ###### 使用同步代码块实现线程安全(Thread方式) ``` package com.felixfei.study.threads; /** * @describle 创建多线程实现多窗口卖票 * 1.会出现线程安全问题:当当前线程操作过程中,其他线程参与进来,也操作了该数据 * 2.解决方案 */ public class WindowForThread2 extends Thread { // 使用静态变量,共享票数 private static Integer num = 100; private static Object obj = new Object(); @Override public void run() { while (true) { // 方式1 // synchronized (obj) { // 方式2:类也是对象 synchronized (WindowForThread2.class) { if (num > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "票数:" + num); num--; } else { break; } } } } public static void main(String[] args) { WindowForThread2 w1 = new WindowForThread2(); WindowForThread2 w2 = new WindowForThread2(); WindowForThread2 w3 = new WindowForThread2(); w1.setName("窗口1"); w2.setName("窗口2"); w3.setName("窗口3"); w1.start(); w2.start(); w3.start(); } } ``` + 说明: 1. 操作共享数据的代码,即为需要被同步的代码 -->不能包含代码多了(相当于一个线程操作完了共享变量),也不能包含代码少了(会出现 脏数据问题) 2. 共享数据:多个线程共同操作的变量 3. 同步监视器,俗称锁,任何一个类的对象,都可以充当锁 4. 要求:多个线程必须要共用同一把锁 5. 在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器 + 好处与坏处 1. 好处:使用同步代码的方式,解决了线程安全的问题 2. 坏处:在操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,有局限性 ##### 使用同步方法的方式实现线程安全(Runnable方式) ``` package com.felixfei.study.threads; /** * @describle 使用实现Runnable方式实现多窗口卖票 */ public class WindowForRunnable2 implements Runnable { // 由于使用Runnable方式创建了一个对象,所以不用添加static关键字 private Integer num = 100; @Override public void run() { while (true) { show(); } } private synchronized void show() { // 同步监视器是this if (num > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "票号为:" + num); num--; } } public static void main(String[] args) { WindowForRunnable2 windowForRunnable = new WindowForRunnable2(); Thread t1 = new Thread(windowForRunnable); Thread t2 = new Thread(windowForRunnable); Thread t3 = new Thread(windowForRunnable); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } } ``` ##### 使用同步方法的方式实现线程安全(Thread方式) ``` package com.felixfei.study.threads; /** * @describle 创建多线程实现多窗口卖票 * 1.会出现线程安全问题:当当前线程操作过程中,其他线程参与进来,也操作了该数据 * 2.解决方案 使用同步方法来处理继承Thread类中的共享数据问题 */ public class WindowForThread3 extends Thread { // 使用静态变量,共享票数 private static Integer num = 100; @Override public void run() { while (true) { show(); } } private static synchronized void show() { // 因为锁为当前类,也称为当前多想,所以使用static关键字,使其变为一份show方法 if (num > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "票数:" + num); num--; } } public static void main(String[] args) { WindowForThread3 w1 = new WindowForThread3(); WindowForThread3 w2 = new WindowForThread3(); WindowForThread3 w3 = new WindowForThread3(); w1.setName("窗口1"); w2.setName("窗口2"); w3.setName("窗口3"); w1.start(); w2.start(); w3.start(); } } ``` + 说明: 1. 同步方法同样涉及到同步监视器,只不过不需要显式的声明 2. 非静态的同步方法,同步监视器是:this 3. 静态的同步方法,同步监视器是当前类本身 ###### 使懒汉式单例模式线程安全 ``` package com.felixfei.study.threads; /** * @describle 使用同步机制将单例模式中的懒汉式改为线程安全的 */ public class BankTest { private static BankTest bankTest = null; private BankTest() { } // 方式一:使用同步代码块使线程安全(效率稍差:需要每次进入同步锁的,进行判断) /*public static BankTest getInstance() { synchronized (BankTest.class) { if (bankTest == null) { bankTest = new BankTest(); } } return bankTest; }*/ // 方式二:使用同步代码块使线程安全(效率较快:如果实例对象不是null的话,直接返回,不用进入同步锁中) public static BankTest getInstance() { if (bankTest == null) { synchronized (BankTest.class) { if (bankTest == null) { bankTest = new BankTest(); } } } return bankTest; } // 使用同步方法使线程安全 /*public synchronized static BankTest getInstance() { if (bankTest == null) { bankTest = new BankTest(); } return bankTest; }*/ public static void main(String[] args) { BankTest instance = BankTest.getInstance(); System.out.println(instance); } } ``` ##### 使用Callable接口方式 + 与使用Runnable相比,Callable功能更强大 1. 相比于run方法,可以有返回值 2. 方法可抛出异常 3. 支持泛型的返回值 4. 需要借助FutureTask类,比如获取返回结果 + Future接口 1. 可以对具体的Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等 2. FutureTask是Future接口的唯一的实现类 3. FutureTask同时实现了Runnable,Future接口。它既可以作为Runnable被线程执行,有可以作为Future得到Callable的返回值 ``` package com.felixfei.study.threads; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @describle 创建线程的方式三:实现Callable接口 --JDK 5.0新增 *

* 打印100以内2的余数,并且返回余数之和 */ // 第一步:实现callable接口 class NumThread implements Callable { // 第二步:重写call方法 @Override public Object call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { if ((i % 2) == 0) { System.out.println("当前数字为" + i); sum += i; } } return sum; } } public class CallableTest { public static void main(String[] args) { // 第三步:创建实现Callable接口的类 NumThread numThread = new NumThread(); // 第四步:使用FutureTask调用该类(FutureTask可以获取实现Callable类实现方法的返回值,和一些操作) FutureTask futureTask = new FutureTask(numThread); // 第五步:启动线程 new Thread(futureTask).start(); Object o = null; try { // 第六步:通过futureTask获取返回值 o = futureTask.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("总和为" + o); } } ``` ##### 使用线程池实现多线程 + 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能的影响很大 + 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免创建销毁、实现重复利用。类似生活中的公共交通工具 + 好处: 1. 提高了响应速度(减少了创建新线程的时间) 2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建) 3. 便于线程管理 1. corePoolSize:核心池的大小 2. maximumPoolSize:最大线程数 3. keepAlivetime:线程没有任务时最多保持多长时间会终止 + 如何创建 1. JDK5.0提供了线程池相关API:ExecutorService和Executors 2. ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor 1. void execute(Runnable command);执行任务/命令,没有返回值,一般用来执行Runnable 2. Future submit(Callable task);执行任务,有返回值,一般用来执行Callable 3. void shutdown(); 关闭连接池 3. Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池 1. Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池 2. Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池 3. Executors.newSingleThreadExecutor():创建只有一个线程的线程池 4. ExecutorsnewScheduledThreadPool(n):创建一个线程池,它可以安排在给定延迟后运行命令或者定期的执行 ``` package com.felixfei.study.threads; import java.util.concurrent.*; /** * @describle 创建线程方式四:使用线程池 */ class NumberThread implements Runnable { @Override public void run() { // 打印100以内的偶数 for (int i = 1; i <= 100; i++) { if ((i % 2) == 0) { System.out.println(Thread.currentThread().getName() + "数字" + i); } } } } class NumberThread1 implements Runnable { @Override public void run() { // 打印100以内的奇数 for (int i = 1; i <= 100; i++) { if ((i % 2) != 0) { System.out.println(Thread.currentThread().getName() + "数字" + i); } } } } public class ExecutorTest { public static void main(String[] args) { // 第一步:创建一个指定线程数的线程池 ExecutorService service = Executors.newFixedThreadPool(10); // 设置线程池的属性,因为ExecutorService是接口,属性是常量无法设置,只能设置其实现类的属性 System.out.println("获取这个对象是哪个类" + service.getClass()); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; service1.setCorePoolSize(15); //service1.setKeepAliveTime(15, TimeUnit.DAYS); // 第二步:执行线程的方法 service1.execute(new NumberThread()); // 一般用来执行Runnable // FutureTask fatureTask = service.submit(Callable callable); 一般用来执行Callable service1.execute(new NumberThread1()); // 第三步:关闭连接池 service1.shutdown(); } } ```