一.线程和进程:
几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行的程序就是一个进程。当一个程序运行时,内部可能包含多个顺序执行流,每个执行流就是一个线程。
进程:当一个程序进入内存运行时,即成为一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。
进程具有三个特征:
独立性:进程是系统中独立存在的实体,拥有自己独立的资源,每个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间;
动态性:进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。
并发性:多个进程可以在单个处理器上并行执行,多个进程之间不会相互影响。
线程:是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、程序计数器和局部变量,但不拥有系统资源,它与父进程的其他线程共享进程所拥有的全部资源。运行独立,执行时是抢占式的。
二.线程的创建和启动:
1.继承Thread类创建线程类:
定义thread类的子类,并重写run()方法,该方法代表了线程需要完成的任务;
创建thread子类的实例,创建线程对象;
调用线程对象的start()方法来启动该线程。
public class NewThread extends Thread {
@Override
public void run() {
super.run();
}
}
//创建线程对象,执行start方法
NewThread newThread=new NewThread();
newThread.start();
2.实现Runnable接口创建线程类
定义Runnable接口的实现类,并重写该接口的run()方法;
创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
class RunnThread implements Runnable{
@Override
public void run() {
}
}
RunnThread runnThread=new RunnThread();
new Thread(runnThread);
3.使用Callable和Future创建线程:
Callable接口提供了一个call()方法可以作为线程执行体,call()方法可以有返回值,call()方法可以抛出异常。Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口可以作为Thread类的target。
创建并启动有返回值的线程的步骤:
1.创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建Callable实现类的实例;
2.使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
3.使用FutureTask对象作为Thread对象的target创建并启动新线程;
4.调用FutureTask对象的get()方法来获取子线程执行结束后的返回值。
三.线程的生命周期:
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建(new)、就绪(running)、阻塞(blocked)和死亡(dead)五种状态。
四、线程的控制:
1.join线程:Thread提供了让一个线程等待另一个线程完成的方法---join()方法,当某个程序执行中调用其他线程的join()方法时,调用线程将会被阻塞,直到被join()方法加入的join线程执行完为止;
2、后台线程:后台运行,它的任务是为其他的线程提供服务,后台线程(Deamon Thread),JVM的垃圾回收线程就是典型的后台线程。如所有的前台线程都死亡后台线程会自动死亡。调用Tread对象的setDeamon(true)方法可指定线程设置为后台线程。
3、线程睡眠sleep:如需要当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以使用Thread类的静态sleep()方法来实现。
4、线程让步yield:让当前执行暂停。但他不会被阻塞该线程,只是将线程转入就绪状态。
5、改变线程优先级:每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获取较少的执行机会。setPriority()、setPriority()来设置和返回优先级。
五、线程同步:
1.同步代码块:在run()方法的方法体不具备有同步安全性。Java中多线程支持引入同步监视器使用监视器就是同步代码块。
synchronized(obj){
……
//同步代码块
}
2.同步方法:使用synchronized关键词来修饰的方法。
public synchronized 返回值 方法名(参数){
……
//同步方法的代码
}
3.释放同步监视器的锁定及释放:
当前线程同步方法、同步代码块执行结束,当前线程释放同步监视器;
当前线程的同步方法、同步代码块中遇到break、return终止了该代码块、方法的继续执行,当前线程释放同步监视器;
当前线程在同步代码块、同步方法中出现了未处理的error或exception,导致代码块、方法异常结束时,当前线程释放同步监视器;
当前线程执行同步代码块或同步方法时,程序执行了不同监视器对象的wait()方法,则线程暂停,当前线程释放同步监视器;
当前执行同步块、方法时,程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器;
当前执行的同步块时,其他线程调用了线程的suspend()方法将线程挂起,该线程不会是方法监视器。
六、同步锁(lock):
Lock是控制多个线程对共享资源进行访问的工具,通常,锁定提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先Lock对象。某些锁可允许对共享资源并发访问,如ReadWriteLock(读写锁)、ReentrantLock(可重入锁)、ReetrantReadWriteLock。
//定义锁对象
private final ReentrantLock lock=new ReentranLock();
//定义需要保证线程安全的方法
public void m(){
lock.lock();
try{
//需要保证线程安全的代码
……
//使用finally块来保证释放锁
finally{
lock.lock();
}
}
}
七、死锁:
当两个线程互相等待对方释放同步监视器时就会放生死锁,一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
八、线程通信:
1. 传统的线程通信:
Object类提供的wait()、notify()、notifyAll()三个方法,这三个方法不属于Thread类而是属于Object类,这三个方法必须由同步监视器对象来调用。可分为:
对于使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以在同步方法中直接调用这三个方法。
对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用这三个方法。
wait():导致当前线程等待,直到其他贤臣调用该同步监视器的notify()方法或notifyAll()方法唤醒该线程。
notify():唤醒在此同步监视器上等待的单个线程,如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意的。
notifyAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才执行被唤醒的线程。
2.使用Condition控制线程通信:
如程序不使用synchronized关键字来保证同步,而是使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也不能使用wait()、notify()、notifyAll()方法进行线程通信。当使用Lock对象来保证同步时,Java提供一个Condition类来保证协调,使用Codition可以让已经得到Lock对象却无法继续执行的线程释放Lock对象,Codition对象也可以唤醒其他处于等待的线程。
Condition实例被绑定在一个Lock对象上,要获取特定Lock实例的Codition实例,调用Lock对象的newCondition()方法即可,Condition类提供三种方法:
await():类似于隐式同步监视器上的wait()方法,导致当前线程等待,直到其他线程调用该Condition的signal()方法或signalAll()方法来唤醒该线程。
signal():唤醒在此Lock对象上等待的单个线程。
singalAll():唤醒在此Lock对象上等待的所有线程。
//显示定义Lock对象
private final Lock lock=new ReentrantLock();
//获取定义Lock对象对应的Condition
private final Condition cond=lock.newCondition();
//加锁
lock.lock();
//等待线程
cond.await();
//唤起其他线程
cond.signalAll();
3.使用阻塞队列(BlockingQueue)控制线程通信:
当生产者线程试图向BlockingQueue中放入元素时,如该队列已满,则该线程被阻塞;当消费者线程取出元素时,如该队列已空,则该线程被阻塞。程序的两个线程通过交替向BlockingQueue中放入、取出元素,可很好的控制线程。
BlockingQueue支持两个阻塞的方法:
1).put(E e):尝试把E元素放入BlockingQueue中,如队列满则阻塞该线程;
2).take():尝试从BlockingQueue头部取出元素,如队列空则阻塞线程。
BlockingQueue继承Queue接口,Queue接口有如下方法:
1).在队列尾部插入元素。包括add(E e)、off(E e)、put(E e)方法,当队列已满时,这三个方法分别抛出异常、返回FALSE、阻塞队列;
2).在队列头部删除并返回删除的元素。包含remove()、poll()、take()方法,当队列已空,这三个方法分别会抛出异常、返回FALSE、阻塞队列;
3).在队列头部取出但不删除元素。包括element()、peek()方法,当队列已空,这两个方法分别抛出异常、返回FALSE。
BlockingQueue有以下五个实现类:
1).ArrayBlockingQueue:基于数组实现的BlockingQueue队列;
2).LinkedBlockingQueue:基于链表实现的BlockingQueue队列;
3).PriorityBlockingQueue:并不是标准的阻塞队列,取出队列最小的元素;
4).SynchronsQueue:同步队列,对该队列的存取操作必须交替进行;
5).DelayQueue:底层基于PriorityBlockingQueue,要求集合元素都实现Delay接口,根据集合元素的getDalay()方法的返回值进行排序。
九、线程组:
Java中使用ThreadGroup来表示线程组,它可对一批线程进行分类管理,Java允许程序直接对线程组进行控制。对线程组的控制相当于同时控制这批线程,用户创建的所有线程都属于指定线程组,如程序没显示指定线程属于哪个线程组,则该线程属于默认线程组。在默认情况下,子线程和创建它的父线程处于同一线程组内。
十、线程池:
系统启动一个新线程的成本比较高,因为涉及与操作系统交互,在这种情形下,使用线程池可很好提高性能,尤其是当程序需要创建大量生存期很短暂的线程时,更应考虑使用它线程池。线程池在系统启动时创建大量空闲的线程,程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动一个线程来执行它们的run()或call()方法,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中称为空闲状态,等待下一个Runnable对象的run()或call()方法。
下面是自己写的关于线程池的总结:
十一、ThreadLocal类:
是Thread Local Variable(线程局部变量)的意思,功用其实就是每个使用该变量的线程都提供一个变量值得副本,使得每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。
ThreadLocal从另一个角度来解决多线程的并发访问,ThreadLocal将需要并发访问的资源复制多分,每个线程拥有一份资源,每个线程都拥有自己的资源副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的整个变量封装进ThreadLocal,或者把该对象与线程相关的状态使用ThreadLocal保存。
十二、线程安全的集合类: