第九章 线程
9.1 定义、实例化并启动线程
考试目标4.1 使用java.lang.Thread和java.lang.Runnable编写代码,定义、实例化并启动新线程。
9.1.1 定义线程
扩展java.lang.Thread类,重写run()方法。
class MyThread extends Thread{
public void run(){
System.out.println("Important job running in MyThread");
}
}
实现java.lang.Runnable,实现run()方法。
class MyRunnable implements Runnable{
public void run(){
System.out.println("Important job running in MyRunnable");
}
}
9.1.2 实例化线程
每个执行线程都是作为Thread类的一个实例开始的。
对于扩展Thread类而定义的线程实例化方式如下:
MyThread t = new MyThread();
对于实现Runnable接口而定义的线程实例化方式如下:
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
在调用start()方法后,发生:
启动新的执行线程(具有新的调用栈)。
线程从新状态转到可运行状态(runnable state)。
当线程获得执行机会时,会运行它的目标run()方法。
Thread.currentThread()用来获取当前线程引用。
Thread.getName()用来获得线程的名字。
启动并运行多个线程:
每个线程都会启动,而且每个线程都将运行到结束。但是顺序,优先没有绝对保证。
当线程的目标run()方法结束时,该线程就完成了。死线程不能再次调用start()方法。
只要线程已经启动过,它就永远不能再次启动。
线程调度器:
可运行线程编程运行中线程的顺序是没有保证的。
java.lang.Thread类中控制(影响)线程的方法:
public static void sleep(long millis) throws InterruptedException public static void yield()
public final void join() throws InterruptedException
public final void setPriority(int newPriority)
//默认是5,值越大,优先越高 1-10
java.lang.Object类中控制(影响)线程的方法:
public final void wait() throws InterruptedException
public final void notify() //唤醒单个线程
public final void notifyAll() //唤醒所有线程
9.2 线程状态与转变
考试目标4.2 识别线程能够位于哪些状态,并确定线程能从一种状态转变成另一种状态的方式。
9.2.1 线程状态
新状态--new
可运行状态--runable
运行中状态--running
等待/阻塞/睡眠状态--waiting/blocked/sleeping
死状态--dead
9.2.2 阻止线程执行
一个线程被踢出“运行中”状态,而不是被送回“可运行”或“死”状态。
即:睡眠,等待,因为需要对象的锁而被阻塞。
9.2.3 睡眠
Thread的两个静态方法:
public static void sleep(long millis) throws InterruptedException //millis是毫秒
public static void sleep(long millis,int nanos) throws InterruptedException //nanos是纳秒
9.2.4 线程优先级和yield()
调度器在优先级上是没有保证的,主要是看它“喜欢。。还是不喜欢”,真够无奈的。
设置线程的优先级
t.setPriority(int i);
yield()方法
Thread的静态方法yield(),让当前的“运行中”线程回到“可运行”状态,让步给具有相同优先级的其他“可运行”线程,但这是没有任何保证的。
join()方法
Thread的非静态方法join()让当前线程加入到引用的线程尾部,这意味着调用方法的线程完成(死状态)之前,主线程不会变为可运行的。
public final void join() //要一直等待该线程结束 throws InterruptedException
public final void join(long millis) //millis为0表示要一直等下去 throws InterruptedException
public final void join(long millis, int nanos) throws InterruptedException
9.3 同步代码
考试目标4.3 给定一个场景,编写代码,恰当地使用对象锁定来保护静态变量或实例变量,使它们不出险并发访问问题。
9.3.1 同步和锁 synchronized
-
只能同步方法(或代码块),不能同步变量或类
-
每个对象只有一个锁。换句话说同一时间只有一个线程可以使用该对象的同步方法或代码块。
-
不必同步类中的全部方法。类可以同时具有同步方法和非同步方法。
-
一旦一个线程获得了对象的锁,就没有任何其他线程可以进入(该对象的)类中的任务同步方法。
-
如果类同事具有同步和非同步方法,则多个线程仍然能够访问该类的非同步方法。
-
如果线程进入睡眠,则它会保持已有的任何锁,不会释放他们。
-
线程可以获得多个锁,即线程可以同时获得多个对象的锁。
静态方法能否同步
可以,静态代码块也可以。例如:
public static synchronized int getCount(){
return count;
}
public static int getCount(){
//注意synchronized关键字后面括号里的内容:即要锁的类或对象
synchronized(MyClass.class){
return count;
}
}
如果线程不能获得锁会怎么样?
会阻塞,等锁释放。
何时需要同步?
书云:对于复杂的情况“如果你不想它的话,你的生命就会更长、更愉快。真的如此,我们没有撒谎。”
线程安全类
StringBuffer等
9.3.2 线程死锁
-
当代码等待从对象删除锁而迫使线程执行暂停时,死锁就发生了。
-
当被锁的对象试图访问另一个被锁的对象,而该对象又要访问第一个被锁的对象时,就会发生死锁现象。换句话说,两个线程都在等待对方释放锁,因此,这些锁永远不会释放!
-
死锁是一种糟糕的设计,不应当让它发生。
9.4 线程交互
考试目标4.4 给定一个场景,编写代码,恰当地使用wait()、notify()和notifyAll()方法。
必须在同步方法内调用wait()、notify()和notifyAll()方法!线程不能调用对象上的等待或通知方法,除非它拥有该对象的锁。
9.4.1 当多个线程等待时使用notifyAll()
在循环中使用wait()