多线程(并发执行) 您所在的位置:网站首页 多线程执行顺序是乱的 多线程(并发执行)

多线程(并发执行)

2024-07-14 18:16| 来源: 网络整理| 查看: 265

一、概念区分 1、并行与并发

并行

​ 当系统有一个以上CPU时,同一时刻,当一个CPU在执行一个任务时,另一个CPU在执行另一个任务,两个任务互不抢占CPU资源,可以同时进行(多核CPU,一个CPU执行一个进程)

并发

​ 一个CPU,同一时间,有多个任务在执行。但并发不是真正意义上的“同时进行”,只是将CPU划分成好几个时间片段,每个片段内执行一个任务,然后在这几个片段之间来回切换,由于CPU处理速度快,让用户感觉像是多个任务在同时执行。

区别:

并行是某一时刻,真正有多个程序在运行;并发是在一段时间内,宏观上多个程序同时运行。

并发,指多个事情,在同一时间段内同时发生了;多个任务之间是相互抢占资源的

并行,指多个事情,在同一时间点上同时发生了;多个任务之间是不相互抢占资源的

只有在多个CPU或CPU多核时,才会发生并行,否则看似同时发生的事情,都是并发的

2、进程与线程

进程

​ 指系统中正在运行的一个应用程序;是资源分配的最小单位

线程

​ 是进程内独立执行的一个单一顺序的控制流;是系统分配处理器时间资源的基本单位;是程序执行的最小单位

在这里插入图片描述

区别

进程之间数据不共享线程之间可以共享资源 二、线程的生命周期

​ 生命周期:在程序开发中,一个对象从被实例化完成,到这个对象使用结束并销毁的整个过程,类似于人的一生

​ 线程的生命周期:一个线程被实例化,到这个线程销毁的整个过程

线程的状态 新建:New

​ 一个线程被实例化完成,但是还没有做任何动作

就绪:Ready

​ 一个线程已经被启动 (调用start()方法),开始争抢CPU的时间片

运行:Run

​ 一个线程抢到了CPU的时间片,开始执行这个线程中的逻辑

阻塞:Interrupt

​ 一个线程在运行的过程中,受到某些操作的影响,放弃已经获取的CPU时间片,并且不再参与CPU时间片的争抢,此时线程处于挂起状态

死亡:Dead

​ 一个线程对象需要被销毁 在这里插入图片描述

三、开启线程的方式 1、继承Thread类,实现其run()方法 //要自定义一个线程类,并且该类要继承Thread类 class MyThread extends Thread{ //重写run方法 @Override public void run() { for(int i=0;i public static void main(String[] args) { MyThread mt=new MyThread(); //新建 mt.start(); //就绪 System.out.println("主线程逻辑执行结束"); } } /*输出结果: 主线程逻辑执行结束 子线程逻辑:0 子线程逻辑:1 子线程逻辑:2 子线程逻辑:3 子线程逻辑:4 */

如果是串行运行,则“主线程逻辑执行结束”这句话应该最后执行。但由于并发执行的多线程存在,使得主程序逻辑先执行完毕,在执行子线程

注意:只有调用start方法才会启动线程,并且使该线程执行run方法;如果直接调用run方法,则并没有开启线程,即线程不会进入就绪状态。

