简单来讲,进程就是正在运行的程序,进程在内存中有独立的地址空间,与其他进程不共享数据。线程则是进程内部的执行单元,一个进程内的多个线程是共享堆空间的。所以编写多线程程序特别需要注意数据的并发访问和维护线程间的关系。
线程的创建、启动
Java内置了对多线程的支持,可以通过继承java.lang.Thread或实现java.lang.Runnable方便地创建自己的线程。
定义线程的两种方式:
1、自定义一个类,继承Thread类,并覆盖run()方法。
2、自定义一个类,实现Runnable接口,并覆盖run()方法,将自定义类的实例传给Thread的构造方法。
在使用上,方式1更直接简单,方式2更灵活。
线程对象调用start()方法启动线程,run()方法中的代码会被新建的线程执行。线程有点像能并发执行的方法。注意,一定是通过start()来启动线程,手工调用run()方法跟调用普通方法没有区别。
线程的6种状态
这些状态用Thread.State枚举类来表示:
NEW 已创建:线程异被创建,但还没有调用start()开启。
RUNNABLE 可运行:线程正在运行,或者一抢到执行权就马上运行。
BLOCKED 阻塞:正在等待获取锁。
WAITING 等待:因为调用了wait()或join()方法而停止运行。因为调用wait()终止的必须由notify()或notifyAll()唤醒,
TIME_WAITING 限时等待:因为调用了sleep()或调用了指定等待时间的wait(long)和join(long)方法而停止运行。指定的时间过去后,会自动唤醒。
TERMINATED 终结:线程结束,可以是run()方法执行完毕或者是抛出异常结束。
通过线程对象调用getState()方法获取线程状态
线程的终止
终止线程的方法Thread.stop()已过时,可以自己设计结束线程的方法:
1、通过设置结束标记控制run()方法的运行和结束;
2、如果遇到线程处于WAIT状态而读不到结束标记,则需要Thread.interrupt()方法中断线程。
class Demo extends Thread {
boolean toRun = true; // 结束标记
public void run() {
synchronized(this) {
while (true == toRun) {
try {
wait(); // Thread.interrupt()方法会使wait()方法产生一个中断异常
} catch (InterruptedException ex) {
myStop(); // 使得我们有机会在异常处理器中设置结束标记
// return;
}
}
}
}
public void myStop() { // 终止线程的方法
toRun = false;
}
}
同步
多线程并发执行时候,执行次序以及执行的时间是不确定的。如果多个线程并发访问同一个数据,有可能会产生意想不到的结果。例如:一线程读取数组元素,而另一个线程正对数组进行排序。对于共享数据,必须要保证同一时间只能被一个线程访问。Java提供了访问共享数据的同步机制。
synchronized代码块原理:
同步代码块可以使用任意对象作为它的锁,当某个线程执行到代码块中的内容时,整个同步代码块将被锁上,其他想进入代码块的线程无法进入只能等待,只有最初进入代码块的线程执行离开,代码块的锁才会解开,其他等待中的线程才有机会执行代码块中的内容。
synchronized方法:
原理与同步代码块相同,只是将同步的范围扩大至整个方法体,作为锁的对象只能是方法内的this。若是静态方法,锁则是该方法所属的类的字节码对象。
同步的代价:
同步机制解决了共享数据的访问,但是增加了资源的消耗,降低了程序的效率。还有,使用同步一旦不小心,就有可能产生死锁的现象:
Object lock1 = new Object();
Object lock2 = new Object();
new Thread() {
public void run() {
while (true) {
synchronized (lock1) {
System.out.println("Thread1 is waiting");
synchronized (lock2) { // 执行到这里,线程1占用lock1,等待lock2
System.out.println("Thread1 come in");
}
}
}
}
}.start();
new Thread() {
public void run() {
while (true) {
synchronized (lock2) {
System.out.println("Thread2 is waiting");
synchronized (lock1) { // 执行到这里,线程2占用lock2,等待lock1
System.out.println("Thread2 come in");
}
}
}
}
}.start();
这个程序中,两个线程各自占用一个锁,而等待对方释放锁
线程间的通讯
一般情况下我们使用多线程是想让多个线程协同完成工作,这就要求在线程与线程之间能够传递消息完成配合。线程通讯会用到的一些方法:
Object.wait() // 可指定等待时间,若不指定,必须由notify()或notifyAll()唤醒
Object.notify() // 唤醒线程池中某一线程
Object.notifyAll() // 唤醒线程池中所有线程
Thread.join() // 执行此方法的线程进入等待状态,直到与此方法关联的线程终止才会被唤醒
static Thread.sleep(long) // 等待指定时间
static Thread.yield() // 执行这个方法的线程会释放执行权,俗称 雷锋方法
线程通讯的新方案
JDK5提供了java.util.concurrent.locks.Lock和java.util.concurrent.locks.Condition两个接口,“其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用”。在原理上,新方案与原有方案是一致的,不过在使用上新方案更清晰、更符合面向对象的要求:
1、明确地将原来潜伏在代码背后的锁封装成Lock对象;
2、原来上锁和解锁都是隐式动作,现在有lock()和unlock()两个方法与之对应;
3、将原来Object的3个等待唤醒方法单独封装成Condition对象,用await()替代了wait()、signal()替代了notify()、signalAll()替代了notifyAll()。