一、进程和线程的概念
在操作系统中,每个运行的程序就是一个进程,进程可以执行多个任务,每个任务就是一个线程。
进程在执行过程中拥有自己独立的内存单元,而多个线程共享内存。
进程之间不能共享内存,但线程实现共享内存很容易。
系统创建进程时要为其分配系统资源,而创建线程代价小的多。
二、多线程的创建和启动
1.继承Thread类
2.实现Runnable接口
3.实现Callable接口,重写call方法,然后用FutureTask封装,call方法可以有返回值,可以声明抛出异常。
三种方式的比较
实现Runnable接口和Callable接口的方式:线程只是实现了接口,还可以继承其他类;在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一资源的情况;劣势是要访问当前线程要用Thread.currentThread()
采用Thread方法就不能继承其他类,如果要访问当前线程可以直接用this访问
三、线程的生命周期
新建、就绪、运行、阻塞、死亡
四、控制线程
1.join():让一个线程等待另一个线程执行完成的方法,调用线程将被阻塞,直到被join()加入的join线程执行完为止
2.后台线程:在后台运行的线程,如果前台线程都死亡,则后台线程自动死亡。Thread.setDaemon(true)
3.线程睡眠sleep(long millis),让当前正在执行的线程暂停millis毫秒,并进入阻塞状态
4.线程让步yield(),让当前正在执行的线程暂停,进入就绪状态,只有优先级相同或更高的处于就绪状态的线程才会获得机会执行
5.sleep方法和yield方法比较:
sleep方法暂停当前线程后,会给其他线程执行机会,不会考虑优先级,但yield只会给优先级相同或更高的线程执行机会;
sleep方法会将线程转入阻塞状态,直到经过阻塞时间才会变成就绪状态,而yield方法会将线程进入就绪状态;
sleep方法声明抛出InterruptedException异常,所以调用sleep方法时,要么捕捉异常要么声明抛出该异常,而yield方法则没有抛出任何异常;
sleep方法比yield方法有更好的移植性,通常不建议使用yield方法来控制并发线程的执行。
6.线程的优先级
Thread.setPriority(int newPriority);
newPriority可以是1~10的数字,也可以是Thread类的3个静态常量:MAX_PRIORITY(10),MIN_PRIORITY(1),NORM_PRIORITY(5)
五、线程同步
使用同步代码块:将要同步的代码用synchronized{}包含起来
同步方法:用synchronized修饰某个方法
同步锁Lock:定义一个锁对象(可重入锁) private final ReentrantLock lock = new ReentrantLock(); 加锁:lock.lock(); 释放锁:lock.unlock();
六、线程通信
1.传统的线程通信
可以使用Object类的wait(),notify(),notifyAll()
wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()或notifyAll()来唤醒该线程。wait方法有三种形式:无时间参数的wait(一直等待,直到其他线程通知),
带毫秒参数的wait和带毫微秒参数的wait(这两种方法都是等待指定时间后自动苏醒)。调用wait方法的线程会释放对同步监视器的锁定。
notify():唤醒在此同步监视器上等待的单个线程,选择是任意的,只有当前线程放弃对该同步监视器的锁定后(使用wait方法),才可以执行被唤醒的线程。
notifyAll():唤醒在此同步监视器上等待的所有线程
死锁是两个线程等待对方释放同步监视器,阻塞是等待其他线程唤醒线程,不等待其他线程释放同步监视器
2.使用Condition控制线程通信
如果程序不使用Synchronized关键字来保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐士的同步监视器,也就不能使用wait(),notify(),notifyAll()方法进行线程通信。
当使用Lock对象来保证同步时,java提供了一个Condition类来保持协调。Condition将同步监视器方法分成截然不同的对象,Lock替代了同步方法或同步代码块,Condition替代了同步监视器。
Condition实例被绑定在一个Lock对象上,要获得特定lock实例的Condition对象,调用Lock对象的newCondition()方法即可。Condition提供了如下三个方法:
await():类似于隐式同步监视器的wait(),导致当前线程等待,直到其他线程调用该线程的signal()方法或signalAll()方法来唤醒该线程。该await方法有更多变体,如long awaitNanos(long nanosTimeout),void awaitUninterruptibly(),awaitUtil(Date deadline)等。
signal():唤醒在此Lock对象上等待的单个线程
signalAll():唤醒在此lock对象上等待的所有线程
3.使用阻塞队列(BlockingQueue)控制线程通信
Java5提供了一个BlockingQueue接口,是queue的子接口,但它主要用途不是作为容器,而是作为线程同步的工具。
在队列尾部插入元素:add(E e),offer(E e),put(E e),当该队列已满时,这3个方法分别会抛出异常,返回false,阻塞队列。
在队列头部删除并返回删除的元素:remove(),poll(),take(),当该队列已空时,这3个方法分别会抛出异常,返回false,阻塞队列。
在队列头部取出但不删除元素:element(),peek(),当队列已空时,这两个方法分别抛出异常,返回false。