java基础--多线程

目录

线程基础

程序和线程和进程的区别

线程的实现方式:

Runnable和Callable有什么区别?

线程的生命周期

线程常用方法

线程安全和锁

线程安全的解决

多线程之间的通信方式

synchronized 和 和 Lock 的 的 区别

什么是死锁

怎么防止死锁

线程池相关

1.线程池重要参数:

2.创建线程池的方式

3.线程池有哪几种类型?

4.你们项目当中什么地方用到了线程池?


线程基础

程序和线程和进程的区别

1.程序是一组机器码合在一起完成特定的功能,在Windows平台上表现为.exe形式。程序开始执行的时候就成为一个正在进行的程序,也就是“进程”。
2.进程是资源分配的最小单位,线程是程序执行的最小单位。
3.进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间。而线程是共享进程中的数据的,使用相同的地址空间,因此 CPU 切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
4.线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。

线程的实现方式:

1.继承Thread类

2.实现Runnable接口

3.实现Callable接口

4.使用线程池来创建和管理线程

Runnable和Callable有什么区别?

runnable 没有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的补充。

线程的生命周期

新建状态  可运行状态  运行状态  阻塞状态  死亡状态
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态:
当线程对象调用了 start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待 JVM 里线程调度器的调度。
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态:
如果一个线程执行了 sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态TERMINATED 执行完成

线程常用方法

1.start() : 线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用 CPU 资源时,就可以脱离创建它的线程独立开始自己的生命周期了。
2.run(): Thread 类的 run()方法与 Runnable 接口中的 run()方法的功能和作用相同,都用来定义线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。
3.sleep(int millsecond): 优先级高的线程可以在它的 run()方法中调用 sleep 方法来使自己放弃 CPU 资源,休眠一段时间。
4.isAlive(): 线程处于“新建”状态时,线程调用 isAlive()方法返回 false。在线程的 run()方法结束之前,即没有进入死亡状态之前,线程调用 isAlive()方法返回 true.
5.currentThread():该方法是 Thread 类中的类方法,可以用类名调用,该方法返回当前正在使用 CPU 资源的线程。
6.interrupt() :一个占有 CPU 资源的线程可以让休眠的线程调用 interrupt()方法“吵醒”自己,即导致休眠的线程发生 InterruptedException 异常,从而结束休眠,重新排队等待 CPU 资源。
7.  join() :线程加入, 主线程需要在子线程执行之后再结束。这就需要用到 join()方法,通过 join 方法,对线程执行顺序进行排序;
8.  yield(): 线程礼让,线程依次运行,理论上平均分配 CPU 时间片,不会抢夺;

线程安全和锁

线程安全的解决

1:使用volatile 关键字来修饰变量,可以实现线程之间的可见性,解决脏读现象,底层是通过限制jvm的重排序来实现的,适用于一个线程修改,多个线程读的场景。
2:使用synchronized同步锁来解决,自动锁的思想,利用jvm的计数器将锁的标记置为1或0
3:使用lock锁的实现,手动锁,需要手动lock和unlock,得注意加锁和解锁的顺序,避免线程死锁
4:使用jdk中提供的线程安全的集合类,比如copyonwriteArrayList或concurrentHashMap等
5:使用juc下面的原子类,底层使用的是cas机制(乐观锁),比如atomicInteger等
6:使用ThreadLock来修饰变量,核心作用是将全局变量局部化,当多个线程要操作同一变量的时候,会复制一份副本去线程局部操作,最终不会改变该变量的值。
7:使用线程池去调度线程处理任务,可以解决并发问题.

多线程之间的通信方式

1、synchronized 同步。本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。
2、while 轮询。其实就是多线程同时执行,会牺牲部分 CPU 性能。不停地通过 while 语句检测条件是否成立 ,从而实现了线程间的通信。
3、wait/notify 机制通过 Object 类的 wait()和 notify()方法,切换线程状态,使线程阻塞或者运行。
4、管道通信。管道流主要用来实现两个线程之间的二进制数据的传播

5、使用手动锁lock的condition的await和signal进行等待和唤醒

synchronized 和 和 Lock 的 的 区别

1、synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
2、synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
3、通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

什么是死锁

当线程 A 持有独占锁 a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。简单来说,就是两个线程为了争抢资源而发生的相互等待的线程阻塞现象。

怎么防止死锁

1、尽量使用 tryLock(long timeout, TimeUnit unit)的方(ReentrantLockReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
2、尽量使用 Java. util.concurrent 并发类代替自己手写锁。
3、尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
4、尽量减少同步的代码块。

线程池相关

1.线程池重要参数:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit,
BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory, RejectedExecutionHandler handler);


corePoolSize: 线程池核心线程的数量;初始化线程数量
maximumPoolSize: 线程池可创建的最大线程数量;最大线程数量
keepAliveTime: 当线程数量超过了 corePoolSize 指定的线程数,并且空闲线程空闲的时间达到当前参数指定的时间时该线程就会被销毁,如果调用过 allowCoreThreadTimeOut(boolean value)方法允许核心线程过期,那么该策略针对核心线程也是生效的;最大空闲存活时间
unit: 指定了 keepAliveTime 的单位,可以为毫秒,秒,分,小时等;时间单位
workQueue: 存储未执行的任务的队列;
threadFactory: 创建线程的工厂,如果未指定则使用默认的线程工厂;
handler: 指定了当任务队列已满,并且没有可用线程执行任务时对新添加的任务的处理策略;

阻塞队列(有界队列arrayBlockingQueue   任务容量有限
          无界队列 linkedBlockingQueue  任务容量无限(很容易造成资源耗尽))

2.创建线程池的方式

线程池创建有七种方式,最核心的是最后一种:
newSingleThreadExecutor():它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目;
newCachedThreadPool():它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列;
newFixedThreadPool(int nThreads):重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads;
newSingleThreadScheduledExecutor():创建单线程池,返回 ScheduledExecutorService,可以进行定时或周期性的工作调度;
newScheduledThreadPool(int corePoolSize) :和 newSingleThreadScheduledExecutor()类似 ,创 建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程;
newWorkStealingPool(int parallelism):这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建 ForkJoinPool,利用 Work-Stealing 算法,并行地处理任务,不保证处理顺序;
ThreadPoolExecutor():是最原始的线程池创建,上面 1-3 创建方式都是对 ThreadPoolExecutor 的封装。

3.线程池有哪几种类型?

原生的线程池有4种
1:单一线程的线程池 singleThreadPool
2:固定数量的线程池  fixedThreadPool
3:可缓存数量的线程池(最常用) cachedThreadPool
4:有定时功能的线程池  schedulerThreadPool

4.你们项目当中什么地方用到了线程池?

1:我们项目当中的tomcat使用了线程池,根据机器性能 设置了最大线程数量2000-10000
2:我们项目当中用户注册成功,发送短信的时候,使用的线程池来进行处理。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值