java基础之多线程
进程
操作系统独立调度和执行的基本单元。其运行过程可能与其他进程的执行过程交替执行。
线程
进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。
线程总是属于某个进程,进程中的多个线程共享进程的内存。
一个线程不能独立的存在,它必须是进程的一部分。
一个进程一直运行,直到所有的非守候线程都结束运行后才能结束。
一个多线程程序包含两个或多个能并发运行的部分。程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径。
为啥要使用多线程?
多线程使用了更小的资源开销。
多线程能编写高效率的程序来达到充分利用 CPU 的目的。
有效利用多线程的关键是理解程序是并发、异步
(Async你做一点,我做一点,线程被调用的时机是随机的,线程之间实际上轮换执行,给人“同时”执行的感觉,)执行,
而不是串行(同步Sync)
执行的。
如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。
注意:
通过对多线程的使用,可以编写出非常高效的程序。不过如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。
请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!
各个线程的执行顺序是“杂乱的”,单个线程内部是有序的。
线程的生命周期
五种状态,创建、就绪、运行、阻塞和死亡。
1) 新建状态。在生成Thread线程对象时,调用该对象的start方法之前。
2) 就绪状态。a.调用了线程对象的start方法之后,该线程位于可运行线程池中,变得可运行,等待获取CPU的使用权,就进入了就绪状态,等待JVM里线程调度器的调度。
b.在线程运行或阻塞之后,也会处于就绪状态。
3) 运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。它可以变为阻塞状态、就绪状态和死亡状态。
4) 阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
5) 死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。会被进行垃圾回收。
线程生命周期相关的方法
t.start()
1.通知“线程规划器”线程t1已准备就绪,可以随时调用该线程的run()方法(实际表现即运行该线程的效果);
2.多次调用会出现IllegalThreadStateException异常;
3.start()方法执行的顺序不代表线程启动的顺序。
t.run()
运行该方法就是同步执行了,t没有交给“线程规划器”处理(即不是被线程调用的),而是由main主线程调用run()。
Thread.sleep(…)
会让正在执行的线程休眠,成阻塞状态,但不会释放对象锁资源以及监控的状态。当指定的时间到了之后又会自动恢复运行状态。
t.wait()
是Object类里面的,主要的意义就是让线程放弃当前的对象的锁,进入等待此对象的等待锁定池,其他正在等待此锁的线程可以得到同步锁并运行,只有针对此对象调动notify方法(或notifyAll)后本线程才能够进入对象锁定池准备获取对象锁进入运行状态。
notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果notify方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在notfiy方法后增加一个等待和一些代码,看看效果