1. 程式人生 > >多線程的實現及其安全問題

多線程的實現及其安全問題

多線程 runnable thread synchronized lock timer

一、進程和線程概述

1、進程:進程是一個具有獨立功能的程序關於某個數據集合的一次運行活動,簡單來說開啟一個程序就開啟了一個進程;

如果開啟多個進程,它們之間是由於CPU的時間片在相互的切換;

2、線程:開啟一個進程的一個任務,對於多線程:每一個線程都在爭奪CPU的執行權(CPU的執行權具有隨機性);

如果一個程序的執行路徑有多條,那麽該線程是多線程;反之,就單線程線程;線程是依賴於進程存在的!

3、Jvm是多線程 —— 至少開啟了兩條線程

main方法 主線程

gc() 垃圾回收線程

二、多線程的實現方式

1、繼承Thread類

1)自定義一個類,該類繼承Thread類

2)重寫的run()方法

public class MyThread extends Thread{

public void run(){

耗時操作;

}

}

3)在主線程中創建該類對象

MyThread mt = new MyThread(); //創建幾個對象,就有幾個線程,去執行run()方法中的耗時操作

4)start()方法,啟動線程

mt.start();

2、實現Runnable接口

1)自定義一個類,該類實現Runnable接口 //數據共享

2)重寫的run()方法

public class MyRunnable implements Runnable{

public void run(){

耗時操作

}

}

3)在主線程中創建該類的對象

MyRunnable mr = new MyRunnable();

4)創建Thread類對象,將第三步的對象作為參數進行傳遞

Thread t1 = new Thread(mr);

Thread t2 = new Thread(mr,"名稱"); //可以設置線程名稱

5)start()方法,啟動線程

t1.start();

t2.start();

3、使用線程池(主要用於計算)

1)自定義一個類,實現Callable接口

public class MyCallable implements Callable{}

2)實現裏面的call方法

相當於run()方法,裏面放一些耗時操作;

3)主線程中創建線程對象

ExecutorService threadPool = Executors.newFixedThreadPool(2) ; 參數表示線程的個數

4)用線程池對象提交任務

threadPool.submit(new MyCallable());

threadPool.submit(new MyCallable());

5)提交後結束線程池

threadPool.shutdown();

三、Thread的常用方法(Thread實現Runnable接口)

public static Thread currentThread()返回對當前正在執行的線程對象的引用

public final String getName()返回該線程的名稱

public final void setName(String name)改變線程名稱

public final void setDaemon(boolean on)當參數為true時,設置該線程為守護線程,當正在運行的線程都是守護線程時Java虛擬機退出

//該方法必須在啟動線程前調用

public final void join()throws InterruptedException等待該線程終止 //等待該線程執行完畢,其他線程才能執行

public static void sleep(long millis)在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行)

public final void stop()強制停止執行 //已過時

public void interrupt()中斷線程 //中斷當前線程的這種狀態(打破了一種狀態)

public static void yield()暫停當前正在執行的線程對象,並執行其他線程

//線程的執行具有隨機性,下次有可能又是它搶占到了CPU執行權,還是它接著執行

四、多線程的安全問題

egg:共有100張票,而它有3個售票窗口售票,請設計一個程序模擬該電影院售票

public class SellTicket implements Runnable{

private int ticket = 100;

@Override

public void run() {

while(true){

if(ticket > 0){

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + ":" + ticket);

ticket --;

}else{

break;

}

}

}

}

public class SellTicketDemo {

public static void main(String[] args) {

SellTicket st = new SellTicket();

Thread t1 = new Thread(st,"窗口1");

Thread t2 = new Thread(st,"窗口2");

Thread t3 = new Thread(st,"窗口3");

t1.start();

t2.start();

t3.start();

}

}

1、問題:

1)出現了同一張票被賣了多次

由於CPU的執行具有原子性(最簡單的操作)操作

2)出現了0票和負票

由於cpu的執行權具有隨機性和延遲性導致

2、線程安全的檢測條件

1)看我們當前的環境是否屬於多線程程序

2)當前的多線程程序中是否有共享數據

3)是否多條語句對共性數據進行操作

3、多線程的同步機制

為了解決上述安全問題java提供了一個同步機制 —— 關鍵詞sychronized

1)同步代碼塊的使用:

sychronized(任意對象){

多條語句對共享數據進行操作的代碼;

}

2)註意事項:

A:所有線程只能使用同一個對象(將任意對象當作一個鎖:使用的是同一把鎖),所以不能使用匿名對象當做參數

B:註意同步代碼塊所包的代碼不能將循環包進去,否則就不是多線程了

3)同步方法:

public static synchronized void 方法名(){

多條語句對共享數據進行操作的代碼;

} //同步方法使用鎖對象是this

靜態的同步方法的鎖對象是:類名.class(java中的反射機制)

4)Lock接口:

實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作

創建具體的鎖對象

private Lock lock = new ReentrantLock(); //Lock接口不能實例化,提供了子實現類:ReentrantLock

public void lock()獲取鎖

多條語句對共享數據進行操作的代碼;

public void unlock():釋放鎖

使用:一般使用try-finally將整個內容包起來,在finally中釋放鎖

