1.進程和線程
- 進程(Process)是計算機中的程序關于某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統的基礎。
- 面向進程的程序設計中,進程是程序的基本執行實體。
- 面向線程的程序設計中,進程是線程的容器。
- 進程是程序的實體,而程序是指令,數據,以及其組織形式的描述。
2.Java中的線程操作
2.1新建線程
2.1.2Thread線程類
- 關鍵字new創建一個線程對象,然后調用對象的start()方法:
Thread t1 = new Thread();
t1.start();
線程對象Thread有一個run()方法,start()方法會新建一個線程并讓這個線程執行run()方法。
直接使用run()方法,會在當前線程中串行調用run()方法。
2.1.3Runnable接口
也可以用Runnable接口新建線程,它只有一個run()方法,而且默認的Thread.run()就是調用內部的Runnable接口,因此使用Runnable更合理。
public interface Runnable {
public abstract void run();
}
Thread類有一個構造方法:
public Thread(Runnable target)
默認的Thread.start()方法調用的時候,新的線程就會執行Runnable.run()方法:
public void run(){
if(target != null){
target.run();
}
}
以下代碼實現Runnable接口,并將該實例傳入Thread,避免重載Thread.run():
public class CreateThread implements Runnable{
public static void main(String[] args){
Thread t1 = new Thread(new CreateThread());
t1.start();
}
@Override
public void run(){
System.out.println("Here is a Runnable!");
}
}
2.2 終止線程
一般來說,線程在執行完成之后就會結束。但是也可以手動關閉線程。
2.2.1Thread.stop()
- Thread.stop()方法可以結束線程,但是是直接終止線程,并立即釋放這個線程所持有的鎖。
- 該方法會導致數據不一致的問題,因此已經被標注為廢棄,不要使用。
例如,以下代碼沒有錯誤信息,但是結果不一致了:
public class StopThreadUnsafe {
public static User u = new User();
public static class User {
int id;
String name;
public User() {
id = 0;
name = "0";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User [id = " + id + ", name = " + name + "]";
}
}
public static class ChangeObjectThread extends Thread {
@Override
public void run() {
while (true) {
synchronized (u) {
int v = (int) (System.currentTimeMillis() / 1000);
u.setId(v);
// do something else
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
u.setName(String.valueOf(v));
}
Thread.yield();
}
}
}
public static class ReadObjectThread extends Thread {
@Override
public void run() {
while (true) {
synchronized (u) {
if (u.getId() != Integer.parseInt(u.getName())) {
System.out.println(u.toString());
}
}
Thread.yield();
}
}
}
public static void main(String[] args) throws InterruptedException {
new ReadObjectThread().start();
while (true) {
ChangeObjectThread t = new ChangeObjectThread();
t.start();
Thread.sleep(150);
t.stop();
}
}
}
如果要安全的停止一個線程,可以自己決定停止線程的時機,例如將前例中的CreateObjectThread線程中增加一個stopMe()方法:
public static class ChangeObjectThread extends Thread {
volatile boolean stopme = false;
public void stopMe() {
stopme = true;
}
@Override
public void run() {
while (true) {
if(stopme) {
System.out.println("exit by stop me");
break;
}
synchronized (u) {
int v = (int) (System.currentTimeMillis() / 1000);
u.setId(v);
// do something else
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
u.setName(String.valueOf(v));
}
Thread.yield();
}
}
}
2.3 中斷線程
- 中斷線程并不會使線程立即退出,而是給線程發送通知,線程接到通知后的操作由線程執行決定。
- JVM中于線程中斷有關的方法如下:
- Thread.interrupt(),是一個實例方法,通知線程中斷,即設置中斷標志位。中斷標志位表示當前線程已經被中斷了。
- Thread.isInterrupted(),也是一個實例方法,判斷當前線程是否有中斷(通過判斷中斷標志位)
- Thread.interrupted(),是一個靜態方法,用來判斷當前線程的中斷狀態,同時清除當前線程的中斷狀態。
- 方法簽名為:
public void Thread.interrupt() //中斷線程
public boolean Thread.isInterrupted() //判斷線程是否中斷
public static boolean Thread.interrupted() //判斷是否中斷,并清除當前中斷狀態
注意:中斷后需要增加中斷處理代碼,不然中斷不會發生作用。
Thread.sleep()函數
在循環體中,出現了sleep()或者wait()等操作,需要通過中斷來識別。wait()在下一小節介紹,這里介紹sleep()方法。
Thread.sleep()函數的作用是讓當前線程休眠若干時間,其函數簽名為:
public static native void sleep(long millis) throws InterruptedException
它會拋出一個InterruptedException異常,不是運行時異常,程序必須捕獲并處理他。當線程在sleep()休眠中被中斷,這個異常就會產生。
注意:Thread.sleep()方法因為中斷拋出異常時,會清除中斷標記,如果不加處理,在下一次循環開始時,就無法捕捉這個中斷,所以在異常處理中需要再次設置中斷標記位。
2.4 等待(wait)和通知(notify)
- 等待(wait)方法和通知(notify)方法是為了支持多線程的協作而存在的。
- 這兩個方法是在Object類中,即任何對象都能調用這兩個方法。
- Object.wait()方法不能隨便調用,需要包含在對應的synchronzied語句中。
- wait()和notify()方法都需要首先獲得目標對象的一個監視器。
- Object.wait()和Thread.sleep()方法都能讓線程等待若干時間,區別為:
- wait()方法可以被喚醒,sleep()方法需要等待時間結束
- wait()方法會釋放目標對象的鎖,而sleep()方法不會釋放任何資源
- 其方法簽名為:
public final void wait() throws InterruptedException
public final native void notify()
線程調用object.wait()方法,它會進入到object的等待隊列。當object.notify()方法被調用時,對象會在線程隊列中,隨機選擇一個線程,將其喚醒。
注意:這個選擇是非公平的,完全隨機。
使用例子:
package temp;
public class SimpleWN {
final static Object object = new Object();
public static class T1 extends Thread{
public void run() {
//獲得object對象鎖
synchronized(object) {
System.out.println(System.currentTimeMillis() + ": T1 Start!");
try {
System.out.println(System.currentTimeMillis() + ": T1 is wait for obejct!");
// 釋放object的對象鎖
object.wait();
}
catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + ": T1 is end");
}
}
}
public static class T2 extends Thread {
public void run() {
synchronized(object) {
System.out.println(System.currentTimeMillis() + ": T2 Start!");
System.out.println("notify one thread!");
// 釋放object對象鎖
object.notify();
System.out.println(System.currentTimeMillis() + ": T2 end!");
try {
// 休眠結束之后,才釋放對象鎖,T1才能繼續執行
Thread.sleep(2000);
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread t1 = new T1();
Thread t2 = new T2();
t1.start();
t2.start();
}
}
2.5 掛起(suspend)和繼續執行(resume)線程
- 被掛起(suspend)的線程,需要等到resume()操作后才能繼續執行
- 這兩個方法已經標注為廢棄,不建議使用
- 廢棄的原因是suspend()方法在導致線程暫停的同時,并不會釋放任何鎖資源,其他任何要范圍被它暫時使用的鎖,都無法正常運行。而被掛起的線程狀態仍然是Runnable,影響對系統狀態的判斷。
以下例子會導致系統鎖死:
public class BadSuspend {
public static Object u = new Object();
static ChangeObjectThread t1 = new ChangeObjectThread("t1");
static ChangeObjectThread t2 = new ChangeObjectThread("t2");
public static class ChangeObjectThread extends Thread{
public ChangeObjectThread(String name) {
super.setName(name);
}
@Override
public void run() {
synchronized(u) {
System.out.println("in " + getName());
Thread.currentThread().suspend();
}
}
}
public static void main(String[] args) throws InterruptedException{
// t1
t1.start();
Thread.sleep(100);
t2.start();
t1.resume();
// 此時t2的狀態依然是RUNNABLE
t2.resume();
t1.join();
t2.join();
}
}
為了達到相同的目的,可以用如下方法(使用wait()和notify()):
public class GoodSuspend {
public static Object u = new Object();
public static class ChangeObjectThread extends Thread{
// 標記變量,表明線程是否被掛起
volatile boolean suspendme = false;
public void suspendMe() {
suspendme = true;
}
public void resumeMe() {
suspendme = false;
synchronized (this){
notify();
}
}
@Override
public void run() {
while(true) {
synchronized(this) {
// 檢查線程是否被掛起
while(suspendme) {
try{
wait();
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized(u) {
System.out.println("in ChangeObjectThread");
}
Thread.yield();
}
}
}
public static class ReadObjectThread extends Thread{
@Override
public void run() {
while(true) {
synchronized(u) {
System.out.println("in ReadObjectThread");
}
Thread.yield();
}
}
}
public static void main(String[] args) throws InterruptedException{
// 實例化線程對象
ChangeObjectThread t1 = new ChangeObjectThread();
ReadObjectThread t2 = new ReadObjectThread();
// 運行線程對象
t1.start();
t2.start();
Thread.sleep(1000);
// 掛起t1線程
t1.suspendMe();
System.out.println("suspend t1 2 sec");
Thread.sleep(2000);
System.out.println("resume t1");
t1.resumeMe();
}
}
2.6 等待線程結束(join)和謙讓(yield)
- join簽名如下,有兩種:
// 阻塞當前線程,直到目標線程執行完畢
public final void join() throws InterruptedException
// 最多等待millies毫秒,之后繼續執行
public final synchronised void join(long millis) throws InterruptedException
- join 的本質是調用線程wait()方法在當前線程對象實例上,它使得調用線程在當前線程對象上等待,被等待的線程會在調用結束前調用notifyAll()通知所有等待線程繼續執行。JDK中join()實現的核心代碼為:
while(isAlive()){
wati(0);
}
基礎join()例子:
package temp;
public class JoinMain {
public volatile static int i = 0;
public static class AddThread extends Thread {
@Override
public void run() {
for (; i < 1000000; i++)
;
}
}
public static void main(String[] args) throws InterruptedException {
AddThread at = new AddThread();
System.out.println(i);
at.start();
System.out.println(i);
at.join();
// join()保證最后輸出的是1000000
System.out.println(i);
}
}
- yield()方法會使當前線程讓出CPU,重新進行資源分配。