多线程的作用:有的线程需要I/O,有的需要CPU资源,多线程编程的目的就是充分使用CPU资源,达到加快程序执行的目的。
线程之间的相互作用:线程之间通信
(生产者-消费者问题:可以看成两个线程,生产者一个线程,消费者一个线程,生产者这生产一个东西告诉消费者可以消费了,消费者消费完这个东西就通知生产者可以生产了,绝对不允许生产者连续生产而无消费者消费的情况)
(哲学家就餐问题: 一群哲学家围着一个圆桌吃饭,吃饭需要筷子,但是筷子并不够,并不能每个人分到一双筷子,筷子的分布情况是这样的: 哲学家的左手和右手边分别有一只筷子,一只筷子是无法吃饭,出现的情况时哲学家每个人手里拿着一只筷子等待着别人释放筷子,但是每个哲学家都这么想,都在等待,因此大家都只有一只筷子没办法吃饭,就饿死了,在操作系统中这种现象被称为:死锁 deadlock)
wait 方法:造成当前线程等待,直到另外一个线程调用了notify或者notifyAll方法 ,当前线程必须拥有该对象的 monitor 才可以,意味着当前线程在该对象的synchronized快里或者synchronized方法里面。
wait 方法的调用必须在synchronized快里或者synchronized方法里面调用才可以 不能被重写 wait(time) 方法, 定义等待线程多长时间
synchronized (obj) {
while (<condition does not hold>)
obj.wait(); // 等待notify通知,但是得到通知了并不会一定会被唤醒,它必须要获取到该对象的锁才能继续往下执行
... // Perform action appropriate to condition
}
notify() 和notifyAll() 也是在synchronized快里或者synchronized方法里面调用才可以 不能被重写
当一个线程执行完毕,释放掉了对象的锁,调用了 notify()方法,就随机的选择了一个正在等待的线程被唤醒,这个线程不是由我们决定的,而是系统自己决定的,
调用了 notifyAll()方法,就通知所有正在等待的线程,然后多个线程通过竞争得到对象的锁。
wait 与 notify 方法都是定义在 Object 类中,而且是 final 的,因此会被所有的 Java类所继承并且无法重 写。这两个方法要求在调用时线程应该已经获得了对象的锁,因此对这两个方法的调用需要放在 synchronized 方法或块当中。当线程执行了 wait方法时,它会释放掉对象的锁。
sleep:另一个会导致线程暂停的方法就是 Thread 类的 sleep 方法,它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的 (这是跟wait方法不同的一点)。
wait方法一般都是要加异常处理的,这是因为:当某代码并不持有监视器的使用权时,去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。
等待池(wait blocked pool ):线程使用了wait方法之后,就进入了等待池,
锁池 (lock blocked pool ):当一个线程释放掉锁后,使用notify或者notifyAll方法通知等待池中的线程,等待池中的线程就会进入锁池,等待获取对象的锁。
// 这个例子可以看做是一个生产者消费者问题
public class WaitTest {
public static void main(String[] args) {
Sample sample = new Sample();
AddThread addThread1 = new AddThread(sample);
AddThread addThread2 = new AddThread(sample);
PlusThread plusThread1 = new PlusThread(sample);
PlusThread plusThread2 = new PlusThread(sample);
// 启动了两个增加线程,启动了两个减少线程
// 线程的调试是非常困难的,而且无法使用try catch语句捕获线程的异常
// 建议为每一个线程或者线程池设置名字,
addThread1.start();
addThread1.setName("add1"); //为线程设置名字
addThread2.start();
plusThread1.start();
plusThread2.start();
// 这样输出的结果是不正确的,并不是 1,0 交替执行,为什么呢?
//因为线程在wait后直接调用后面的代码了,而没有再一次的检查是否符合条件
// 将两个synchronized方法里面的判断语句 if 修改为while就可以了
}
}
// 增加的线程
class AddThread extends Thread{
private Sample sample;
public AddThread( Sample sample){
this.sample = sample;
}
public void run(){
for(int i = 0; i < 10; i++){
sample.add();
// 要想实现 0, 1 交替输出,输出语句是不能放在这里的,因为上面的方法虽然被阻塞了
// 但是下面的语句线程是可以执行的,因此会出现乱序问题
// System.out.println(sample.getNumber());
}
}
}
// 减少的线程
class PlusThread extends Thread{
private Sample sample;
public PlusThread( Sample sample){
this.sample = sample;
}
public void run(){
for(int i = 0; i < 10; i++){
sample.plus();
// System.out.println(sample.getNumber());
}
}
}
class Sample{
private int number;
public int getNumber() {
return number;
}
public synchronized void add(){
// if (0 != number){
while (0 != number){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
number++;
System.out.println(number); // 输出语句应该放在同步代码快中
notifyAll();
}
public synchronized void plus(){
// if (0 == number){
while (0 == number){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
number --;
System.out.println(number);
notifyAll();
}
}
join() 方法:
当一个线程A在另一个线程B之中调用了join方法,或者在B线程之前调用了join方法,则B 必须等待A执行完之后才能执行。但是要注意的是,A必须在B启动(调用start方法)之前调用join方法,否则不起任何作用,下面的例子说明了此点。
// 线程变成了顺序执行, t1 执行完毕之后--》 t2 执行完毕之后 --》main线程里的最后一句输出语句
public static void main(String[] args){
t1.start();
t1.join();
t2.start();
t2.join();
System.out.println("main Thread");
}
// 线程的执行顺序, t1和t2交替执行,等到t1和t2都执行完毕之后才会调用main线程的输出语句
public static void main(String[] args){
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("main Thread");
}
Thread.interrupt()方法
首先,看看Thread类里的关于中断的有关几个方法的信息:
public static boolean interrupted() : 测试当前线程是否已经中断, 线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false。
public boolean isInterrupted() 测试线程是否已经中断。线程的中断状态不受该方法的影响。
public void interrupt() 中断线程。调用此方法将会设置该线程的中断状态位,即设置为true,线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会停止一个正在运行的线程(而且stop方法是不被建议使用的)。
注意的是,Java的中断是一种协作机制,也就是说调用线程对象的interrupt方法并不一定就中断了正在运行的线程,每个线程都有一个boolean的中断状态,interrupt方法仅仅只是将该状态置为true,中断之后会抛出一个InterruptedException, 中断的结果,线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身对于中断的异常处理的情况。
可以中断的有哪些?
第一: sleep, wait , join, 都是可以被中断的
第二: I/O操作,
传统的的I/O操作(阻塞式,流的形式)被阻塞时,是不可以被中断的,调用流的close方法也会被阻塞;
Java 1.4中引入的新的I/O —NI/O (基于通道的,非阻塞式的)是可以被中断的,可以调用流的close方法关闭流,NI/O的特性之一就是当文件读取堵塞时可以进行其他操作,而不会一直阻塞,而且一个I/O连接可以在空闲时帮助其他的线程进行I/O进行工作。
第三; synchronized在获锁的过程中是不能被中断的, 也就是说死锁状态的线程也是无法被中断的。
那么如何停止一个线程?
不建议直接使用stop方法,最好的方式是使用共享变量的方式来停止线程, 线程周期性的检查这一个变量,然后有序的中止任务。
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁 (Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。