进程和线程:
进程:执行中的程序(狭义),一个具有独立功能的程序关于某个数据集合的一次运行活动(广义),每个进程都有独立的代码和数据空间(进程上下文),进程是系统进行资源分配和调度的一个独立单位,进程间的切换开销较大,一个进程包含1--n个线程
线程:线程是进程的一个实体,是CPU调度和分配的基本单位,与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行顺序,线程切换开销小
并发、并行、同步、异步:
并发:在操作系统中,同一个处理机上,一个时间段内有几个程序都处于启动运行到运行完毕之间(但在某个时间点,只有一个程序在执行)
并行:针对多处理器而言,在某一时刻,有多条任务在执行
同步:顺序执行,执行完一个再执行下一个,需要等待,协调运行
异步:彼此独立,在等待某个事件的过程中继续做自己的事
多线程的目的是更好的使用CPU的资源
1、进程的三种状态
就绪状态:进程获得除CPU以外的所有资源,只要获得处理机便可执行
执行状态:进程获得处理机,正在处理机上执行
阻塞状态:进程等待某种事件发生(如等待I/O完成),放弃处理机而处于阻塞状态
2、线程的基本状态
新建
new 创建了一个线程对象,分配了内存
等待
new之后,start()之前
就绪
star()之后,位于CPU的可运行线程池中,等待CPU调度
运行
获得CPU执行权,正在执行run()方法,任何时刻只会有一个线程处于运行状态,只有就绪状态中的线程才有机会进入运行状态
阻塞
等待阻塞:运行的线程调用wait()方法,JVM把线程放入等待池中,只有收到notify()或者notifyAll()消息,才会进入就绪状态(可运行)
同步阻塞:运行的线程在获取对象的同步锁时,该同步锁被其他线程占用,JVM会把这个线程放入锁池
其他阻塞:运行的线程调用sleep()方法,或者发出I/O请求,当sleep()超时或者I/O完成,线程就重新进入就绪状态
死亡
执行完run()
3、创建线程的三种方式
继承Thread类,重写run
实现Runable接口,实现run,实现类的实例作为Thread类的参数传入
通过Callable接口和Future接口
创建Callable实现类,实现call方法
创建该实现类的对象,并用FutureTask类来包装该对象
FutureTask对象作为Thread的参数 创建线程
调用FutureTask的get()获得子线程执行后的返回值
对比:
采用实现Runnable、Callable接口的方式创见多线程时,优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
使用继承Thread类的方式创建多线程时优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类
4、线程状态转换和常用方法
join()
强行执行线程 t.join()
sleep()
让线程休眠(暂停执行)
是线程在指定时间内阻塞,指定时间一过重新进入就绪状态
wait()和notify()
wait(xxx) 当对应的notify()被调用或者超出指定时间,线程就会进入就绪状态
wait() 只有当对应的notify()被调用 才会进入就绪状态
suspend()和resume()
suspend()使线程进入阻塞状态并且不会自动恢复。只有当对应的resume()被调用才会进入就绪状态
yield()
线程礼让
使线程放弃当前获得的CPU时间,但是不进入阻塞,线程任处于就绪状态,随时可以再获得CPU执行
sleep()和wait区别
sleep():Thread中;线程暂停执行指定时间,到时自动进入可运行状态,不会释放对象锁
wait():Object中;线程释放对象锁,进入此对象的等待锁定池,只有针对次对象notify或notifyall,线程才会进入可运行状态
wait()、notify()、notifyall()这三个方法是Object类的,而不是Thread类的,可以用来控制线程的状态
当对象调用了wait():使持有这个对象的线程把对该对象的控制权交出去,然后进入等待状态
当对象调用了notify()/notifyAll():通知一个/所有等待这个对象的控制权的线程参与到对象锁的竞争中
5、线程同步、锁、死锁
线程同步是为了防止多个线程访问同一个数据对象时 对数据造成破环
每个对象都有(且只有)一个内置锁,当程序运行到synchronized同步代码块或同步方法时,对象锁才会起作用
当一个线程A拥有一个对象的锁的时候,其他线程就不可以获得该锁,直到线程A释放锁(线程A退出synchronized代码块或同步方法)
synchronized锁住的是代码还是对象?
答:synchronized锁住的是括号里的对象,对于非static 的synchronized方法,锁住的是对象本身,也就是this
当synchronized锁住一个对象后,其他线程要想获得该对象的锁就必须等线程执行完成释放锁,才能再次给对象加锁
所以在使用synchronized的时候,能缩小代码段的范围就尽量缩小,能在代码块上加同步就不在整个方法上加同步。这叫减小锁的粒度,使代码更大程度的并发,原因就是如果锁住的代码段太长,别的线程就要等很久,等得花儿都谢了
非static的synchronized和static synchronized
前者是实例锁(如果该类是单例,那么也相当于全局锁),后者是全局锁(无论实例有多少个,线程都共享该锁)
static synchronized方法,方法中无法使用this,所以它锁的不是this 而是类的class对象
synchronized(类名.class) 也可以实现全局锁