我们现在所使用操作系统都是多任务操作系统(早期使用的DOS操作系统为单任务操作系统),多任务操作指在同一时刻可以同时做多件事(可以同时执行多个程序)。
多进程:每个程序都是一个进程,在操作系统中可以同时执行多个程序,多进程的目的是为了有效的使用CPU资源,每开一个进程系统要为该进程分配相关的系统资源(内存资源);
多线程:线程是进程内部比进程更小的执行单元(执行流|程序片段),每个线程完成一个任务,每个进程内部包含了多个线程每个线程做自己的事情,在进程中的所有线程共享该进程的资源;
主线程:在进程中至少存在一个主线程,其他子线程都由主线程开启,主线程不一定在其他线程结束后结束,有可能在其他线程结束前结束。Java中的主线程是main线程,是Java的main函数;
当应用场景为计算密集型时:为了将每个cpu充分利用起来,线程数量正常是cpu核数+1,还可以看jdk的使用版本,1.8版本中可以使用cpu核数*2。
当应用场景为io密集型时:做web端开发的时候,涉及到大量的网络传输,不进入持,缓存和与数据库交互也会存在大量io,当发生io时候,线程就会停止,等待io结束,数据准备好,线程才会继续执行,所以当io密集时,可以多创建点线程,让线程等待时候,其他线程执行,更高效的利用cpu效率,他有一个计算公式,套用公式的话,双核cpu理想的线程数就是20。
采用多线程技术的应用程序可以更好地利用系统资源。主要优势在于充分利用了CPU的空闲时间片,用尽可能少的时间来对用户的要求做出响应,使得进程的整体运行效率得到较大提高,同时增强了应用程序的灵活性。由于同一进程的所有线程是共享同一内存,所以不需要特殊的数据传送机制,不需要建立共享存储区或共享文件,从而使得不同任务之间的协调操作与运行、数据的交互、资源的分配等问题更加易于解决。
多线程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、死锁、线程不安全等等。
进程:是正在运行中的程序,是系统进行资源调度和分配的的基本单位。
线程:是进程的子任务,是任务调度和执行的基本单位;
一个程序至少有一个进程,一个进程至少有一个线程,线程依赖于进程而存在;
进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。线程与进程相似,但线程是一个比进程更小的执行单位。
一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
a.继承 Thread 类;b.实现 Runnable 接口;c. 实现Callable接口;d. 使用线程池。
我们可以通过继承Thread类或者调用Runnable接口来实现线程,因为Java不支持类的多重继承,但允许你调用多个接口。所以如果你想要继承其他的类,当然是调用Runnable接口好了。
如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。
启动一个线程需要调用 Thread 对象的 start() 方法;
调用线程的 start() 方法后,线程处于可运行状态,此时它可以由 JVM 调度并执行,这并不意味着线程就会立即运行;
run() 方法是线程运行时由 JVM 回调的方法,无需手动写代码调用;
直接调用线程的 run() 方法,相当于在调用线程里继续调用了一个普通的方法,并未启动一个新的线程。
线程通常有五种状态:创建,就绪,运行,阻塞和死亡状态
(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()方法,该线程结束生命周期。
多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
使用Thread类的setDaemon(true)方法可以将线程设置为守护线程,需要注意的是,需要在调用start()方法前调用这个方法,否则会抛出IllegalThreadStateException异常。
当我们在Java程序中创建一个线程,它就被称为用户线程。一个守护线程是在后台执行并且不会阻止JVM终止的线程。当没有用户线程在运行的时候,JVM关闭程序并且退出。一个守护线程创建的子线程依然是守护线程。
Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。
Windows系统下执行java -jar arthas-boot.jar
Linux系统下解压arthas,执行ps -ef | grep java找出java进程pid数字
new 一个 Thread,线程进入了新建状态;调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行.
Callable接口类似于Runnable,从名字就可以看出来,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。可以认为是带有返回值的Runnable.Future接口表示异步任务,是还没有完成的任务给出的未来结果。所以说Callable用于产生结果,Future用于获取结果。
(1) 抢占式调度策略
Java运行时系统的线程调度算法是抢占式的 (preemptive)。Java运行时系统支持一种简单的固定优先级的调度算法。如果一个优先级比其他任何处于可运行状态的线程都高的线程进入就绪状态,那么运行时系统就会选择该线程运行。新的优先级较高的线程抢占(preempt)了其他线程。但是Java运行时系统并不抢占同优先级的线程。换句话说,Java运行时系统不是分时的(time-slice)。然而,基于Java Thread类的实现系统可能是支持分时的,因此编写代码时不要依赖分时。当系统中的处于就绪状态的线程都具有相同优先级时,线程调度程序采用一种简单的、非抢占式的轮转的调度顺序。
(2) 时间片轮转调度策略
有些系统的线程调度采用时间片轮转(round-robin)调度策略。这种调度策略是从所有处于就绪状态的线程中选择优先级最高的线程分配一定的CPU时间运行。该时间过后再选择其他线程运行。只有当线程运行结束、放弃(yield)CPU或由于某种原因进入阻塞状态,低优先级的线程才有机会执行。如果有两个优先级相同的线程都在等待CPU,则调度程序以轮转的方式选择运行的线程。
抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。
线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。同上一个问题,线程调度并不受到 Java 虚拟机控制,所以由应用程序来控制它是 更好的选择(也就是说不要让你的程序依赖于线程的优先级)。时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程。分配 CPU 时间可以基于线程优先级或者线程等待的时间。
两者最主要的区别在于:sleep 方法没有释放锁,而 wait 方法释放了锁 。
两者都可以暂停线程的执行。
wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。
因为这些方法的调用是依赖锁对象,而同步代码块的锁对象是任意。锁而Object代表任意的对象,所以定义在这里面。
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。当前线程到了就绪状态,那么接下来具体是哪个个线程会从就绪状态变成执行状态就要看系统的分配了。
Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。
Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。
线程在运行过程中,有些时候可能需要中断一些阻塞的线程,类Thread中提供了几种中断线程的方法,其中Thread.suspend()和Thread.stop()方法已经过时了,因为这两个方法是不安全的。Thread.stop(),会直接终止该线程,并且会立即释放这个线程持有的所有锁,而这些锁恰恰是用来维持数据的一致性的,如果此时。写线程写入数据时写到一半,并强行终止,由于此时对象锁已经被释放,另一个等待该锁的读线程就会读到这个不一致的对象。Thread.suspend()会导致死锁,Thread.resume()也不能使用。
Java 提供了很丰富的API 但没有为停止线程提供 API。JDK 1.0 本来有一些像stop(), suspend() 和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的JDK 版本中他们被弃用了.之后Java API 的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。当run() 或者 call() 方法执行完的时候线程会自动结束, 如果要手动结束一个线程.你可以用volatile 布尔变量来退出 run()方法的循环或者是取消任务来中断线程。
interrupted:查询当前线程的中断状态,并且清除原状态。如果一个线程被中断了,第一次调用 interrupted 则返回 true,第二次和后面的就返回 false 了。
isInterrupted仅仅是查询当前线程的中断状态。
1)notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会;
2)notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会;
每一个线程都是有优先级的.一般来说.高优先级的线程在运行时会具有优先权. 但这依赖于线程调度的实现.这个实现是和操作系统相关的(OS dependent)。我们可以定义线程的优先级.但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程优先级是一个int 变量(从 1-10).1 代表最低优先级.10 代表最高优先级。
线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。
举个例子,假设Thread2中new了Thread1,main函数中new了Thread2,那么:
(1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2自己调用的;
(2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1自己调用的;
简单的说,如果异常没有被捕获该线程将会停止执行。Thread.UncaughtExceptionHandler 是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造成线程中断的时候JVM 会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler 并将线程和异常作为参数传递给handler 的uncaughtException()方法进行处理。
(1)线程的生命周期开销非常高
(2)消耗过多的CPU 资源
如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置。大量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量的线程在竞争CPU 资源时还将产生其他性能的开销。
(3)降低稳定性
JVM 在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并且承受着多个因素制约,包括JVM 的启动参数、Thread 构造函数中请求栈的大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出OutOfMemoryError 异常。
wait();(强迫一个线程等待)
notify();(通知一个线程继续执行),
notifyAll()(所有线程继续执行),
sleep()(强迫一个线程睡眠N毫秒),
join()(等待线程终止)
yield()(线程让步)等等;
FutureTask 表示一个异步运算的任务。 FutureTask 里面可以传入一个 Callable 的具体实现类,可以对这
个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于 FutureTask
也是 Runnable 接口的实现类,所以 FutureTask 也可以放入线程池中。
多个线程在正常情况下的运行是互不干扰的,但是CUP对线程的切换是随机的,这样线程运行的过程就脱离了我们的控制,如果我们想让多个线程之间有规律的运行,就需要线程通讯,线程之间通信的可以让多个线程按照我们预期的运行过程去执行。
1)wait()和notify()
wait(): 当前线程释放锁并且进入等待状态。
notify(): 唤醒当前线程,上面wait() 的时候线程进入了等待状态,如果我们想让线程执行需要通过notify()唤醒该线程。
notifyAll(): 唤醒所有进入等待状态的线程。
2)join()方法
join()方法的作用是使A线程加入B线程中执行,B线程进入阻塞状态,只有当A线程运行结束后B线程才会继续执行。
3)volatile关键字
volatile 关键字是实现线程变量之间真正共享的,就是我们理想中的共享状态,多个线程同时监控着共享变量,当变量发生变化时其它线程立即改变,具体实现与JMM内存模型有关。