一、线程简介
1、进程:抽象概念,没有统一标准定义。它是具有一定独立功能的程序在数据集上的一次动态执行过程,是操作系统进行资源分配和调度的最小单位。
2、线程:抽象概念,是比进程更小的抽象。它是程序执行中一个单一的顺序控制流程,是程序执行的最小单元,是处理器调度和分派的基本单位。
3、区别:a.线程是程序执行的最小单位,进程是操作系统分配资源的最小单位;
b.一个进程可以包含多个线程,线程是进程中代码的不同执行路线;
c.进程之间相互独立,而同一进程下的多个线程间可以共享内存和资源;
d.线程的切换比进程切换块得多;
e.process和thread。
4、多线程:大多多线程是模拟出来的,其实就是并发,一个cpu同一时间点只执行一个线程,因为够快并分段执行多个线程,让人感觉是同时执行。真正的多线程是多个cpu,即多核,每核执行一个线程,同时执行,即为并行。所以这就又引出一个问题,多线程一定比单线程块吗?其实不一定,多线程间轮换和切换上下文是要耗费时间的。情况不同,结果不同。比如IO操作多的程序,一般多线程效率更高;线程数量少,单线程很可能更快。
二、线程实现
1、继承Thread类:
step1:自定义线程类,继承Thread;
step2:重写run()方法,编写方法体代码;
step3:创建自定义线程类实例,调用start()方法启动线程(线程不一定立即执行,有cpu调度安排)。
2、实现Runnable接口
step1: 创建自定义类,实现Runnale接口;
step2:实现run()方法,编写线程执行体;
step3:创建线程对象,调用start()方法启动线程。(使用了代理模式)
小结:继承Thread类通过子类对象start方法启动线程,因为单继承局限性不建议使用;实现runnable接口,传入对象给Thread对象再调用start方法,推荐使用,同一对象被多个线程使用。
3、实现Callable接口
特点:具有返回值,可抛出异常
step1: 自定义类,实现Callable接口,需要指定返回值类型(泛型);
step2: 实现call方法,需要抛出异常;
step3: 创建目标对象--自定义类对象;
step4: 创建执行服务:ExecutorService service = Executors.newFixedThreadPool();
step5: 提交目标对象执行:Future<Boolean> f1 = service.submit(t1);
step6:获取执行结果(自定义类时指定返回值类型):Object r2 = f2.get();
step7:关闭服务:service.shutDownNow()。
三、线程状态
1、创建:new Thread(),线程对象一旦创建就进入到了该状态;
2、就绪:调用start方法后进入该状态,不会立即执行,等待调度和资源;
3、运行:获取资源执行线程,这时候执行线程方法体代码;
4、阻塞:待用sleep、wait、同步锁等,线程进入阻塞状态,阻塞解除后计入就绪状态;
5、死亡:线程正常执行完成后进入死亡状态,进入后不能再次启动。
a) 线程停止:
关于线程的停止,不推荐使用Thread类中的stop、interrup等方法。建议定义一个标志变量或者利用次数,通过控制该变量让线程自己停止。
public void run(){
boolean flag=true;
while(flag){
//coding
}
}
public void setFlag(boolean flag){
this.flag = flag;
}
b)线程礼让:
让当前正在执行的线程暂停,但不阻塞,线程转入就绪状态。cpu重新调度,不一定礼让成功。
c)线程合并(插队):
join()方法,当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。
d)线程观测:
线程虽然理论上有五种状态,但在实际jvm中我们观测到的其实是六种状态,由Thread.State来专门描述:
NEW:一个尚未启动的线程的状态。也称之为初始状态、开始状态。
RUNNABLE:一个可以运行的线程的状态,可以运行是指这个线程已经在JVM中运行了,但是有可能正在等待其他的系统资源。也称之为就绪状态、可运行状态。
BLOCKED:一个线程因为等待监视锁而被阻塞的状态。也称之为阻塞状态。
WAITING:一个正在等待的线程的状态。也称之为等待状态。造成线程等待的原因有三种,分别是调用Object.wait()、join()以及LockSupport.park()方法。处于等待状态的线程,正在等待其他线程去执行一个特定的操作。例如:因为wait()而等待的线程正在等待另一个线程去调用notify()或notifyAll();一个因为join()而等待的线程正在等待另一个线程结束。
TIMED_WAITING:一个在限定时间内等待的线程的状态。也称之为限时等待状态。造成线程限时等待状态的原因有五种,分别是:Thread.sleep(long)、Object.wait(long)、join(long)、LockSupport.parkNanos(obj,long)和LockSupport.parkUntil(obj,long)。
TERMINATED:一个完全运行完成的线程的状态。也称之为终止状态、结束状态。
小结:其中NEW就是创建状态,RUNNALE对应就绪和运行状态,BLOCKED\WAITING\TIMED-WAITING都是阻塞状态,TERMINATED是死亡状态。
线程优先级、守护线程比较简单,略。
四、线程同步
1、并发:多个线程同时操作同一个对象。
2、线程同步:处理多线程时,多个线程访问同一个对象,且某些线程还会修改对象,及处理并发问题,我们就需要线程同步。线程同步是一种机制,多个同时访问同一资源的线程进入资源的等待池 线程队列,前面线程使用完毕,下一个线程再使用。使用队列+锁来解决线程不安全问题,即线程同步。
3、锁:在Java语言中,每一个对象有一把锁。线程可以使用synchronized关键字来获取对象上的锁,不可避免的会出现性能问题。
4、同步实现方法:类中对象声明为private,外部无法访问和修改,只能通过类中提供的方法来访问。使用synchronized修饰方法或代码块,控制对象的锁,从而实现线程同步,保证线程安全。
synchronized是一种修饰符,用户代码块时:synchronized(obj){.....},obj叫做同步监视器,建议使用共享资源。
synchronized修饰方法时,实际锁定的时this对象。
5、线程同步弊端:加锁影响性能,对于读数据无需同步,只有修改数据才用,避免不必要的资源浪费。
6、死锁:多个线程各自占有一些共享资源,并且互相等待其他线程释放占有的资源才能运行,而导致程序停止运行的情形。某一同步代码块“同时拥有两个以上的锁”时,就会发生“死锁”问题。
7、Lock: JDK5开始,java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。Lock接口时控制多个线程对共享资源进行访问的工具,每次只能一个线程对Lock对象加锁,线程开始访问共享资源前应先获得Lock对象。
8、ReentrainLock类实现了Lock接口,它拥有与synchronized相同的并发性和内存语义,它时比较常用的,可以显显式地加锁和释放锁。
关于可重入锁等深入的知识,还需专门探究。
五、线程通信问题
当多个线程之间互为条件时,如“生产者-消费者”模式,仅有synchrozied现实同步时不够的,还需要Object类中的wait()、notify(),以及线程之间进行通信来知道何时调用这些方法。
1、信号灯法:通过一个flag进行判断执行消费还是生产
2、管程法:生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据。
六、线程池
经常创建和销毁线程对性能影响很大,提前创建好很多线程,放入线程池中,使用时获取,用完放回,可以避免频繁创建和销毁,能重复利用。
提高了响应速度,降低了资源消耗,便于管理线程。
java5提供了线程池相关的API:ExecutorService和Executors。
ExecutorService;真正的线程池接口,常见子类ThreadPoolExecutor。
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。