多线程的理解

多线程

概念

1.进程与线程
进程:进程是程序的一次执行过程,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位。
线程:线程是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
联系: 线程是进程的一部分,一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
区别:理解它们的差别,从资源使用的角度出发。(所谓的资源就是计算机里的中央处理器,内存,文件,网络等等)

2.多线程的概念
对于多核cpu,进程中的多线程并行执行。对于单核cpu,多线程在单cpu中并发执行,根据时间片切换线程

3.并发编程中的三个概念

并发:在同一个cpu中同时执行多个任务
并行:在多个cpu中同时执行多个任务

①可见性: 一个线程对共享变量修改,另一个线程能看到最新的结果
②有序性:一个线程内代码按编写顺序执行
③原子性: 一个线程内多行代码以一个整体运行,期间不能有其它线程的

4.java中volatile关键字的作用
volatile是Java中的关键字,用来修饰被不同线程访问的变量。JMM(Java内存模型)是围绕并发过程中如何处理可见性、原子性和有序性这3个特征建立起来的,而volatile可以保证其中的两个特性。 volatile 能够保证共享变量的可见性有序性,但并不能保证原子性

创建线程方法

1)继承Thread类创建线程
在这里插入图片描述
或者使用内部类的方式直接创建线程
在这里插入图片描述

2)实现Runnable接口创建线程
测试代码:
在这里插入图片描述
创建Runnable实现类:
在这里插入图片描述
在这里插入图片描述

3)使用Callable和Future创建线程

public class ThirdThread
{
     public static void main(String[] args)
     {
           // 创建Callable对象
           ThirdThread rt = new ThirdThread();
           // 先使用Lambda表达式创建Callable<Integer>对象
           // 使用FutureTask来包装Callable对象
           FutureTask<Integer> task = new  FutureTask<Integer>((Callable<Integer>)() -> {
                int i = 0;
                for ( ; i < 100 ; i++ )
                {
                     System.out.println(Thread.currentThread().getName()
                           + " 的循环变量i的值:" + i);
                }
                // call()方法可以有返回值
                return i;
           });
           for (int i = 0 ; i < 100 ; i++)
           {
                System.out.println(Thread.currentThread().getName()
                     + " 的循环变量i的值:" + i);
                if (i == 20)
                {
                     // 实质还是以Callable对象来创建、并启动线程
                     new Thread(task , "有返回值的线程").start();
                }
           }
           try
           {
                // 获取线程返回值
                System.out.println("子线程的返回值:" +  task.get());
           }
           catch (Exception ex)
           {
                ex.printStackTrace();
           }
     }
}

4)使用线程池例如用Executor框架
在这里插入图片描述

创建线程池方法

线程池的创建方式总共包含以下 7 种(其中六种是通过 Executors 创建的,一种是通过 ThreadPoolExecutor 创建的):

1)Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
2)Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
3)Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
4)Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
5)Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
6)Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
7)ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。

线程池种的核心参数7个

  1. corePoolSize 核心线程数目 (最多保留的线程数)
  2. maximumPoolSize 最大线程数目 (核心线程+救急线程)
  3. keepAliveTime 生存时间 ( 针对救急线程)
  4. unit时间单位 ( 针对救急线程)
  5. workQueue (阻塞队列)
  6. threadFactory 线程工厂 (可以为线程创建时起个好名字)
  7. handler 拒绝策略(4种)
    CallerRunsPolicy 谁创建谁运行该线程
    AbortPolicy 报异常
    DiscardPolicy 丢弃该任务(不会报异常)
    DiscardoldestPolicy 丢掉最先加入的队列 将最新的任务加入到队列中

线程的状态

Java种的6种(新建 可运行 终结 阻塞 等待 有时限等待)
1、New新建状态:线程刚被创建,start方法之 前的状态。
2、Runnable运行状态:得到时间片运行中状态,Ready就绪,未得到时间片就绪状态。
3、Blocked阻塞状态: 如果遇到锁,线程就会变为阻塞状态等待另一个线程释放锁。
4、Waiting等待状态: 无限期等待。
5、Time_ Waiting超时等待状态:有明确结束时间的等待状态。
6、Terminated终 止状态:当线程结束完成之后就会变成此状态。

操作系统的5种(新建 就绪 运行 阻塞 终结)

sleep vs wait区别

共同点: wait() , wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态

方法归属不同
①sleep(long) 是Thread的静态方法
②而wait(), wait(long)都是Object的成员方法,每个对象都有

醒来时机不同
①执行 sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来
②wait(long) 和wait()还可以被notify唤醒,wait() 如果不唤醒就一直等下去
③它们都可以被打 断唤醒

锁特性不同
①wait 方法的调用必须先获取wait对象的锁,而sleep则无此限制
②wait方法执行后会释放对象锁,允许其它线程获得该对象锁( 我放弃,但你们还可以用)
③而sleep如果在synchronized代码块中执行,并不会释放对象锁( 我放弃,你们也用不了)

lock vs synchronized区别

二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能

Synchronized 是关键字 在同步代码块中执行完就可以释放锁

lock 是接口 必须使用unlock手都才能释放 一般用在try finally中
Lock提供了许多synchronized不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量
Lock有适合不同场景的实现,如ReentrantLock, ReentrantReadWriteL ock

在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖
在竞争激烈时,Lock 的实现通常会提供更好的性能

悲观锁与乐观锁

1.悲观锁的代表 是synchronized和Lock锁
①其核心思想是[线程只有占有了锁,才能去操作共享变量,每次只有一一个线程占锁成功,获取锁失败的线程,都得停下来等待
②线程从运行到阻塞、再从阻塞到唤醒,涉及线程上下文切换,如果频繁发生,影响性能
③实际上, 线程在获取synchronized和Lock锁时,如果锁已被占用,都会做几次重试操作,减少阻塞的机会
2.乐观锁的代表是 AtomicInteger,使用cas来保证原子性
①其核心思想是[无需加锁,每次只有一一个线程能成功修改共享变量,其它失败的线程不需要停止,不断重试直至成功I
②由于线程一直运行, 不需要阻塞,因此不涉及线程上下文切换
③它需要多核cpu支持,且线程数不应超过cpu核数

threadlocalmap

1.ThreadLocal可以实现资源对象]的线程隔离,让每个线程各用各的[资源对象] ,避免争用引发的线程安全问题,
2. ThreadLocal 同时实现了线程内的资源共享
3.其原理是, 每个线程内有一个ThreadLocalMap类型的成员变量,用来存储资源对象
①调用set 方法,就是以ThreadLocal自己作为key,资源对象作为value,放入当前线程的ThreadLocalMap集合中
②调用get方法,就是以ThreadLocal自己作为key,到当前线程中查找关联的资源值
③调用remove方法,就是以ThreadLocal自己作为key,移除当前线程关联的资源值
4.作用:解决线程隔离
5.为什么 ThreadLocalMap中的key (即ThreadLocal )要设计为弱引用?
是因为线程可能长时间运行,为了释放key中的内存,在jvm垃圾回收的时候可以释放弱引用(key)

多线程之间通信的5种方式(稍后补充代码部分)
方式一:使用Object类的wait() 和 notify() 方法
方式二:既然wait和notify配合synchronized使用能够实现,当然用 ReentrantLock 结合 Condition也可以完成;
方式三:使用 volatile 关键字
方式四:使用JUC工具类 CountDownLatch
方式五:基本LockSupport实现线程间的阻塞和唤醒

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OrangeNickChen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值