概述
- 進程:是一個正在執行中的程序。每一個進程執行都有一個執行順序。該順序是每一個執行路徑,或者叫一個控制單元
- 線程:進程中的一個獨立的控制單元,線程在控制着進程的執行。一個進程中至少有一個線程。
創建線程
創建線程有兩種方法
1. 繼承Thread類
創建流程:
1.1、定義類繼承Thread
1.2、複寫Thread類中的run方法
1.3、調用線程的start方法(啓動線程,調用run方法)
start方法開啓線程並調用run方法,直接調用run方法沒有開啓線程(僅僅是調用對象方法)
2. 實現Runnable接口
創建流程:
2.1、定義類實現Runnable接口
2.2、覆蓋Runnable接口中的run方法
2.3、通過Thread類建立線程對象
2.4、將Runnable接口的子類對象作爲實際參數傳遞給Thread類的構造函數
2.5、調用Thread類的start方法開啓線程
注:其中stop();方法已過時
實現方式的好處:
實現:避免單繼承的侷限性。獨立資源。在定義線程時,建議使用實現方式
同步
- 多線程同步(synchronized)前提:
必須要有兩個或者兩個以上的線程
必須是多個線程使用同一個對象的鎖 - 同步的利弊
同步好處:解決了多線程的安全問題
同步弊端:多個線程需要判斷鎖,比較消耗資源 - 函數函數的鎖(this)
public class ThisLockDemo {
public static void main(String[] args) {
TicketDemo td = new TicketDemo();
Thread t1 = new Thread(td);
Thread t2 = new Thread(td);
Thread t3 = new Thread(td);
Thread t4 = new Thread(td);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketDemo implements Runnable{
private int tick = 100;
@Override
public void run() {
while (true) {
saleTick();//這裏是有省略this的
}
}
public synchronized void saleTick() {//可以推斷出這裏也是this鎖
if (tick > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 賣票啦 " + tick--);
}
}
}
驗證this鎖
public class ThisLockDemo {
public static void main(String[] args) {
TicketDemo td = new TicketDemo();
Thread t1 = new Thread(td);
Thread t2 = new Thread(td);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
td.flag = false;
t2.start();
}
}
class TicketDemo implements Runnable{
private int tick = 10;
public boolean flag = true;
Object obj = new Object();
@Override
public void run() {
if (flag) {
while (true) {
synchronized(obj) {
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " if賣票啦 " + tick--);
}
}
}
} else {
while (true)
saleTick();
}
}
public synchronized void saleTick() {//this
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " else賣票啦 " + tick--);
}
}
}
輸出結果爲:
Thread-0 if賣票啦 10
Thread-1 else賣票啦 9
Thread-0 if賣票啦 8
Thread-1 else賣票啦 7
Thread-0 if賣票啦 6
Thread-1 else賣票啦 5
Thread-0 if賣票啦 4
Thread-1 else賣票啦 3
Thread-0 if賣票啦 2
Thread-1 else賣票啦 1
Thread-0 if賣票啦 0
結果出現0號票,出現安全隱患。
分析:此多線程程序加了鎖還出現安全隱患,說明同步的兩個前提沒有滿足。
第一個條件是必須兩個或者兩個以上的線程,此條件滿足。
既然滿足了第一個條件,那麼肯定是第二個條件不滿足了(使用同一個鎖)。
來驗證下this鎖會不會出現安全問題:
public class ThisLockDemo {
public static void main(String[] args) {
TicketDemo td = new TicketDemo();
Thread t1 = new Thread(td);
Thread t2 = new Thread(td);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
td.flag = false;
t2.start();
}
}
class TicketDemo implements Runnable{
private int tick = 10;
public boolean flag = true;
//Object obj = new Object();
@Override
public void run() {
if (flag) {
while (true) {
synchronized(this) {
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " if賣票啦 " + tick--);
}
}
}
} else {
while (true)
saleTick();
}
}
public synchronized void saleTick() {//this
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " else賣票啦 " + tick--);
}
}
}
輸出結果:
Thread-0 if賣票啦 10
Thread-0 if賣票啦 9
Thread-0 if賣票啦 8
Thread-0 if賣票啦 7
Thread-0 if賣票啦 6
Thread-0 if賣票啦 5
Thread-0 if賣票啦 4
Thread-1 else賣票啦 3
Thread-1 else賣票啦 2
Thread-0 if賣票啦 1
數次運行,結果並沒有出現0號票,說明同步函數的鎖是this
4.靜態同步函數的鎖是class對象
靜態方法中是沒有this的,那麼鎖是誰呢?來驗證下
public class ThisLockDemo {
public static void main(String[] args) {
TicketDemo td = new TicketDemo();
Thread t1 = new Thread(td);
Thread t2 = new Thread(td);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
td.flag = false;
t2.start();
}
}
class TicketDemo implements Runnable{
private static int tick = 10;
public boolean flag = true;
@Override
public void run() {
if (flag) {
while (true) {
synchronized(this) {//這裏鎖是obj(new Object())輸出的結果同樣存在安全隱患
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " if賣票啦 " + tick--);
}
}
}
} else {
while (true)
saleTick();
}
}
public static synchronized void saleTick() {//this
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " else賣票啦 " + tick--);
}
}
}
輸出結果:
Thread-0 if賣票啦 10
Thread-1 else賣票啦 9
Thread-0 if賣票啦 8
Thread-1 else賣票啦 7
Thread-0 if賣票啦 6
Thread-1 else賣票啦 5
Thread-0 if賣票啦 4
Thread-1 else賣票啦 3
Thread-0 if賣票啦 2
Thread-1 else賣票啦 1
Thread-0 if賣票啦 0
很顯然,鎖不是this,靜態方法進內存的時候是沒有對象的,是由類直接調用的,類進內存會封裝成class類型的對象(即字節碼文件對象):類名.class 該對象的類型是Class
那麼試試:類名.class鎖實驗下
public class ThisLockDemo {
public static void main(String[] args) {
TicketDemo td = new TicketDemo();
Thread t1 = new Thread(td);
Thread t2 = new Thread(td);
t1.start();
try{Thread.sleep(10);}catch(InterruptedException e){}
td.flag = false;
t2.start();
}
}
class TicketDemo implements Runnable{
private static int tick = 10;
public boolean flag = true;
@Override
public void run() {
if (flag) {
while (true) {
synchronized(TicketDemo.class) {
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " if賣票啦 " + tick--);
}
}
}
} else {
while (true)
saleTick();
}
}
public static synchronized void saleTick() {//this
if (tick > 0) {
try {Thread.sleep(20);} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + " else賣票啦 " + tick--);
}
}
}
輸出結果:
Thread-0 if賣票啦 10
Thread-0 if賣票啦 9
Thread-0 if賣票啦 8
Thread-0 if賣票啦 7
Thread-0 if賣票啦 6
Thread-1 else賣票啦 5
Thread-1 else賣票啦 4
Thread-1 else賣票啦 3
Thread-1 else賣票啦 2
Thread-1 else賣票啦 1
由此可以推斷出靜態函數的鎖是類進內存會封裝成class類型的對象(即字節碼文件對象):類名.class
多線程下的延遲加載單例模式(雙重判斷鎖)
class Single {
private static Single s = null;
public static Single getInstance() {
if (s == null) {
synchronized (Single.class) {
if (s==null) {
s = new Single();
}
}
}
return s;
}
}
線程死鎖:同步中嵌套同步,但是鎖卻不同步,容易導致死鎖。線程死鎖時,第一個線程等待第二個線程釋放資源,而同時第二個線程又在等待第一個線程釋放資源。這裏舉一個通俗的例子:如在人行道上兩個人迎面相遇,爲了給對方讓道,兩人同時向一側邁出一步,雙方無法通過,又同時向另一側邁出一步,這樣還是無法通過。假設這種情況一直持續下去,這樣就會發生死鎖現象。 導致死鎖的根源在於不適當地運用“synchronized”關鍵詞來管理線程對特定對象的訪問。
來看個小例子
public class DeadLockDemo {
public static void main(String[] args) {
TicketDemo td = new TicketDemo();
Thread t = new Thread(td);
Thread t1 = new Thread(td);
t.start();
try{Thread.sleep(10);}catch(Exception e){}
td.flag = false;
t1.start();
}
}
class TicketDemo implements Runnable {
private int tick = 1000;
Object obj = new Object();
boolean flag = true;
@Override
public void run() {
if(flag) {
while (true) {
synchronized (obj) {//object鎖
saleTick();//this鎖
}
}
} else {
while (true){
saleTick();
}
}
}
public synchronized void saleTick() {//this鎖
synchronized (obj) {//object鎖
if (tick > 0) {
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName() + " 線程名稱 " + tick--);
}
}
}
}
輸出結果(每次運行結果不一樣,如果數值較小容易出現和諧狀態,可以將數值調大,即可產生死鎖)
再舉個簡單點的死鎖例子:
public class DeadLockDemo2 {
public static void main(String[] args) {
Thread t = new Thread(new DeadLock(true));
Thread t1 = new Thread(new DeadLock(false));
t.start();
t1.start();
}
}
class DeadLock implements Runnable {
private boolean flag;
public DeadLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
while (true) {//如果你電腦上運行時和諧情況比較多,
//就加個循環,只要數據夠大,在這種嵌套鎖(鎖對象不同)的情況下肯定會死鎖
synchronized (MyLock.locka) {
System.out.println("if locka");
synchronized (MyLock.lockb) {
System.out.println("if lockb");
}
}
}
} else {
while (true) {
synchronized (MyLock.lockb) {
System.out.println("else lockb");
synchronized (MyLock.locka) {
System.out.println("else locka");
}
}
}
}
}
}
class MyLock {
static Object locka = new Object();
static Object lockb = new Object();
}
我的輸出結果:
else lockb
if locka//到這裏就鎖住了。
線程間通訊
等待喚醒機制
主要方法:wait();notify();notifyAll();
都使用在同步中,因爲要對持有監視器(鎖)的線程操作,所以必須在同步中使用
這些方法在操作同步中線程時,都必須要標識他們所操作線程中的鎖對象。只有同一個鎖上的被等待線程,可以被同一個鎖上notify喚醒。不可以對不同鎖中的線程進行喚醒。也就是說等待和喚醒必須是同一個鎖。
示例:
/*
需求:多個線程操作同一個資源。
如,一個線程存名字和性別。另外一個線程獲取姓名和性別。
*/
public class InputOutput {
public static void main(String[] args) {
Person p = new Person();
Input i = new Input(p);
Output o = new Output(p);
Thread t = new Thread(i);
Thread t1 = new Thread(o);
t.start();
t1.start();
}
}
class Input implements Runnable {
private Person p;
public Input(Person p) {
this.p = p;
}
@Override
public void run() {
while (true) {
System.out.println(p.name + " ... " + p.sex);
}
}
}
class Output implements Runnable {
private Person p;
public Output(Person p) {
this.p = p;
}
@Override
public void run() {
boolean flag = true;
while (true) {
if (flag) {
p.name = "張三";
p.sex = "男";
flag = false;
} else {
p.name = "divid";
p.sex = "women";
flag = true;
}
}
}
}
class Person {
String name;//此爲演示代碼,工作中一般將屬性私有化,並提供get和set方法
String sex;
}
得到結果爲:
//部分結果
張三 ... 男
divid ... women
divid ... 男
張三 ... 男
divid ... women
張三 ... 男
張三 ... women
divid ... women
張三 ... women
可以看出,多線程之間通訊存在安全隱患。修改代碼(加鎖):
public class InputOutput {
public static void main(String[] args) {
Person p = new Person();
Input i = new Input(p);
Output o = new Output(p);
Thread t = new Thread(i);
Thread t1 = new Thread(o);
t.start();
t1.start();
}
}
class Input implements Runnable {
private Person p;
public Input(Person p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized(p) {//此處的鎖是用的p(因爲p是兩個線程操縱的共同數據,記住:爲保證同步,鎖的對象必須相同)
System.out.println(p.name + " ... " + p.sex);
}
}
}
}
class Output implements Runnable {
private Person p;
public Output(Person p) {
this.p = p;
}
@Override
public void run() {
boolean flag = true;
while (true) {
synchronized(p) {
if (flag) {
p.name = "張三";
p.sex = "男";
flag = false;
} else {
p.name = "divid";
p.sex = "women";
flag = true;
}
}
}
}
}
class Person {
String name;//此爲演示代碼,工作中一般將屬性私有化,並提供get和set方法
String sex;
}
結果:
張三 ... 男
張三 ... 男
張三 ... 男
張三 ... 男
張三 ... 男
張三 ... 男
張三 ... 男
張三 ... 男
張三 ... 男
divid ... women
divid ... women
divid ... women
divid ... women
divid ... women
divid ... women
divid ... women
得到的結果雖然真確了,但是卻不是我要的:存一個,打印一個。修改後
//優化後的代碼
public class InputOutput {
public static void main(String[] args) {
Person p = new Person();
new Thread(new Input(p)).start();//生產1
new Thread(new Input(p)).start();//生產2
new Thread(new Output(p)).start();//消費1
new Thread(new Output(p)).start();//消費2
}
}
class Input implements Runnable {
private Person p;
public Input(Person p) {
this.p = p;
}
@Override
public void run() {
while (true) {
p.out();
}
}
}
class Output implements Runnable {
private Person p;
public Output(Person p) {
this.p = p;
}
@Override
public void run() {
int x = 0;
while (true) {
if (x == 0) {
p.setPerson("張三","男");
} else {
p.setPerson("Divid","women");
}
x = (x + 1) % 2;
}
}
}
class Person {
private String name;
private String sex;
boolean flag = false;
//此爲演示代碼,一般將屬性私有化,並提供get和set方法
//使用this作爲鎖的對象
public synchronized void setPerson(String name,String sex) {
while(this.flag) {//加入兩個村的線程都wait了。notifyAll喚醒所有線程後就會再次判斷標記,如果是if則直接往下運行,會出現生產兩次,消費一次、生產一次消費兩次的情況
try {this.wait();} catch (InterruptedException e) {}
}
this.name = name;
this.sex = sex;
this.flag = true;
//喚醒所有線程(flag判斷有if變成while後)(因爲線程是先進先出,假設生產2生產一次後wait了,然後生產1判斷標記後wait了,再消費1消費一次後wait了,接着喚醒一次,消費2wait了。生產2就活了,2生產一次,改變標記,喚醒一次,生產1醒了,判斷標記後又wait,所有的線程都wait了。。。就掛了。所以必須notifyAll)
this.notifyAll();
}
public synchronized void out() {
while(!this.flag) {
try {this.wait();} catch (InterruptedException e) {}
}
System.out.println(this.name + " ... ... " + this.sex);
this.flag = false;
this.notifyAll();
}
}
運行結果正常。
//可以copy代碼自己運行下
張三 ... 男
divid ... women
張三 ... 男
divid ... women
張三 ... 男
divid ... women
解釋爲什麼要用notifyAll而不是notify
JDK1.5之後用lock(多個Condition對象,可以完美的控制鎖)
import java.util.concurrent.locks.*;
public class ProduceTest {
public static void main(String[] args) {
Resource r = new Resource();
Consumer c = new Consumer(r);
Producer p = new Producer(r);
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
Thread t3 = new Thread(p);
Thread t4 = new Thread(p);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Consumer implements Runnable {
private Resource r;
public Consumer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.out();
}
}
}
class Producer implements Runnable {
private Resource r;
public Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.setResource("zhangsan");
}
}
}
class Resource {
private String name;
private int count = 0;
boolean flag = false;
private final Lock lock = new ReentrantLock();
private final Condition p_cd = lock.newCondition();//多個Condition對象
private final Condition c_cd = lock.newCondition();//可以查看api:java.util.concurrent.locks 包裏面
public void setResource(String name) {
lock.lock();
try {
while (this.flag) {
//生產的鎖
try {p_cd.await();} catch (InterruptedException e) {}
}
this.name = name + "--" + count++;
System.out.println(Thread.currentThread().getName()+ " ....生產者.... " + this.name );
this.flag = true;
//喚醒消費線程
c_cd.signal();
} finally {//解鎖(必須做)
lock.unlock();
}
}
public void out() {
lock.lock();
try {
while (!this.flag) {
//消費的鎖
try {c_cd.await();} catch (InterruptedException e) {}
}
System.out.println(Thread.currentThread().getName()+ " ............消費者........... " + this.name );
this.flag = false;
//喚醒生產的線程
p_cd.signal();
} finally {//解鎖(必須做)
lock.unlock();
}
}
}
創建線程的三種方式
- 繼承Thread類
- 實現Runnable接口
- 使用ExecutorService、Callable、Future實現有返回結果的線程
Callable和FutureTask實現多線程
public class CallableTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<String> c = new CallableDemo();
FutureTask<String> ft = new FutureTask<String>(c);
FutureTask<String> ft1 = new FutureTask<String>(c);
Thread t1 = new Thread(ft);
Thread t2 = new Thread(ft1);
t1.start();
t2.start();
}
}
class CallableDemo implements Callable<String> {
private int count = 1;
@Override
public String call() throws Exception {
System.out.println("CallableDemo count++ = " + (count++));
return null;
}
}
ExecutorService、Callable、Future線程池實現(有返回值的線程)
參考:FelixZh
public class CallableTest {//包都是java.util.concurrent下的
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService es = Executors.newFixedThreadPool(5);
List<Future<String>> list = new ArrayList<Future<String>>();
for (int x=0; x<5; x++) {
Future<String> submit = es.submit(new CallableDemo());
list.add(submit);
}
es.shutdown();
for (Future<String> f : list) {
System.out.println(f.get().toString());
}
}
}
class CallableDemo implements Callable<String> {
private int count = 1;
@Override
public String call() throws Exception {
System.out.println("CallableDemo count++ = " + (count++));
return "CallableDemo count++ = " + (count++);
}
}
還有守護線程,停止線程,線程優先級,yield和join方法等等。