1.線程和進程的區別
- 進程是資源分配的最小單位,線程是CPU調度的最小單位;
- 線程不能看作獨立應用,而進程可以看作獨立應用;
- 進程有獨立的地址空間,相互不影響,線程只是進程執行的不同路徑;
- 線程沒有獨立的地址空間,多進程的程序比多線程的程序健壯;
- 進程的切換比線程的切換開銷大;
2.Java進程和線程的關係
- java對操作系統提供的功能進行封裝,包括進程和線程;
- 運行一個程序會產生一個進程,進程至少包含一個線程;
- 每個進程對應一個JVM實例,多個線程共享JVM裏面的堆;
- java採用單線程編程模型,程序會自動創建主線程;
- 主線程可以創建子線程,原則上要後於子線程完成執行;
3.Thread.start()和Thread.run()方法的區別
- 調用start()方法會創建一個新的子線程並啓動;
- run()方法只是Thread的一個普通方法的調用;
4.Thread和Runnable的區別
- Thread是實現了Runnable接口的類,使得run支持多線程;
- 因類的單一繼承原則,推薦多使用Runnable接口;
5.如何給run()方法傳參
- 構造函數傳參
- 成員變量傳參
- 回調函數傳參
6.如何實現處理線程的返回值
- 主線程等待法
public class ThreadReturn implements Runnable{
private String runResult;
@Override
public void run() {
try {
Thread.currentThread().sleep(5000);
}catch (Exception e){
e.printStackTrace();
}
runResult="我是子線程方法執行想要返回的結果值";
}
public String getRunResult() {
return runResult;
}
public static void main(String[] args) {
ThreadReturn threadReturn=new ThreadReturn();
Thread thread=new Thread(threadReturn);
thread.start();
//主線程等待子線程執行完
while (threadReturn.getRunResult()==null){
try {
Thread.currentThread().sleep(100);
}catch (Exception e){
e.printStackTrace();
}
}
System.out.println(threadReturn.getRunResult());
}
}
弊端: 無法準確地預估全部子線程執行完畢的時間。主線程sleep時間太久,主線程就需要空等;sleep時間太短,子線程又可能沒有全部執行完畢。
- 使用Thread類的join()堵塞當前線程以等待子線程執行完畢
public class ThreadReturn implements Runnable{
private String runResult;
@Override
public void run() {
try {
Thread.currentThread().sleep(5000);
}catch (Exception e){
e.printStackTrace();
}
runResult="我是子線程方法執行想要返回的結果值";
}
public String getRunResult() {
return runResult;
}
public static void main(String[] args) {
ThreadReturn threadReturn=new ThreadReturn();
Thread thread=new Thread(threadReturn);
thread.start();
//主線程等待子線程執行完
// while (threadReturn.getRunResult()==null){
// try {
// Thread.currentThread().sleep(100);
// }catch (Exception e){
// e.printStackTrace();
// }
// }
try {
thread.join();
}catch (Exception e){
e.printStackTrace();
}
System.out.println(threadReturn.getRunResult());
}
}
- 通過Callable接口實現:通過FutureTask或者線程池獲取
//通過FutureTask獲取線程返回值
public class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
String value="test result";
System.out.println("Ready to work");
Thread.currentThread().sleep(5000);
System.out.println("Task done");
return value;
}
public static void main(String[] args) {
FutureTask<String> futureTask=new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
if(!futureTask.isDone()){
System.out.println("Task has not finished,pleas wait");
}
try {
System.out.println("Task return:"+futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("All is done");
}
}
//線程池獲取返回值
public class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
String value="test result";
System.out.println("Ready to work");
Thread.currentThread().sleep(5000);
System.out.println("Task done");
return value;
}
public static void main(String[] args) {
// FutureTask<String> futureTask=new FutureTask<>(new MyCallable());
// new Thread(futureTask).start();
// if(!futureTask.isDone()){
// System.out.println("Task has not finished,pleas wait");
// }
// try {
// System.out.println("Task return:"+futureTask.get());
// } catch (InterruptedException e) {
// e.printStackTrace();
// } catch (ExecutionException e) {
// e.printStackTrace();
// }
// System.out.println("All is done");
ExecutorService executorService= Executors.newCachedThreadPool();
Future future=executorService.submit(new MyCallable());//往線程池中提交線程
if(!future.isDone()){
System.out.println("Task has not finished,pleas wait");
}
try {
System.out.println("Task return:"+future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}finally {
executorService.shutdown();//關閉線程池
}
System.out.println("All is done");
}
}
6.線程的6種狀態
- 初始(NEW):新創建了一個線程對象,但還沒有調用start()方法。
- 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱爲“運行”。
線程對象創建後,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在獲得CPU時間片後變爲運行中狀態(running)。 - 阻塞(BLOCKED):表示線程阻塞於鎖,等待獲取排它鎖。
- 等待(WAITING):不會分配CPU執行時間,進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)來喚醒。
沒有設置TIMEOUT參數的Object.wait()方法;
沒有設置TIMEOUT參數的Thread.join()方法;
LockSupport.park()方法; - 超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間後自行返回。
Thread.sleep()方法;
設置了TIMEOUT參數的Object.wait()方法;
設置了TIMEOUT參數的Thread.join()方法;
LockSupport.parkNanos()方法;
LockSupport.parkUntil()方法; - 終止(TERMINATED):表示該線程已經執行完畢。
7.Thread.sleep()和Object.wait()的區別
- Thread.sleep()是Thread類的方法,Object.wait是Object類中定義的方法。
- Thread.sleep()方法可以在任何地方使用,Object.wait()方法只能在synchronized方法或synchronized塊中使用。
本質區別: - Thread.sleep()只會讓出CPU,不會導致鎖行爲的改變。
- Object.wait()不僅讓出CPU,還會釋放已經佔用的同步資源鎖。
public class WaitSleepDemo {
public static void main(String[] args) {
final Object lock=new Object();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread A is waiting to get lock");
synchronized (lock){
System.out.println("thread A get lock");
try {
Thread.sleep(20);
System.out.println("thread A do wait method");
lock.wait(1000);
System.out.println("thread A is done");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread b is waiting to get lock");
synchronized (lock){
System.out.println("thread b get lock");
try {
System.out.println("thread b is sleeping 10000ms");
Thread.sleep(10000);
System.out.println("thread b is done");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
8.Object.notify()和Object.notifyAll()的區別
先談兩個概念:
- 鎖池EntryList
假設線程A已經擁有了某個對象(不是類)的鎖,而其它線程B、C想調用這個對象的synchronized方法或塊,由於B、C線程在進入對象的synchronized方法或塊之前必須獲得該對象鎖的擁有權,而恰巧該對象的鎖目前正被A所佔用,此時B、C線程就會被堵塞,進入一個地方去等待鎖的釋放,這個地方便是鎖池。 - 等待池WaitSet
假設線程A調用了某個對象的wait()方法,線程A就會釋放該對象的鎖,同時線程A就進入到了該對象的等待池中,進入到等待池中的線程不會去競爭該對象的鎖。
notify:會隨機從等待池中選取一個線程進入鎖池去競爭獲取鎖的機會。
notifyAll:會讓所有處於等待池的線程全部進入鎖池去競爭獲取鎖的機會。
public class NotificationDemo {
private volatile boolean go=false;
public static void main(String[] args) {
NotificationDemo test=new NotificationDemo();
Runnable waitTask=new Runnable() {
@Override
public void run() {
try {
test.shouldGo();
}catch (Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"finished execution");
}
};
Runnable notifyTask=new Runnable() {
@Override
public void run() {
test.go();
System.out.println(Thread.currentThread().getName()+"finished execution");
}
};
Thread t1=new Thread(waitTask,"WT1");
Thread t2=new Thread(waitTask,"WT2");
Thread t3=new Thread(waitTask,"WT3");
Thread t4=new Thread(notifyTask,"NT1");
//starting all waiting thread
t1.start();
t2.start();
t3.start();
//確保所有等待線程都能成功開始執行
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
t4.start();
}
private synchronized void shouldGo() throws InterruptedException {
while (go!=true){
System.out.println(Thread.currentThread()+"is going to wait on this object");
wait();//釋放鎖,線程進入等待池,等待喚醒
System.out.println(Thread.currentThread()+"is woken up");
}
go=false;
}
private synchronized void go(){
while (go==false){
System.out.println(Thread.currentThread()+" is going to notify all or one thread waiting on this object");
go=true;
//notify();
notifyAll();
}
}
}
9.Thread.yield()方法
讓當前正在運行的線程回到可運行狀態,以允許具有相同優先級的其他線程獲得運行的機會。因此,使用yield()的目的是讓具有相同優先級的線程之間能夠適當的輪換執行。但是,實際中無法保證yield()達到讓步的目的,因爲,讓步的線程可能被線程調度程序再次選中。yield()不會導致線程轉到等待、阻塞、終止等狀態。
10.synchronized
線程安全問題的主要誘因
- 存在共享數據(也稱臨界資源)
- 存在多條線程共同操作這些共享數據
解決問題的根本方法:
同一時刻有且只有一個線程在操作共享數據,其他線程必須等到該線程處理完數據後再對共享數據進行操作。
互斥鎖的特性:
- 互斥性:即在同一時間只允許一個線程持有某個對象鎖,通過這種特性來實現多線程的協調機制,這樣在同一時間只有一個線程對需要同步的代碼塊(複合操作)進行訪問。互斥性也稱爲操作的原子性。
- 可見性:必須確保在鎖被釋放之前,對共享變量所作的修改,對於隨後獲得該鎖的另一個線程是可見的(即在獲得鎖時應獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續操作,從而引起不一致。
synchronized 鎖的不是代碼,鎖的都是對象。
根據獲取的鎖的分類:獲取對象鎖和獲取類鎖
獲取對象鎖的兩種用法
- 同步代碼塊(synchronized(this)),synchronized(類實例對象),鎖是小括號()中的實例對象。
- 同步非靜態方法(synchronized method),鎖是當前對象的實例對象。
獲取類鎖的兩種用法
- 同步代碼塊(synchronized(類.class)),鎖是小括號()中的類對象(Class對象)。
- 同步靜態方法(synchronized static method),鎖是當前對象的類對象(Class對象)。
對象鎖和類鎖的總結
- 有線程訪問對象的同步代碼塊時,另外的線程可以訪問該對象的非同步代碼塊;
- 若鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊時,另外一個訪問對象的同步代碼塊的線程會被堵塞;
- 若鎖住的是同一個對象,一個線程在訪問對象的同步方法時,另外一個訪問對象同步方法的線程會被堵塞;
- 若鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊時,另一個訪問對象的同步方法的線程會被堵塞,反之亦然;
- 同一個 類的不同對象的對象鎖互不干擾;
- 類鎖由於也是一種特殊的對象鎖,因此表現和上述1、2、3、4一致,而由於一個類只有一把對象鎖,所以同一個類的不同對象使用類鎖將會是同步的;
- 類鎖和對象鎖互補干擾;
public class SynchRunnable implements Runnable{
@Override
public void run() {
String threadName=Thread.currentThread().getName();
if(threadName.startsWith("A")){
asyc();
}else if(threadName.startsWith("B")){
synchObjectBlock1();
}else if(threadName.startsWith("C")){
synchObjectBlock2();
}else if(threadName.startsWith("D")){
synchClassBlock1();
}else if(threadName.startsWith("E")){
synchClassBlock2();
}
}
public void asyc(){
System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
public void synchObjectBlock1(){
System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (this){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
public synchronized void synchObjectBlock2(){
System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
public void synchClassBlock1(){
System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (SynchRunnable.class){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
public static synchronized void synchClassBlock2(){
System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
public static void main(String[] args) {
SynchRunnable synchRunnable=new SynchRunnable();
Thread a1=new Thread(synchRunnable,"A1");
Thread a2=new Thread(synchRunnable,"A2");
Thread b1=new Thread(synchRunnable,"B1");
Thread b2=new Thread(synchRunnable,"B2");
Thread c1=new Thread(synchRunnable,"C1");
Thread c2=new Thread(synchRunnable,"C2");
Thread d1=new Thread(new SynchRunnable(),"D1");
Thread d2=new Thread(new SynchRunnable(),"D2");
Thread e1=new Thread(new SynchRunnable(),"E1");
Thread e2=new Thread(new SynchRunnable(),"E2");
a1.start();
a2.start();
b1.start();
b2.start();
c1.start();
c2.start();
d1.start();
d2.start();
e1.start();
e2.start();
}
}