多线程

    技术2025-07-10  6

    多线程程序的优点:

    提高应用程序的响应。对图形化界面更有意义,可增强用户体验。提高计算机系统CPU的利用率改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改

    何时需要多线程 1.程序需要同时执行多个任务 2.程序需要实现需要等待的任务,如用户输入、文件读写、网络操作等 3.需要一些后台运行的程序

    创建线程和使用

    Java的JVM允许程序运行多个线程,通过java.lang.Thread 类来体现。

    创建线程的几种方式 1.创建一个Thread的子类并重写run方法,之后子类就可以实例化并start

    定义子类继承Thread类。子类中重写Thread类中的run方法。创建Thread子类对象,即创建了线程对象。调用线程对象start方法:启动线程,调用run方法。 注意一个线程只能start一次 /** * 多线程的创建,继承Thread类方式 * 1) 定义子类继承Thread类。 * 2) 子类中重写Thread类中的run方法。将线程要执行的操作写在run方法中 * 3) 创建Thread子类对象,即创建了线程对象。 * 4) 调用线程对象start方法:启动线程,调用run方法。 * * @author ycy * @create 2020 - 07 -04 -3:27 下午 */ //例子:遍历100内的偶数 //1.创建一个继承与Thread类的子类 class MyThread extends Thread { //子类中重写Thread类中的run方法。 @Override public void run() { for(int i=0;i<1000;++i){//fori为模版 if(i%2==0){ System.out.println(i); } } } } public class ThreadTest { public static void main(String[] args) { //创建子类对象 MyThread t1 = new MyThread(); //start方法 t1.start(); for (int i = 0; i < 100; i++) { if(i%2!=0){ System.out.println("i = " + i); } } System.out.println("hello"); } }

    thread的几种方法

    void start(): 启动线程,并执行对象的run()方法

    run(): 线程在被调度时执行的操作

    String getName(): 返回线程的名称

    void setName(String name):设置该线程名称

    static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类

    static void yield():线程让步 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 若队列中没有同优先级的线程,忽略此方法

    join() : 当某个程序执行流中调用其他线程的 join() 方法时,调用线程将 被阻塞,直到 join() 方法加入的 join 线程执行完为止 低优先级的线程也可以获得执行

    static void sleep(long millis):(指定时间:毫秒) 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后 重排队。 抛出InterruptedException异常

    stop(): 强制线程生命期结束,不推荐使用

    boolean isAlive():返回boolean,判断线程是否还活着

    sleep​(long millis):阻塞一段时间

    线程优先级 MAX_PRIORITY:10 MIN _PRIORITY:1 NORM_PRIORITY:5

    getPriority() :返回线程优先值 setPriority(int newPriority) :改变线程的优先级

    说明:线程创建时继承父线程的优先级 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

    2.实现Runable接口

    定义子类,实现Runnable接口。子类中重写Runnable接口中的run方法。通过Thread类含参构造器创建线程对象。将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。 /** * 1) 定义子类,实现Runnable接口。 * 2) 子类中重写Runnable接口中的run方法。 * 3) 通过Thread类含参构造器创建线程对象。 * 4) 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。 * 5) 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。 * @author ycy * @create 2020 - 07 -04 -5:56 下午 */ class MyThread implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(i); } } } public class ThreadTest2 { public static void main(String[] args) { MyThread myThread = new MyThread(); Thread t1 = new Thread(myThread); t1.start(); } }

    比较: 开发中优先选择实现接口的方式 原因:1.实现方式没有单继承的局限性 2.实现适合处理多线程有共享数据,数据放在实现的类中,以同一个对象来建造多个Thread类对象 联系:thread也实现了Runnable,都需要重写run方法

    方式三:JDK5.0 实现Callable接口 与使用Runnable相比, Callable功能更强大些 相比run()方法,可以有返回值,重写call方法 方法可以抛出异常 支持泛型的返回值 需要借助FutureTask类,比如获取返回结果

    Future接口 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。 FutrueTask是Futrue接口的唯一的实现类 FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值

    import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author ycy * @create 2020 - 07 -05 -7:50 下午 */ //1.创建一个实现Callable的实现类 class NumThread implements Callable { //2.实现call方法,将此线程需要执行的操作声明在call()方法中 @Override public Object call() throws Exception { int sum = 0; for (int i = 0; i <= 100; i++) { if (i % 2 == 0) { System.out.println(i); sum += i; } } return sum; } } public class NewThread { public static void main(String[] args) { //3.创建Callable实现类的对象 NumThread numThread =new NumThread(); //4。将此Callable实现类的对象作为参数传递到FutureTask构造器中,创建该类的对象 FutureTask futureTask = new FutureTask(numThread); //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并start() new Thread(futureTask).start(); try { //6.获取Callable中call方法的返回值 //get()返回值即为FutureTask构造器参数Callable实现类重写的call()返回值 Object sum = futureTask.get(); System.out.println("总和为:"+sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }

    理解callable比tunable强大: 1.有返回值呢 2.可以抛出异常获得异常信息 3.支持泛型

    方式四:使用线程池 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。 好处: 提高响应速度(减少了创建新线程的时间) 降低资源消耗(重复利用线程池中线程,不需要每次都创建) 便于线程管理(防止拥堵,控制频率,不是自己想怎样怎样

    线程池相关API JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors

    ExecutorService:真正线程池接口,常见子类为ThreadPoolExecutor

    1.void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行 Runnable 2. Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable 3.void shutdown() :关闭连接池

    Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池 1。Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池 2.Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池 3.Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池 4.Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运 行命令或者定期地执行。)

    corePoolSize:核心池的大小 maximumPoolSize:最大线程数 keepAliveTime:线程没有任务时最多保持多长时间后会终止

    import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; /** * @author ycy * @create 2020 - 07 -05 -8:33 下午 */ class NumberThread1 implements Runnable{ @Override public void run() { for (int i = 0; i < 101; i++) { if(i%2==0){ System.out.println(Thread.currentThread().getName()+"i = " + i); } } } } class NumberThread2 implements Runnable{ @Override public void run() { for (int i = 0; i < 101; i++) { if(i%2!=0){ System.out.println(Thread.currentThread().getName()+"i = " + i); } } } } public class ThreadPool { public static void main(String[] args) { //1.提供指定线程数量的线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); //返回的是接口实现类对象 //设置线程池的属性 System.out.println(executorService.getClass()); ThreadPoolExecutor service1 = (ThreadPoolExecutor)executorService; service1.setCorePoolSize(15); //2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象 executorService.execute(new NumberThread1());//适合用于Runnable executorService.execute(new NumberThread2()); // executorService.submit();//适合使用与Callable executorService.shutdown();//关闭连接池 } }

    线程的生命周期 Thread.State中定义了几种线程状态 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态

    就绪: 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源

    运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能

    阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中 止自己的执行,进入阻塞状态

    死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

    线程状态关系

    线程同步 由线程没有同步会引起一些问题

    /** * @author ycy * @create 2020 - 07 -05 -11:32 上午 */ class Window implements Runnable { private int tickets = 100; @Override public void run() { while (true) { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + tickets--); } else { break; } } } } public class WindowTest{ public static void main(String[] args) { Window window = new Window(); Thread t1 = new Thread(window); Thread t2 = new Thread(window); t1.setName("窗口一"); t2.setName("窗口二"); t1.start(); t2.start(); } }

    上面的代码中存在窗口1与窗口2同时售出一张票的问题需要解决

    解决方法:当一个线程在操作票数时其他进程不能参与进来,直到该进程操作完毕,即使该进程阻塞也不行,java中通过同步机制来解决线程的安全问题

    方式1:同步代码块 synchronized(同步监视器){ 被同步的代码} 操作共享数据的代码即为需要被同步的代码,即为临界区

    同步监视器即为锁,任何一个类的对象都可以是锁,但要求各同步对象之间必须用同一把锁

    /** * @author ycy * @create 2020 - 07 -05 -11:32 上午 */ class Window implements Runnable { private int tickets = 100; Object obj = new Object(); @Override public void run() { while (true) { synchronized (obj) { if (tickets > 0) { System.out.println(Thread.currentThread().getName() + tickets--); } else { break; } } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class WindowTest { public static void main(String[] args) { Window window = new Window(); Thread t1 = new Thread(window); Thread t2 = new Thread(window); t1.setName("窗口一"); t2.setName("窗口二"); t1.start(); t2.start(); } }

    上面是运用了同步机制的代码,obj可换成其他对象,也可以用Window.class(类也是对象,也是唯一的),其中sleep是为了能看到两进程之间切换所加,发现之后不会有重票出现

    方式2:同步方法 如果操作共享数据的代码完整声明在一个方法中,我们可以声明方法是同步的。形式:public synchronized void run(),但不应该用run

    class Window1 implements Runnable { private int tickets = 100; Object obj = new Object(); @Override public void run() { while (true) { show(); if(tickets==0) break; } } private synchronized void show() {//同步监视器:this if (tickets > 0) { System.out.println(Thread.currentThread().getName() + tickets--); } try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } } public class WindowTest2 { public static void main(String[] args) { Window1 window1 = new Window1(); Thread t1 = new Thread(window1); Thread t2 = new Thread(window1); t1.setName("窗口一"); t2.setName("窗口二"); t1.start(); t2.start(); } }

    对于继承Thread类方式需要用 private static synchronized void show(),此时同步监视器为Window.class

    总结:1.同步方法仍然涉及到监视器,只是不显示声明 2.非静态同步方法,监视器:this 静态同步方法:当前类本身

    线程安全的单例模式(懒汉式)

    /** * 同步懒汉 * * @author ycy * @create 2020 - 07 -05 -2:27 下午 */ class Bank { private Bank() { } private static Bank bank = null; // public static synchronized Bank getBank() { // //方式一:效率差 // if (bank == null) { // bank = new Bank(); // } // return bank; // } //方式二:效率高 public static Bank getBank(){ if (bank==null){ synchronized (Bank.class){ if(bank==null) bank = new Bank(); } } return bank; } }

    方式三:锁 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

    java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

    ReentrantLock 类实现了Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

    import java.util.concurrent.locks.ReentrantLock; /** * @author ycy * @create 2020 - 07 -05 -3:25 下午 */ class Window3 implements Runnable { private int tickets = 100; //1.实例化ReentrantLock private ReentrantLock lock = new ReentrantLock(true); //fair:不让一个一直打 @Override public void run() { while (true) { try { //2.调用lock lock.lock(); if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + tickets--); } else { break; } } finally { //3.调用解锁方法 lock.unlock(); } } } } public class LockTest { public static void main(String[] args) { Window3 w = new Window3(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }

    这种方法需要手动开关锁

    死锁:线程之间相互占有对方需要的资源并不放弃占有,又向对方提出资源申请导致了相互等待

    线程通信 涉及的三个方法: wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步锁。

    notify()一旦执行此方法,就会唤醒被wait()的一个线程,如果有多个线程被wait,就唤醒优先级高的

    notifyAll():一旦执行此方法,就会唤醒所有被wait的线程

    注意点: 1.三个方法只能出现在同步代码块或同步方法中 2.三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常

    Processed: 0.010, SQL: 9