Java线程模型
概念
因为Java字节码运行在JVM中,而JVM运行在各个操作系统上,<br>所以当JVM想要进行线程创建和回收的这种操作时,是必须要调用操作系统的相关接口,<br>也就是说JVM线程与操作系统线程之间存在着某种映射关系。<br>这两种不同维度的线程之间的规范和协议呢,就是线程模型<br><br>
模型分类
一对一
用户线程与内核线程建立了一对一的关系<br>缺点:如果用户线程阻塞会直接反映到操作系统上 导致内核状态频繁切换降低性能
多对一
多个用户线程映射到一个内核线程上 用户线程的调度是用户空间来完成的<br>缺点:如果一个用户线程进行了内核的调用并阻塞,其他线程都无法进行内核调用,Java早期就用了这种模型
名词解说
线程主要分两类<br> 内核线程,简称KLT(Kernel Level Thread)<br> 用户线程,简称ULT(User Level Thread)<br><br>
线程
线程状态
<font color="#e65100">1.新建(new)现成已经新建,但是没屌用start()启动线程<br>2.就绪:调用了start()方法就是就绪状态,(sleep(),join yieid等方法调用后也是就绪状态)<br>3.运行(runnable):获得cpu时间片 开始执行run方法中的代码块(就绪状态和运行状态统称为runnable状态)<br>4.阻塞(blocked)阻塞状态不会占用cpu(1)线程等待获取锁 (2)io阻塞 sleep join yieid wait(等都可以阻塞)<br>5.等待(waiting)不会被分配cpu时间片 需要被其他线程显示的唤醒 才会进入就绪状态 object.wait()等待 唤醒方式object.notify() huozhe notifyall()<br>6.限时等待(timetwaiting)例如 sleep(1000) 时间到后自动唤醒<br>7.结束:线程执行完或者发生异常没处理的就是死亡状态</font>
线程操作
1.sleep 休眠: 执行状态变成限时阻塞状态,当线程休眠时间到后 线程不一定会立刻执行 ,因为cpu可能在执行其他线程 线程会进入就绪状态<br>2.interrupt 中断: 如果要中断处于阻塞的线程(object.wait(),Thread.join(),Thread.sleep() 都属于阻塞)则会抛出异常<br> 如果要中断正常运行中的线程则线程不受任何影响 仅仅是线程中断标记设置成了true 通过isinterrupted()可查看线程中断状态<br>3.join 合并: <font color="#7b1fa2">当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行<br> 例如:有 t1 t2 t3 三线程启动 此时有4个线程 main t1 t2 t3 在t1.join的时候 main线程主动让出cpu(阻塞 因为调用了wait方法) 等待t1 线程执行完成后 如果t2也调用了join方法则main线程还是阻塞 等待t2执行完成后在执行 以此类推 等到所有join方法执行完成后在 调用唤醒方法 让main线程进入到就绪状态<br></font>4.yieid让步: 让目前执行的线程放弃cpu的执行权 不会阻塞线程 只是让线程进入就绪状态 然后系统线程调度重启调度<br>5.daemon守护线程:守护线程也称之为后台线程 例如JVM中的GC线程就是守护线程 setDaemon(true)设置守护线程(必须在启动前设置)<br><br>
start()和run()
start()用于启动线程,<br>run()是线程启动后执行的代码入口
thread和runnable
区别:1.使用thread开启线程 可以在子类中直接调用父类的方法(例如线程名称:属性等)<br> 2.(1)使用runnable如果想拿到当前线程的属性方法 则必须先拿到线程的实例(Thread.currentThread())<br> (2)使用runnable创建的类并不是线程类 而是线程类执行的target的目标类 需要将实力传入线程类<br> 也就是(Thread)的构造器才能创建并且执行线程<br> (3)runnable可以更好实现多个线程并发访问的完成同一个任务或资源<br><font color="#f44336">thread和runnable都不能支持返回值</font><br>
callable和futureTask
<font color="#f44336">问题:Callable能否和runnable一样,作为Thread线程的实例的target来执行?<br> 不行 因为Thread中的Target属性为runnable callable接口和runnable并<br> 没有任何继承关系</font>
线程之间的通信
等待-通知模式
<font color="#d32f2f">1.object.wait()(等待),object.notify()唤醒 现成等待和唤醒 需要统一对象锁来开启 要不然不知道是哪个等待或者唤醒哪个<br></font><font color="#f44336"> 使用该方法的时候 必须在拥有对象的同步锁情况下进行</font><br> wati()流程:1.调用改方法后将该线程加入 waitset集中 等待被唤醒<br> 2.释放当前owner权利 让当前线程进入waiting<br> notify() 1.唤醒waitset()中第一条等待的线程 如果是notifyAll()会唤醒所有的等待线程<br> 2.唤醒的线程会从waitSet()移动到EntryList中 让线程具有抢夺owner的权利 线程的状态从 waiting 变成blocked<br> 3.entryList中的线程抢夺到owner的权利后 线程的状态从blocked变成runnable
ThreadPoolExecutor线程池
核心参数
1.int corePoolSize 线程核心数量 也就是最少线程数量<br>2.int maxmumPoolSize 最大线程数量 线程数量大于corepoolSize 小于maximunpoolSize并且阻塞队列已满时才会创建新线程<br>3.long keepAliveTime 空闲线程存活时间 <br>3.阻塞对垒BlockingQUeue<Runnable> <br>4.新线程产生方式ThreadFactory <br>5.拒绝策略 RejectedExecutionHanler<br> (1)<font color="#f44336">AbortPolicy 拒绝策略</font> 线程池队列满了 新任务就会被拒绝 并且抛出异常 也是线程池默认策略<br> (2)<font color="#f44336">DiscardPolicy 抛弃策略</font> 线程池队列满 新任务会被抛弃 不会抛出异常<br> (3)<font color="#f44336">DiscardOldestPolicy 抛弃最老任务</font> 现成队列满了 就会将最早进入队列的任务抛弃<br> (4)<font color="#f44336">CallerRunsPolicy 执行者策略</font> 新任务添加时 如果添加失败 那么提交任务线程会自己去执行该任务 不在线程池中执行<br><br>如何设置核心线程数量<br> 1.IO密集任务 io操作的多 一般是核心cpu核心数的两倍<br> 2.CPU密集任务 主要是大量的计算 等于cpu的核心数 这样才效率高 不频繁切换上下文<br> 3.混合型任务 最佳线程数 = ((线程等待时间+线程CPU时间)/线程cpu时间)+CPU核数<br><br><br>
并发
ThreadLocal
线程本地副本 1,主要作用线程隔离,夸函数传递 能够实现每个线程都有一份变量的本地值,每个线程都有自己的独立ThreadlocaMap空间 <br> key为threadlocal实例 value 为保存的值 也就是threadloca中使用map保存了不同线程的值而已 然后根据不同线程来取不同的值<br><br>会存在内存泄露情况 threadLoca中的key是弱引用 在gc回收的时候key被回收 但是value不会回收 存储的数据多了就容易内存泄露 解决方法就是调用remove()方法<br><br>
ThreadLocal面试问题
我觉得这个文章面试总结的挺他么的到位 nice
面试可能会问的问题
1. <font color="#f44336">i++ 不是线程安全操作</font> <font color="#2196f3">因为它是一个复合操作 其中包含三个jvm指令:内存取值,寄存器增加1,存值到内存(这三个都是原子性操作,但是两个以上的院 原子操作一起进行的就不是原子操作了)</font><br><font color="#2196f3"> 这三个指令都是独立运行的 中间完全可能会出现多个线程并发执行 例如:4个线程同时读取到count=1 并且都进行自增操作</font><br><font color="#2196f3"> 然后存到内存 结果count=2 并不是count=4</font><br><br><font color="#9c27b0">2.object.wait()和notify()</font><font color="#006064">方法为什么要在synchronized同步代码块中使用<br> wait因为jvm释放当前对象锁监视器的owner(使用权) jvm会将当前线程移入到WaitSet()队列 这些操作都是和对象监控器相关的<br> notify jvm对象锁的waitset() 移动到entrylist也都是跟对象锁监视器有关的<br> 什么是对象监视器:例如 : Object obj= new Object() synchronized(obj){} 这个obj就是对象监视器<br></font><font color="#9c27b0"><br></font>3.为什么Java局部变量 方法参数不存在内存可见性问题 在Java中 所有的局部变量和方法参数都不会线程共享 所以也不存在内存可见性问题 所有的object 实例 class实例和数组都存在jvm 堆内存 堆内存存在线程共享 所以有线程可见性问题 <br><br><br><br>