4、修改後代碼:

public class SellTicket implements Runnable{

private static int ticket = 100;

@Override

public void run() {

while(true){

sellTicket();

if(ticket <= 0){

break;

}

}

}

public static synchronized void sellTicket(){

if(ticket > 0){

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + ":" + ticket);

ticket --;

}

}

}

public class SellTicketDemo {

public static void main(String[] args) {

SellTicket st = new SellTicket();

Thread t1 = new Thread(st,"窗口1");

Thread t2 = new Thread(st,"窗口2");

Thread t3 = new Thread(st,"窗口3");

t1.start();

t2.start();

t3.start();

}

}

5、和線程安全相關的類

StringBuffer sb = new StringBuffer();

Vector<T> v = new Vector<T>() ;

Hashtable<K, V> hs = new Hashtable<K,V>() ;

public static <T> List<T> synchronizedList(List<T> list)返回指定列表支持的同步(線程安全的)列表

List<T> list = Collections.synchronizedList(new ArrayList<T>());

五、死鎖的問題

1、線程安全的弊端:

1)執行效率低

2)容易產生死鎖

兩個或兩個以上的線程,搶占CPU的執行權,然後出現了互相等待的情況;

2、生產者消費者模式

分別進行產生數據和消費數據兩條線程,針對同一資源進行操作

egg:

public class StudentDemo {

public static void main(String[] args) {

Student s = new Student();

SetThread st = new SetThread(s);

GetThread gt = new GetThread(s);

Thread t1 = new Thread(st);

Thread t2 = new Thread(gt);

t1.start();

t2.start();

}

}

public class Student {

private String name;

private int age;

public Student() {

super();

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

}

public class SetThread implements Runnable{

private Student s;

public SetThread(Student s) {

super();

this.s = s;

}

@Override

public void run() {

int i = 0;

while(true){

if(i % 2 == 0){

s.setName("張三");

s.setAge(30);

}else{

s.setName("李四");

s.setAge(40);

}

i ++;

}

}

}

/*

* 理想狀態:

* 張三=30

* 李四=40

* 張三=30

* 李四=40

* 循環...

*/

public class GetThread implements Runnable{

private Student s;

public GetThread(Student s) {

this.s = s;

}

@Override

public void run() {

while(true){

System.out.println(s.getName() + "=" + s.getAge());

}

}

}

3、問題:

1)出現了 張三=40 線程隨機性導致

解決方法:給代碼加上同步代碼塊

2)並沒有和理想狀態一樣,張三、李四依次出現,而是同一個數據被打印了多次

解決方法:等待喚醒機制

註意:同步鎖,不同的線程之間使用的是同一把鎖對象!

Object類中有關多線程的方法:

public final void wait()throws InterruptedException當前線程等待

public final void notify()喚醒在此對象監視器上等待的單個線程

public final void notifyAll()喚醒在此對象監視器上等待的所有線程

//這些方法一般是使用的鎖對象進行調用,鎖對象可以是任意對象,所以這幾個有關多線程的方法在Object類中

4、修改後代碼:

public class StudentDemo {

public static void main(String[] args) {

Student s = new Student();

SetThread st = new SetThread(s);

GetThread gt = new GetThread(s);

new Thread(st).start();

new Thread(gt).start();

}

}

public class Student {

private String name;

private int age;

private boolean falg;

public Student() {

super();

}

public synchronized void set(String name,int age){

if(falg == true){

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

this.name = name;

this.age = age;

falg = true;

this.notify();

}

public synchronized void get(){

if(falg == false){

try {

this.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

System.out.println(this.name + "=" + this.age);

falg = false;

this.notify();

}

}

public class SetThread implements Runnable{

private Student s;


public SetThread(Student s) {

super();

this.s = s;

}

@Override

public void run() {

int i = 0;

while(true){

if(i % 2 == 0){

s.set("張三", 30);

}else{

s.set("李四", 40);

}

i++;

}

}

}

public class GetThread implements Runnable{

private Student s;

public GetThread(Student s) {

super();

this.s = s;

}

@Override

public void run() {

while(true){

s.get();

}

};

}

六、定時機制

1、定時器:可以進行任務的重復操作

可安排任務定時執行一次,或者定期重復執行

2、Timer類

1)Timer的使用

構造方法:public Timer()創建一個新計時器

方法: public void schedule(TimerTask task, Date time)安排在指定的時間執行指定的任務

參數1:task - 所要安排的任務

參數2:time - 執行該任務的時間毫秒值

public void schedule(TimerTask task, Date firstTime,long period)每隔多少毫秒進行重復性的任務操作

public boolean cancel()取消此計時器任務

2)TimerTask類

需要自定義類去繼承,並重寫其run()方法;作為所要安排的任務

3)執行一個定時器,3秒之後爆炸,並且每隔2秒繼續執行

public class TimerDemo{

public static void main(String[] args){

Timer t = new Timer();

t.schedule(new MyTask(), 3000, 2000);

}

}

class MyTask extends TimerTask{

@Override

public void run() {

System.out.println("bom,爆炸了");

}

}


多線程的實現及其安全問題