多线程
目录
1什么是线程
* 线程是程序执行的一条路径, 一个进程中可以包含多条线程
* 多线程并发执行可以提高程序的效率, 可以同时完成多项工作
Java程序运行原理
* Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
表面上是同时进行的,但是实际上是不断的切换的
多线程不是为了提高执行速度,而是提高应用程序的使用率。
线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈。
可以给现实世界中的人类一种错觉:感觉多个线程在同时并发执行。
线程的调度与控制
线程的调度模型分为: 分时调度模型和抢占式调度模型,Java使用抢占式调度模型
通常我们的计算机只有一个 CPU,CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。在单 CPU 的机器上线程不是并行运行的,只有在多个 CPU 上线程才可以并行运行。Java 虚拟机要负责线程的调度,取得 CPU 的使用权,目前有两种调度模型:分时调度模型和抢占式调度模型,Java 使用抢占式调度模型。分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
分时调度模型: 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
抢占式调度模型: 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些.
2 并行并发区别
3 JVM的启动是多线程的吗
* JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
线程状态转换1
新建:采用 new 语句创建完成
就绪:执行 start 后
运行:占用 CPU 时间
阻塞:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁,等待输入的场合
终止:退出 run()方法
多线程不是为了提高执行速度,而是提高应用程序的使用率.
线程和线程共享”堆内存和方法区内存”.栈内存是独立的,一个线程一个栈.
线程状态转换2
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程调度
1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
2、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
3、线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
注意:Thread中suspend()和resume()两个方法在JDK1.5中已经废除,不再介绍。因为有死锁倾向。
4 多线程实现的方式
创建线程方法1
创建线程方法2
推荐使用第一种。
5 多线程实现匿名内部类
一般用匿名内部类来实现。好处就是不用在用一个类来实现Thread;
第一种
第二种
6多线程设置名字以及获取
7 获取当前线程的对象
8 多线程休眠
9 守护线程
从线程分类上可以分为:用户线程(以上讲的都是用户线程),另一个是守护线程。守护线程是这样的,所有的用户线程结束生命周期,守护线程才会结束生命周期,只要有一个用户线程存在,那么守护线程就不会结束,例如 java 中著名的垃圾回收器就是一个守护线程,只有应用程序中所有的线程结束,它才会结束。
其它所有的用户线程结束,则守护线程退出!
守护线程一般都是无限执行的.
10 加入线程
11 礼让线程
yield让出cpu
11 线程优先级
12 同步代码块
有时候多线程会出现这种情况
匿名对象创建对象时,只有创建对象的语句,却没有把对象地址赋值给某个变量。虽然是创建对象的简化写法,但是应用场景非常有限。
13 非静态的同步方法对象锁
字节码对象
14 线程安全问题
会跨过0
解决办法
会出现问题,所以要对它进行同步代码块
15 不能多次启动线程
Java的线程是不允许启动两次的,第二次调用必然会抛出IllegalThreadStateException,这是一种运行时异常,多次调用start被认为是编程错误。
关于线程生命周期的不同状态,在Java 5以后,线程状态被明确定义在其公共内部枚举类型java.lang.Thread.State中,分别是:
- 新建(NEW),表示线程被创建出来还没真正启动的状态,可以认为它是个Java内部状态。
- 就绪(RUNNABLE),表示该线程已经在JVM中执行,当然由于执行需要计算资源,它可能是正在运行,也可能还在等待系统分配给它CPU片段,在就绪队列里面排队。
- 在其他一些分析中,会额外区分一种状态RUNNING,但是从Java API的角度,并不能表示出来。
- 阻塞(BLOCKED),这个状态和我们前面两讲介绍的同步非常相关,阻塞表示线程在等待Monitor lock。比如,线程试图通过synchronized去获取某个锁,但是其他线程已经独占了,那么当前线程就会处于阻塞状态。
- 等待(WAITING),表示正在等待其他线程采取某些操作。一个常见的场景是类似生产者消费者模式,发现任务条件尚未满足,就让当前消费者线程等待(wait),另外的生产者线程去准备任务数据,然后通过类似notify等动作,通知消费线程可以继续工作了。Thread.join()也会令线程进入等待状态。
- 计时等待(TIMED_WAIT),其进入条件和等待状态类似,但是调用的是存在超时条件的方法,比如wait或join等方法的指定超时版本,如下面示例:
public final native void wait(long timeout) throws InterruptedException;
- 终止(TERMINATED),不管是意外退出还是正常执行结束,线程已经完成使命,终止运行,也有人把这个状态叫作死亡。
在第二次调用start()方法的时候,线程可能处于终止或者其他(非NEW)状态,但是不论如何,都是不可以再次启动的。
16 出现死锁的情况
会直接锁死 不要出现同步代码块嵌套
为了避免死锁
17 Runtime
一
Runtime:运行时,是一个封装了JVM的类。每一个JAVA程序实际上都是启动了一个JVM进程,每一个JVM进程都对应一个Runtime实例,此实例是由JVM为其实例化的。所以我们不能实例化一个Runtime对象,应用程序也不能创建自己的 Runtime 类实例,但可以通过 getRuntime 方法获取当前Runtime运行时对象的引用。一旦得到了一个当前的Runtime对象的引用,就可以调用Runtime对象的方法去控制Java虚拟机的状态和行为。
查看官方文档可以看到,Runtime类中没有构造方法,本类的构造方法被私有化了, 所以才会有getRuntime方法返回本来的实例化对象,这与单例设计模式不谋而合
public static Runtime getRuntime()
直接使用此静态方法可以取得Runtime类的实例
二.使用Runtime获取JVM的空间信息
每一个Runtime对象都是JVM实例化的,所以可以通过Runtime类取得相关信息
public class RuntimeDemo01{
public static void main(String[] args){
Runtime run = Runtime.getRuntime(); //通过Runtime类的静态方法获取Runtime类的实例
System.out.println("JVM最大内存量:"+run.maxMemory());
System.out.println("JVM空闲内存量:"+run.freeMemory());
}
}
JVM垃圾回收
public void gc()
public class RuntimeDemo01{
public static void main(String[] args){
Runtime run = Runtime.getRuntime(); //通过Runtime类的静态方法获取Runtime类的实例
System.out.println("JVM最大内存量:"+run.maxMemory());
System.out.println("JVM空闲内存量:"+run.freeMemory());
String str = "Hello"+"World";
System.out.println(str);
for(int i=0;i<2000;i++){
str = str + i;
}
System.out.println("操作String之后的JVM空闲内存量:"+run.freeMemory());
run.gc();
System.out.println("垃圾回收之后的JVM空闲内存量:"+run.freeMemory());
}
}
三Runtime类与Process类
除了观察内存使用量之外,还可以直接使用Runtime类运行本机的可执行程序。
public Process exec(String command)throwsIOException
调用记事本:
public class RuntimeDemo03{
public static void main(String[] args){
Runtime run = Runtime.getRuntime(); //通过Runtime类的静态方法获取Runtime类的实例
try{
run.exec("notepad.exe"); //调用本机程序,此方法需要进行异常处理
}catch(Exception e){
e.printStackTrace();
}
}
}
exec()方法的返回值是Process类,Process类也有一些方法可以使用,比如结束一个进程,通过destroy()结束
注意:Runtime类本身就是单例设计模式的一种应用,因为整个JVM中只存在一个Runtime类的对象,可以使用Runtime类取得JVM的系统信息,或者使用gc()方法释放掉垃圾空间,还可以运行本机的程序
18 Timer
19进程间通信
想让下面的交替执行
20三个或三个以上的进程通信
21 线程组
22 线程池
此处参考https://blog.csdn.net/wanliguodu/article/details/81154294
提到线程线程池我们先来说一下线程池的好处,线程池的有点大概可以概括三点:
(1)重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
(2)能有效控制线程池的最大并发数,避免大量线程之间因互相抢夺系统资源而导致的阻塞现象。
(3)能够对线程进行简单的管理,并提供定时执行以及指向间隔循环执行等功能。
1 线程池的创建
Java SE 5的java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。Executor在客户端和任务执行之间提供了一个间接层;与客户端直接执行任务不同,这个中介对象将执行任务。Executor允许你管理异步任务的执行,而无须显式的管理线程的生命周期。Executor在Java中启动任务的优选方法。
public class CachedThreadPool {
/**
* @param args
*/
public static void main(String[] args) {
class MyRunnable implements Runnable{
private int a = 5;
@Override
public void run() {
synchronized(this){
for(int i=0;i<10;i++){
if(this.a>0){
System.out.println(Thread.currentThread().getName()+" a的值:"+this.a--);
}
}
}
}
}
ExecutorService exec = Executors.newCachedThreadPool();
for(int i=0;i<5;i++)
exec.execute(new MyRunnable());
exec.shutdown();
}
}
ThreadPoolExecutor
(1)ThreadPoolExecutor简介
ThreadPoolExecutor是线程池类。对于线程池,可以通俗的将它理解为“存放一定数量的一个线程集合。线程池允许若个线程同时运行,运行同时运行的线程数量就是线程池的容量。当添加到线程池中的线程超过它的容量时,会有一部分线程阻塞等待,线程池会通过相应的调度策略和拒绝策略,对添加到线程池中的线程进行管理。”
线程池的分类
ExecutorService是Executor直接的扩展接口,也是最常用的线程池接口,我们通常见到的线程池定时任务线程池都是它的实现类。上面的Executors.newCachedThreadPool();中的Executors还有其他静态方法可以调用,每个方法都有不同特性,它们都是直接或间接的通过配置ThreadPoolExecutor来实现自己的功能特性,这四类线程池分别是FixedThreadPool、CachedThreadPool、ScheduledThreadPool以及SingleThreadExecutor。
(1)FixedThreadPool
通过Executor的newFixedThreadPool方法来创建。它是一种线程数量固定的线程池,当线程池处于空闲状态时,它们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。由于FixedThreadPool只有核心线程线程并且这些核心线程不会被回收,这意味着它能过更加快速的相应外界的请求。newFixedThreadPool方法的实现如下,可以发现FixedThreadPool中只有核心线程并且这些核心线程没有超时机制,另外任务队列也是没有大小限制的。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newFixedThreadPool()在调用ThreadPoolExecutor()时,它传递一个LinkedBlockingQueue()对象,而LinkedBlockingQueue是单向链表实现的阻塞队列。在线程池中,就是通过该阻塞队列来实现“当线程池中任务数量超过允许的任务数量时,部分任务会阻塞等待”。关于LinkedBlockingQueue的实现细节,在后续的文章会继续介绍。
有了FixedThreadPool,你可以一次性预先执行代价高昂的线程分配,因而也就可以限制线程的数量了。这可以节省时间,因为你不用为每个任务都固定的付出创建线程的开销。在事件驱动的系统中,这种方式较好。
(2)SingleThreadExecutor
通过Executor的newSingleThreadExecutor方法来创建。这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中,这使得这些任务之间不需要处理线程同步的问题。SingleThreadExecutor方法的实现如下所示:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
这对于你希望在另一个线程中连续运行的任何事物(长期存活的任务)来说,这是很有用的,例如监听进入的套接字连接的任务。它对于希望在线程中运行的短任务也是同样方便,例如,更新本地或远程日志的小任务,或者是事件分发线程。
(3)ScheduledThreadPool
通过Executors的newScheduledPool方法来创建。它的核心线程数量时固定的,而非核心线程数是没有限制的,并且当非核心线程闲置是会被立即回收。ScheduledThreadPool这类线程主要用于执行定时任务和具有固定周期的重复任务,newScheduledThreadPool方法的实现如下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
new DelayedWorkQueue());
}
ScheduledThreadPoolExecutor继承ThreadPoolExecutor,并实现ScheduledExecutorService。
(4)CachedThreadPool
通过Executors的newCachedThreadPool方法来创建。它是一种线程数量不定的线程池,它只有非核心线程,并且其最大线程数为Integer.MAX_VALUE。由于Integer.MAX_VALUE是一个很大的数,实际上就相当于最大线程数可以任意大。当线程池中的线程都是处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务。线程池中的空闲线程都有超时机制,这个超时长为60秒,超过60秒闲置线程就会被回收。和FixedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空集合,这将导致任何任务都会立即被执行,因为在这种场景下SynchronousQueue是无法插入任务的。SynchronousQueue是一个非常特殊的队列,在很多情况下可以把它简单理解为一个无法存储元素的队列,由于它在实际中较少使用,这里就不探讨了。从CachedThreadPool的特性来看,这类线程池比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时,线程池中的线程都会超时而被终止,这个时候CachedThreadPool之中实际上是没有任何线程的,它几乎是不占用任何系统资源的,newCachedThreadPool的实现方法如下:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
2 线程池中任务的添加
1 execute()
说明:execute()的作用是将任务添加到线程池中执行。它分为三种情况:
(1)如果“线程池中任务数量” < “核心池大小” 时,即线程池中少于corePoolSize个任务;此时就新建一个线程,并将该任务添加到线程中进行执行。
(2)如果“线程池中任务数量” >= “核心池大小” ,并且“线程池是允许状态”;此时,则将任务添加到阻塞队列中阻塞等待。在该情况下,会再次确认“线程状态”,如果“第2次读到的线程池状态”和“第1次读到的线程次状态”不同,则从阻塞队列中删除该任务。
(3)如果非上述的两种情况,就会尝试新建一个线程,并将该任务添加到线程中进行执行。如果执行失败,则通过reject()拒绝该任务。
2 addWorker()
addWorker()的作用是将firstTask添加到线程池中,并启动该任务。当core为true是,则以corePoolSize为界限,若“线程池中已有任务数量” >= corePoolSize ,那么返回false;当core为false时,则以maximumPoolSize为界限,若“线程池中已有任务数量” >= maximumPoolSize ,则返回false。addWorker()方***先通过for循环不断尝试更新 ctl状态,ctl 记录了“线程池中任务数量和线程池状态”。更新成功后,在通过try模块来将任务添加到线程池中,并启动任务所在的线程。
从addWorker()方法中,我们可以发现:线程池在添加任务时,会创建任务对应的Worker对象,而一个Worker对象包含了一个Thread对象。通过将Worker对象添加到“线程的workers集合中”,从而实现将任务添加到线程池中。通过启动Worker对应的Thread线程,则执行该任务。
3 submit()
submit()实际上也是通过调用execute()实现的,源码如下:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
3 线程池的关闭
在ThreadPoolExecutor类中的shutdown()方法源码为:
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
// 获取锁
mainLock.lock();
try {
// 检查终止线程池的“线程”是否有权限。
checkShutdownAccess();
// 设置线程池的状态为关闭状态。
advanceRunState(SHUTDOWN);
// 中断线程池中空闲的线程。
interruptIdleWorkers();
// 钩子函数,在ThreadPoolExecutor中没有任何动作。
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
// 释放锁
mainLock.unlock();
}
// 尝试终止线程池
tryTerminate();
}
4 使用Callable
Runnable是执行工作的独立任务,但是它不返回任何值。如果你希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在Java SE 5 中引入的Callable是一种具有类型参数的泛型,它的类型参数表示的是从方法call()中返回的值,并且必须使用ExecutorService.submit()方法调用它,下面是简单示例:
submit()方***产生Future对象,它用Callable返回结果的特定类型进行了参数化。
23互斥锁
- 一个可重入互斥
Lock
具有与使用synchronized
方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。A
ReentrantLock
由线程拥有 ,最后成功锁定,但尚未解锁。 调用lock
的线程将返回,成功获取锁,当锁不是由另一个线程拥有。 如果当前线程已经拥有该锁,该方法将立即返回。 这可以使用方法isHeldByCurrentThread()
和getHoldCount()
进行检查。