zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java中多线程、多线程的实现方式、同步代码块的方式

2023-09-11 14:22:06 时间

进程

  • 进程的概念
    操作系统(OS)中正在执行的应用程序,目前操作系统允许多个进程同时工作,被称为多进程
  • 多进程并发执行原理
    宏观上并行(一起执行);微观上串行(一个一个的执行),哪一个进程获取CPU时间片该进程获取执行权

线程

概念

在进程中,可以同时执行多个任务,每一个任务可以说是为一个线程,线程是进程的工作单位
线程也被称为轻量级的进程

目前的程序是单线程

以main函数的开始为开始,以main 函数的结束为结束,这个线程被称为主线程(Java中只有能代码实现多线程)

线程的组成部分

(1) cup
获取cpu时间片
(2) 数据
栈空间独立,堆空间共享每一个线程都有自己的独有栈空间;所有线程共享同一堆空间
(3) 程序代码
利用Java代码实现多线程

代码实现多线程的方式

第一种方式
  1. 类继承java.lang.Thread类,同时覆盖run()方法
    注意:线程任务定义在run方法中
  2. 创建线程对象
MyThread t = new MyThread(); 
  1. 开启线程
    利用 start开启线程,jvm执行线程时会自动的 调用run方法
    t.start();
第二种方式
  1. 类实现 java.lang.Runnable 接口,实现接口中 run() 方法
    注意:实现run时,访问修饰符必须是 public ,任务代码定义在run方法中,当前类代表是任务类
  2. 创建目标对象
MyTarget tg = new MyTarget();
  1. 创建线程对象,同时将目标对象作为参数进行传递
Thread t = new Thread(tg);
  1. 开启线程:调用 start方法
    t.start();
第三种方式 – 线程池
  1. 线程池
    线程容器,将预先创建的线程对象存入到线程池中,只要将任务提交给线程池,会分配对象线程对象完成提交任务,线程池中的线程对象可以被重复使用

  2. 好处
    避免频繁的创建线程和销毁,从而提高空间利用和执行效率

  3. 线程池常用的接口和类,位于 java.util.concurrent包中
    3.1 Executor:线程池的顶级接口
    3.2 ExecutorService:是 Executor 的子接口,线程池的核心接口

    1. submit(Runnable task):将线程任务提交给线程池
      submit(Callable<\T> task):将线程任务提交给线程池
    2. shutdown(): 关闭线程池,将线程池的线程对象全部销毁

    3.3 Executors:获取线程池对象的工具类,其中方法基本都为静态方法

    1. static ExecutorService newFixedThreadPool(int n):获取一 个固定数量线程的线程池,参数指定线程池中线程对象的数量
    2. static ExecutorService newCachedThreadPool() :获取动 态数量线程对象的线程池,根据提交的任务需求,不够用时, 则自动完成线程创建
第四种方式:Callable
  1. Callable接口
    位于 java.util.concurrent 包中,类似于Runnable接口的应用,对应的对象代表线程任务
    注意: Callable是泛型接口,泛型约束了接口中方法的返回值的数据类型
  2. 接口中的方法
    V call():带有返回值的方法,同时可以抛出 异常
    Future< T> f= pool.submit(c1);
  3. Future是存储submit提交任务执行之后的结果
    利用 Future中的 get方法获取执行的结果

线程状态

  1. 初始状态
    线程已被创建但尚未执行(start() 尚未被调用)
  2. 可执行状态
    线程可以执行,虽然不一定正在执行。CPU 时间随时可能被分配给该线程,从而使得它执行
  3. 死亡状态
    正常情况下 run() 返回使得线程死亡。调用 stop()或 destroy() 亦有同样效果,但是不被推荐,前者会产生异常,后者是强制终止,不会释放锁
  4. 阻塞状态
    线程不会被分配 CPU 时间,无法执行
    static void sleep(long ms) :如果在某一个线程中调用 Thread.sleep方法,则此线程进入有限期等待状态(计时状态/休眠,单位ms), 同时释放cpu;但是不释放该线程拥有得到锁标记
    public final join():允许其他线程加入到当前线程中(调用join方法语句所在的线程),让调用join方法的线程先执行,等待其他线程任务执行结束之后,再执行自身的任务

线程同步

临界资源

被多个线程共享的同一个对象,此对象被称为临 界资源

原子操作

不可分割的多步操作,被视为一个整体,其执行顺序和步骤不能被打破

线程同步

多线程并发时,为了保证临界资源的正确性,而不能破坏程序中的原子操作(线程同步目的就是为保证原子操作的整体性,从而达到临界资源最终数据的正确性)

线程同步第一种方式:同步代码块

  • 同步代码块对临界资源对象加锁
synchronized( 临界资源对象){
// 原子操作
}
  • 定义位置
    同步代码块定义在方法内部
  • 执行的原理
    线程执行过程中,遇到同步代码块,只有线 程获取临界资源对象锁标记时,才能执行同步代码块{}中的 内容,并且只有{}中内容全部执行完成,才能释放该线程拥 有的锁标记;如果临界资源对象的锁标记被其他线程占用, 该线程进入阻塞状态(Blocked状态-等着需要的锁标记释放, 去获取),直到拿到所需要的锁标记才从阻塞状态结束,同时 再获取到cpu资源,才能继续执行同步代码块{}中内容

    注意:线程同步时,不同的线程必须是抢占同一个对象锁 标记,才能达到线程同步的目的(原子操作不被破坏,临界资 源的正确性)

线程同步第二种方式:同步方法

修饰符 synchronized 返回值 方法名(形参列表){ 
	// 原子操作
}
  • synchronized 修饰方法被称为同步方法
    0 同步方法等价于同步代码块
修饰符 返回值类型 方法名(形参列表){ 
	synchronized(this){
		// 方法中语句(包含原子操作)
	}
}

线程同步第三种方式:Lock

  • Lock
    接口,位于 java.util.concurrent.locks 包中,代表
  • Lock中常用方法
    • void lock():获取锁,如果被占用,则需要等待
    • void unlock():释放锁
  • 实现类
    ReentrantLock

死锁

  1. 死锁现象:两个线程或是多个线程相互占用对方所需要的的 资源,而都不释放,导致彼此之间相互等待对方释放资源, 从而产生无限制的等待现象。
  2. 结果:出现死锁后,不会抛出异常,也没有任何提示,只是所有的线程都处于阻塞状态,如果没有外力介入,程序将无 法继续
  3. 解决方案
  • 尽可能的调整加锁的方案
  • 尽量避免同步的嵌套嵌套
  • 可以采用线程间的通信,等待-通知
    在Object类中有 wait(等待)-notify/notifyAll(通知)方法
    • wait():等待
    1. 让当前进入等待状态,释放cpu的同时,释放拥有的锁标记
    2. wait方法调用必须使用在调用它的对象所在的同步代码块中
synchronized(o){ 
o.wait();
}
  • notify:通知
  1. notify():通知一个线程从等待状态结束
    notifyAll():通知所有的线程从等待状态结束
  2. notify/notifyAll方法的调用必须在调用它的对象所在同步代码块中
synchronized(o){ 
	o.notify();
}
  1. notify和notifyAll只是起到通知的作用,不会释放锁标记
    注意:调用 wait方法的对象和调用notify的对象是同一对象 (临界资源)

整理不易,喜欢请点个赞!
编者微信:1014961803,添加时请备注"CSDN"