2、实现Runnable接口,实现其run()方法 /* * Runnable接口是一个函数式接口,可以采用Lambda表达式实现其run方法 */ public class ThreadClass { public static void main(String[] args) { Runnable r1=()->{ for(int i=0;i public static void main(String[] args) { //返回值是int类型 Callable callable=()->{ int result=0; for(int i=0;i integer = task.get(); //该方法会抛出两个异常,需要手动处理 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(integer); } } 4、异同点 继承Thread类,可读性更高,但是如果某个类类继承了Thread类,那么该类将不能再继承其他类,这有可能会破坏原有的继承结构使用Runnable接口,程序可对象降低,但不会破坏继承结构,一般多使用这种方式 四、线程的常用方法 1、线程的命名setName 实例化一个线程,使用setName()方法实例化一个线程的同时,通过构造方法对线程进行命名3、使用用户自定义的线程类,在实例化的同时,进行名字的赋值 需要给自定义线程类添加对应的构造方法 class MyThread extends Thread{ public MyThread() {} public MyThread(String name) { this.setName(name); //使用setName()方法 //super(name); //直接调用父类的构造方法 } } public class ThreadClass { public static void main(String[] args) { //1、实例化一个线程,使用setName()方法 Thread t=new Thread(); t.setName("用户线程1"); System.out.println(t.getName()); //2、实例化一个线程的同时,通过构造方法对线程进行命名 // 构造方法:Thread(Runnable r,String name); Thread t2=new Thread(()->{},"用户线程2"); System.out.println(t2.getName()); //3、使用用户自定义的线程类,在实例化的同时,进行名字的赋值 // 需要给自定义线程类添加对应的构造方法 MyThread t3=new MyThread("用户线程3"); System.out.println(t3.getName()); } } 2、线程休眠sleep(Run->Interrupt) 调用**sleep()**方法,参数:以毫秒为单位的时间差会抛出InterruptedException异常,需要处理使得线程由运行状态变为阻塞状态,当休眠时间到达时,才会重新变为就绪状态。即使此时系统中没有其他可执行的线程,处于sleep的线程也依然不会执行 class MyThread extends Thread{ //重写run方法 @Override public void run() { for(int i=0;i Thread.sleep(1000); //休眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ThreadClass { public static void main(String[] args) { //调用threadSleep方法 threadSleep(); } /****线程休眠****/ public static void threadSleep() { //实例化一个线程 MyThread mt=new MyThread(); mt.start(); } } //输出形式:每隔1秒输出一个i值 3、线程的优先级setPriority 调用**setPriority()**方法,参数:[0,10]范围内的一个整数,默认是5设置优先级,只是设置这个线程可以抢到CPU时间片的概率,并不是优先级高的线程一定能抢到CPU时间片(不是优先级高的线程一定先执行,也不是优先级高的线程执行完再执行其他线程)设置优先级必须要放在线程开始(start)之前 public class ThreadClass { public static void main(String[] args) { threadPriority(); } /****设置线程的优先级***/ public static void threadPriority() { Runnable r=()->{ for(int i=0;i public static void main(String[] args) { Runnable runnable=()->{ for(int i=0;i /* * 当主线程运行到第50次时,调用join方法,那么此时会等join方法加入的线程执行完毕,在执行主线程 * */ if(i==50) { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("main"+i); } } } /*输出:在50之前,主线程和子线程交替执行,但是等到主线程为50时,此时子线程会执行直到100结束,然后主线程才执行 */

6、守护线程setDaemon

如果所有的用户线程结束,那么守护线程会自动死亡;虚拟机不需要等待守护线程执行结束setDaemon默认是false,如果要设置一个线程为守护线程,则改为true即可 public class DaemonTest { public static void main(String[] args) { Runnable r1=()->{ while(true) { System.out.println("守护线程"); } }; for(int i=0;i //描述剩余票的数量 public static int restCount=100; } public class SourseProblem { public static void main(String[] args) { Runnable r=()->{ //当余票大于0时,可以继续售票 while(TicketCenter.restCount>0) { System.out.println(Thread.currentThread().getName()+"卖出一张票,剩余"+ --TicketCenter.restCount+"张"); } }; //四个线程模拟四个售票员,线程名模拟售票员名 Thread t1=new Thread(r,"Thread-1"); Thread t2=new Thread(r,"Thread-2"); Thread t3=new Thread(r,"Thread-3"); Thread t4=new Thread(r,"Thread-4"); t1.start(); t2.start(); t3.start(); t4.start(); } }

输出结果:

在这里插入图片描述

出现临界资源问题,这是因为一个线程在计算余票的过程中,还没来的及将计算、或计算后的结果还没来得及赋给restCount,CPU就被其他线程抢走,此时其他线程中的余票是当前抢到时刻的余票值。

2、解决方法 JVM实现的synchronizedJDK实现的ReentrantLock

方式一:使用同步代码块

用synchronized修饰多线程需要访问的代码

class TicketCenter{ //描述剩余票的数量 public static int restCount=100; } public class SourseProblem { public static void main(String[] args) { Runnable r=()->{ //当余票大于0时,可以继续售票 while(TicketCenter.restCount>0) { //同步监视器 synchronized("") { if(TicketCenter.restCount //描述剩余票的数量 public static int restCount=100; } public class SourseProblem { public static void main(String[] args) { Runnable r=()->{ while(TicketCenter.restCount>0) { soldTicket(); } }; Thread t1=new Thread(r,"Thread-1"); Thread t2=new Thread(r,"Thread-2"); Thread t3=new Thread(r,"Thread-3"); Thread t4=new Thread(r,"Thread-4"); t1.start(); t2.start(); t3.start(); t4.start(); } //同步方法 public synchronized static void soldTicket(){ if(TicketCenter.restCount //描述剩余票的数量 public static int restCount=100; } public class SourseProblem { public static void main(String[] args) { //实例化一个锁对象 ReentrantLock rt=new ReentrantLock(); Runnable r=()->{ while(TicketCenter.restCount>0) { //对临界资源上锁 rt.lock(); if(TicketCenter.restCount public static void main(String[] args) { Runnable runnable1=()->{ synchronized("A"){ System.out.println("A线程持有了A锁,等待B锁"); //此时A线程已经持有A锁了,让它继续持有B锁 /*为了确保产生死锁 try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }*/ synchronized("B"){ System.out.println("A线程持有了A锁和B锁"); } } }; Runnable runnable2=()->{ synchronized("B"){ System.out.println("B线程持有了B锁,等待A锁"); //此时B线程已经持有B锁了,让它继续去持有A锁 synchronized("A"){ System.out.println("B线程持有了A锁和B锁"); } } }; Thread t1=new Thread(runnable1); Thread t2=new Thread(runnable2); t1.start(); t2.start(); } } /*输出结果: B线程持有了B锁,等待A锁 A线程持有了A锁,等待B锁 (程序未结束) */

上述代码其实不能完全产生死锁,如果在A线程获取B锁之前,B线程都没有获得执行机会,那么B线程就不会获取到B锁,此时程序依然会执行,不会产生死锁。为了一定产生死锁情况,可以在A线程执行过程中调用一个sleep方法。

4、线程通信:解决死锁的办法

方式1:synchronized下的通信

wait():等待,当前的线程释放对同步监视器的锁定,并且让出CPU资源,使得当前的线程进入等待队列中notify():通知,唤醒在此同步监视器上等待的一个线程(具体哪一个由CPU决定),使这个线程进入锁池notifyAll():通知,唤醒在此同步监视器上等待的所有线程,使这些线程进入锁池 public class DeadLock { public static void main(String[] args) { Runnable runnable1=()->{ synchronized("A"){ System.out.println("A线程持有了A锁,等待B锁"); //A线程释放A锁(捕获异常) try { "A".wait(); } catch (InterruptedException e) { e.printStackTrace(); } synchronized("B"){ System.out.println("A线程持有了A锁和B锁"); } } }; Runnable runnable2=()->{ synchronized("B"){ System.out.println("B线程持有了B锁,等待A锁"); synchronized("A"){ System.out.println("B线程持有了A锁和B锁"); //此时B线程已经执行完成了,但是A线程任然还在等待,因此需要唤醒A线程 "A".notify(); } } }; Thread t1=new Thread(runnable1); Thread t2=new Thread(runnable2); t1.start(); t2.start(); } } /*输出结果: A线程持有了A锁,等待B锁 B线程持有了B锁,等待A锁 B线程持有了A锁和B锁 A线程持有了A锁和B锁 */

方式2:Lock锁下的通信,采用Condition控制通信。JUC中的类(java.util.comcurrent类)

await():等价于wait()signal():等价于notify()signalAll():等价于notifyAll() 4、多线程下的单例类

懒汉式单例类会出现问题

//定义一个单例类 class Boss{ //构造器私有化 private Boss() { System.out.println("一个Boss对象被实例化了"); } private static Boss instance=null; //外部类只能通过该方法获取Boss类的实例 public static Boss getBoss() { if(instance==null) { instance=new Boss(); } return instance; } } public class SingletonTest { public static void main(String[] args) { Runnable runnable=()->{ Boss.getBoss(); }; //开辟了100条线程去获取这Boss实例 for(int i=0;i //构造器私有化 private Boss() { System.out.println("一个Boss对象被实例化了"); } private static Boss instance=null; public static Boss getBoss() { //同步代码段 synchronized("") { if(instance==null) { instance=new Boss(); } } return instance; } } public class SingletonTest { public static void main(String[] args) { Runnable runnable=()->{ Boss.getBoss(); }; //开辟了100条线程去获取这Boss实例 for(int i=0;i //构造器私有化 private Boss() { System.out.println("一个Boss对象被实例化了"); } private static Boss instance=null; //同步方法 public static synchronized Boss getBoss() { if(instance==null) { instance=new Boss(); } return instance; } } public class SingletonTest { public static void main(String[] args) { Runnable runnable=()->{ Boss.getBoss(); }; //开辟了100条线程去获取这Boss实例 for(int i=0;i public static void main(String[] args) { Runnable r=()->{ System.out.println(Thread.currentThread().getName()); }; //创建线程池,设置大小为10 ExecutorService service=Executors.newFixedThreadPool(10); //执行 service.execute(r); service.execute(r); service.execute(r); service.execute(r); //关闭连接 service.shutdown(); } } /*输出结果: pool-1-thread-3 pool-1-thread-4 pool-1-thread-2 pool-1-thread-1 */ 七、JUC组件 1、未来任务FutureTask

利用Callable创建线程时,有返回值,该值由Future进行封装,FutureTask实现了RunnableFuture接口,而该接口继承自Runnable和Future接口,因此FutureTask既可以当做一个任务执行,也可以有返回值。

当计算一个任务需要很长时间时,可使用FutureTask来封装这个任务,使得主线程在完成自己的任务后在去获取这个计算结果

import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class FutureTaskTest { public static void main(String[] args) { //创建一个Clallable接口,有返回值,给子线程执行 Callable cla=()->{ int result=0; for(int i=0;i System.out.println("主线程任务正在执行"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } }; Thread t2=new Thread(runnable); t2.start(); //得到有返回值的输出 try { System.out.println(futureTask.get()); } catch (InterruptedException | ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /*输出结果: 另一个线程任务正在执行 4950 */ //如果将Callable执行体中的Thread.sleep(10);去掉,则执行结果为:4950 另一个线程任务正在执行。 2、阻塞队列BlockingQueue

利用BlockingQueue作为线程同步的工具,主要用来实现消费者生产者设计模式。详见《生产者消费者设计模式》

3、叉链接ForkJoin

主要用于并行计算中,将大的任务分成小的任务进行计算,再把小任务的结果合并成总的计算结果



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有