線程的理解
1、同一個應用中,多個任務同時進行。就像editplus編輯工具,打開一個文件窗口就是一個線程。
2、線程可以有多個,但cpu每時每刻只做一件事(多核除外)。由於cpu處理速度很快,我們就感覺是同時進行的。所以宏觀上,線程是併發進行的;從微觀角度看,線程是異步執行的。
3、使用線程的目的是最大限度的利用cpu資源。想想當你在editplus中按下"ctrl + shift +S"保存全部文件的時候,如果要保存的文件比較多,沒有多線程的話,前面的文件沒有完成操作的話,後面的操作是執行不了的~!
創建線程
實現多線程,有兩種手段:一:繼承Thread類(啓動線程方法:new MyThread().start();)
步驟:
1,定義類繼承Thread。
2,複寫Thread類中的run方法。
目的:將自定義代碼存儲在run方法。讓線程運行。
3,調用線程的start方法,
該方法兩個作用:啓動線程,調用run方法。
線程中,start和run的區別
start():是開啓線程並調用run方法
run():存放的是線程執行的代碼,如果單純的調用run方法,只是普通的創建對象調用方法,而並沒有開啓線程
class 類名 extends Thread{
@Override
public void run() {
//code
}
}
class Show extends Thread
{
public void run()
{
for (int i =0;i<5 ;i++ )
{
System.out.println(name +"_" + i);
}
}
public Show(){}
public Show(String name)
{
this.name = name;
}
private String name;
public static void main(String[] args)
{
new Show("csdn").run();
new Show("黑馬").run();
}
}
【運行結果】:
發現這些都是順序執行的,說明調用run()方法不對,應該調用的是start()方法。
把上面的主函數修改爲如下:
public static void main(String[] args)
{
new Show("csdn").start();
new Show("黑馬").start();
}
在命令行執行:javac Show.java java Show,輸出的可能的結果如下:
因爲需要用到CPU的資源,多個線程都獲取cpu的執行權,cpu執行到誰,誰就運行。所以每次的運行結果基本是都不一樣的;
這也是多線程的一個特性:隨機性。誰搶到誰執行,至於執行多長,cpu說了算。
那麼:爲什麼我們不能直接調用run()方法呢?
我的理解是:線程的運行需要本地操作系統的支持。
如果你查看start的源代碼的時候,會發現:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
在start0()方法中jvm調用run()方法,這個這個方法用了native關鍵字,native表示調用本地操作系統的函數,多線程的實現需要本地操作系統的支持。二.實現Runable接口,(啓動線程方法:new Thread(new MyRunnable()).start();)
class 類名 implements Runnable {
@Override
public void run() {
//code
}
}
class Show implements Runnable
{
public Show(){}
public Show(String name)
{
this.name = name;
}
@Override
public void run()
{
for (int i =0;i<5 ;i++ )
{
System.out.println(name +"_" + i);
}
}
private String name;
public static void main(String[] args)
{
new Thread(new Show("csdn")).start();
new Thread(new Show("黑馬")).start();
}
}
【可能的運行結果】:
關於選擇繼承Thread還是實現Runnable接口?
其實Thread也是實現Runnable接口的:
public class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
其實Thread中的run方法調用的是Runnable接口的run方法。不知道大家發現沒有,Thread和Runnable都實現了run方法,這種操作模式其實就是代理模式。Thread和Runnable的區別:
如果一個類繼承Thread,則不能資源共享(有可能是操作的實體不是唯一的);但是如果實現了Runable接口的話,則可以實現資源共享。
class Show extends Thread
{
@Override
public void run()
{
for (int i = 0; i < 5 ; i++ )
{
if (count > 0)
{
System.out.println(Thread.currentThread().getName()+": count=" + count--);
}
}
}
private int count = 5;
public static void main(String[] args)
{
new Show().start();//new 出來多個實體
new Show().start();
}
}
【運行結果】:public static void main(String[] args)
{
Show s = new Show();
s.start();
s.start();
}
我們再以實現Runnable接口的方式修改上面的程序:
class Show implements Runnable
{
private int count = 10;//假設有10張票
@Override
public void run()
{
for (int i = 0; i < 5 ; i++ )
{
if (this.count > 0)
{
System.out.println(Thread.currentThread().getName()+"正在賣票" + this.count--);
}
}
}
public static void main(String[] args)
{
Show s = new Show(); //注意必須保證只對1個實體s操作
new Thread(s,"窗口1").start();
new Thread(s,"窗口2").start();
new Thread(s,"窗口3").start();
}
}
【運行結果】:
總結:
實現Runnable接口比繼承Thread類所具有的優勢:
1):適合多個相同的程序代碼的線程去處理同一個資源
2):可以避免java中的單繼承的限制
3):增加程序的健壯性,代碼可以被多個線程共享,代碼和數據獨立。
所以,還是以實現接口的方式來創建好些。在java中所有的線程都是同時啓動的,至於什麼時候,哪個先執行,完全看誰先得到CPU的資源;每次程序運行至少啓動2個線程。一個是main線程,一個是垃圾收集線程。因爲每當使用java命令執行一個類的時候,實際上都會啓動一個JVM,每一個jVM實際上就是在操作系統中啓動了一個進程。
設置線程優先級(主線程的優先級是5,不要誤以爲優先級越高就先執行。誰先執行還是取決於誰先去的CPU的資源)
<span style="font-size:14px;">Thread t = new Thread(myRunnable);
t.setPriority(Thread.MAX_PRIORITY);//一共10個等級,Thread.MAX_PRIORITY表示最高級10
t.start();</span>
判斷線程是否啓動class Show implements Runnable
{
public void run()
{
for (int i = 0; i < 5 ; i++ )
{
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args)
{
Show s = new Show();
Thread t = new Thread(s);
System.out.println("線程啓動前:" + t.isAlive());
t.start();
System.out.println("線程啓動後:" + t.isAlive());
}
}
【運行結果】:
主線程也有可能在子線程結束之前結束。並且子線程不受影響,不會因爲主線程的結束而結束。
join,sleep,yield的用法與區別
join方法:假如你在A線程中調用了B線程的join方法B.join();,這時B線程繼續運行,A線程停止(進入阻塞狀態)。等B運行完畢A再繼續運行。
sleep方法:線程中調用sleep方法後,本線程停止(進入阻塞狀態),運行權交給其他線程。
yield方法:線程中調用yield方法後本線程並不停止,運行權由本線程和優先級不低於本線程的線程來搶。(不一定優先級高的能先搶到,只是優先級高的搶到的時間長)
後臺線程
<span style="font-size:14px;">Thread t = new Thread(new Show());
t.setDaemon(true);
t.start();</span>
在java程序中,只要前臺有一個線程在運行,整個java程序進程不會消失,所以此時可以設置一個後臺線程,這樣即使java進程消失了,此後臺線程依然能夠繼續運行。結束線程(修改標示符flag爲false來終止線程的運行)
<span style="font-size:14px;">class Show implements Runnable
{
private boolean flag = true;
public void run()
{
while(flag)
{
System.out.println(Thread.currentThread().getName()+" is living");
}
}
public void shutDown()
{
this.flag = false;
}
public static void main(String[] args)
{
Show s = new Show();
Thread t = new Thread(s);
t.start();
try
{
Thread.sleep(2);
}
catch (Exception e)
{
e.printStackTrace();
}
s.shutDown();
}
}</span><span style="color: rgb(128, 128, 128); font-size: 15px;">
</span>
線程同步synchronized
synchronized可以修飾方法,或者方法內部的代碼塊。被synchronized修飾的代碼塊表示:一個線程在操作該資源時,不允許其他線程操作該資源。
【問題引出】:比如說對於賣票系統,有下面的代碼:
class Show implements Runnable
{
private int count =5;//5張票要賣
public void run()
{
for (int i = 0; i < 10 ; i++ )
{
if (count > 0)
{
try
{
Thread.sleep(200);
}
catch (Exception e)
{
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+","+count--);
}
}
public static void main(String[] args)
{
Show s = new Show();
new Thread(s,"A").start();
new Thread(s,"B").start();
new Thread(s,"C").start();
}
}
【運行結果】:
這裏出現了負數,顯然這個是錯的。,應該票數不能爲負值。
如果想解決這種問題,就需要使用同步。
所謂同步就是在統一時間段中只有有一個線程運行,其他的線程必須等到這個線程結束之後才能繼續執行。
【使用線程同步解決問題】
採用同步的話,可以使用同步代碼塊和同步方法兩種來完成。
同步代碼塊:
語法格式:
synchronized(同步對象){
//需要同步的代碼
}
但是一般都把當前對象this作爲同步對象。
class Show implements Runnable
{
private int count =5;
public void run()
{
for (int i = 0; i < 20 ; i++ )
{
synchronized(this)
{
if (count > 0)
{
try
{
//Thread.currentThread().yield();
Thread.sleep(300);
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+","+count--);
}
}
}
}
public static void main(String[] args)
{
Show s = new Show();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
t3.start();
t1.start();
t2.start();
}
}
【運行結果】:
也可以採用同步方法:
語法格式爲synchronized 方法返回類型方法名(參數列表){
// 其他代碼
}
class Show implements Runnable
{
private int count =5;
public void run()
{
for (int i = 0; i < 20 ; i++ )
{
sale();
}
}
public synchronized void sale()
{
if (count > 0)
{
try
{
//Thread.currentThread().yield();
Thread.sleep(300);
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+","+count--);
}
}
public static void main(String[] args)
{
Show s = new Show();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
t3.start();
t1.start();
t2.start();
}
}
同步的前提:1,必須要有兩個或者兩個以上的線程。
2,必須是多個線程使用同一個鎖。
必須保證同步中只能有一個線程在運行。
好處:解決了多線程的安全問題。
弊端:多個線程都需要判斷鎖,較爲消耗資源,
wait、notify、notifyAll的用法
wait方法:當前線程轉入阻塞狀態,讓出cpu的控制權,解除鎖定。
notify方法:喚醒因爲wait()進入阻塞狀態的其中一個線程。
notifyAll方法: 喚醒因爲wait()進入阻塞狀態的所有線程。
這三個方法都必須用synchronized塊來包裝,而且必須是同一把鎖,不然會拋出java.lang.IllegalMonitorStateException異常。
當多個線程共享一個資源的時候需要進行同步,但是過多的同步可能導致死鎖,同步中嵌套同步容易引發死鎖。
此處列舉經典的【生產者和消費者問題】:
class Info {
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;
}
private String name = "lfz";
private int age = 20;
}
/**
* 生產者
*/
class Producer implements Runnable{
private Info info=null;
Producer(Info info){
this.info=info;
}
public void run(){
boolean flag=false;
for(int i=0;i<10;++i){
while (true)
{
if(flag){
this.info.setName("adanac");
this.info.setAge(20);
flag=false;
}else{
this.info.setName("jean");
this.info.setAge(30);
flag=true;
}
}
}
}
}
/**
* 消費者類
* */
class Consumer implements Runnable{
private Info info=null;
public Consumer(Info info){
this.info=info;
}
public void run(){
for(int i=0;i<10;++i){
System.out.println(this.info.getName()+"<---->"+this.info.getAge());
}
}
}
class Show
{
public static void main(String[] args)
{
Info info=new Info();
Producer pro=new Producer(info);
Consumer con=new Consumer(info);
new Thread(pro).start();
new Thread(con).start();
}
}
從結果中看到,名字和年齡並沒有對應。
那麼如何解決呢?
1)加入同步
2)加入等待和喚醒
先來看看加入同步會是如何:
class Info {
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 synchronized void set(String name, int age){
this.name=name;
this.age=age;
}
public synchronized void get(){
System.out.println(this.getName()+"<===>"+this.getAge());
}
private String name = "lfz";
private int age = 20;
}
/**
* 生產者
*/
class Producer implements Runnable{
private Info info=null;
Producer(Info info){
this.info=info;
}
public void run(){
boolean flag=false;
for(int i=0;i<10;++i){
while (true)
{
if(flag){
this.info.set("adanac",10);
flag=false;
}else{
this.info.set("lfz",20);
flag=true;
}
}
}
}
}
/**
* 消費者類
* */
class Consumer implements Runnable{
private Info info=null;
public Consumer(Info info){
this.info=info;
}
public void run(){
for(int i=0;i<10;++i){
try{
Thread.sleep(100);
}catch (Exception e) {
e.printStackTrace();
}
this.info.get();
}
}
}
class Show
{
public static void main(String[] args)
{
Info info=new Info();
Producer pro=new Producer(info);
Consumer con=new Consumer(info);
new Thread(pro).start();
new Thread(con).start();
}
}
運行結果來看,錯亂的問題解決了,現在是adanac對應20,jean對於30。
/*
對於多個生產者和消費者。
爲什麼要定義while判斷標記。
原因:讓被喚醒的線程再一次判斷標記。
爲什麼定義notifyAll,
因爲需要喚醒對方線程。
因爲只用notify,容易出現只喚醒本方線程的情況。導致程序中的所有線程都等待。
*/
但是還是出現了重複讀取的問題,也肯定有重複覆蓋的問題。
如果想解決這個問題,就需要使用Object類幫忙了,我們可以使用其中的等待和喚醒操作。
要完成上面的功能,我們只需要修改Info類飢渴,在其中加上標誌位,並且通過判斷標誌位完成等待和喚醒的操作,代碼如下:class Info {
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 synchronized void set(String name, int age){
if (!flag)
{
try
{
super.wait();
}
catch (Exception e)
{
e.printStackTrace();
}
}
this.name=name;
this.age=age;
flag = false;
super.notify();
}
public synchronized void get(){
if (flag)
{
try
{
super.wait();
}
catch (Exception e)
{
e.printStackTrace();
}
}
System.out.println(this.getName()+"<===>"+this.getAge());
flag = true;
super.notify();
}
private String name = "xiaoli";
private int age = 22;
private boolean flag = false;
}
/**
* 生產者
*/
class Producer implements Runnable{
private Info info=null;
Producer(Info info){
this.info=info;
}
public void run(){
boolean flag=false;
for(int i=0;i<10;++i){
while (true)
{
if(flag){
this.info.set("adanac",10);
flag=false;
}else{
this.info.set("lfz",20);
flag=true;
}
}
}
}
}
/**
* 消費者類
* */
class Consumer implements Runnable{
private Info info=null;
public Consumer(Info info){
this.info=info;
}
public void run(){
for(int i=0;i<10;++i){
this.info.get();
}
}
}
class Show
{
public static void main(String[] args)
{
Info info=new Info();
Producer pro=new Producer(info);
Consumer con=new Consumer(info);
new Thread(pro).start();
new Thread(con).start();
}
}
【運行結果】:
JDK1.5 中提供了多線程升級解決方案。
將同步Synchronized替換成現實Lock操作。
將Object中的wait,notify notifyAll,替換了Condition對象。
Lock:替代了Synchronized
lock
unlock
newCondition()
Condition:替代了Object wait notify notifyAll
await();
signal();
signalAll();
*/