- 前言,前段時間一直在研究JavaWeb方向,最近閒下來,回過頭來看看線程,又有不一樣的見解。
package sh.thread;
public class ThreadDemo1 extends Thread{
//1.自定義線程要執行的目標代碼
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("線程1:"+i);
}
}
public static void main(String[] args){
//2.創建自定義線程類的對象
ThreadDemo1 T1 = new ThreadDemo1();
//3.調用start方法啓動線程
T1.start();
for(int i=0;i<5;i++){//main主線程
System.out.println("線程2:"+i);
}
}
}
/*
* 1.線程基礎知識:
* 進程:正在執行的程序稱作爲一個進程。 進程負責了內存空間的劃分。
* 線程:線程在一個進程中 負責了代碼的執行,就是進程中一個執行路徑。
* 多線程:在一個進程中有多個線程(多個不同路徑),同時在執行不同的任務。
*
* 2.多線程的好處:
* 1.解決了一個進程能同時執行多個任務的問題。(高併發點擊網頁必須的!厲害吧,騷年!)
* 2.提高了資源的利用率。
*
* 3.多線程的弊端:
* 1.增加cpu的的負擔。
* 2.降低了一個進程中線程的執行概率。
* 3.引發了線程安全問題。
* 3.出現了死鎖現象。
*
* 4.創建線程的方式:
* 方法一:
* 1.自定義一個類繼承Thread類。
* 2.重寫Thread類的run方法,把自定義線程的任務代碼寫在run方法中
* (重寫的目的:每個線程都有自己的任務代碼,jvm創建的主線程的任務代碼就是
* main方法中的所有代碼,而自定義線程的任務代碼就寫在run方法中)
* 3.創建Thread的子類對象,並且調用start方法開啓線程。
* 注意:一個線程一旦開啓,那麼線程就會執行run方法中的代碼,run方法千萬不能直接調用,
* 直接調用 就相當調用了一個普通的方法而並沒有開啓新的線程。
* */
用上面的一個基礎線程,引出線程生命週期圖,嗯,非常清晰了。
線程的一些常用方法及註解
package sh.thread;
public class ThreadDemo2 extends Thread{
/*
5. 線程常用的方法:
Thread(String name) 初始化線程的名字
setName(String name) 設置線程對象名
getName() 返回線程的名字
sleep() 靜態:在哪個線程內,就是那個線程 線程睡眠指定的毫秒數。 靜態的方法, 那個線程執行了sleep方法代碼那麼就是那個線程睡眠。
currentThread() 靜態:同上 返回當前的線程對象,該方法是一個靜態的方法, 注意: 那個線程執行了currentThread()代碼就返回那個線程 的對象。
getPriority() 返回當前線程對象的優先級 默認線程的優先級是5
setPriority(int newPriority) 設置線程的優先級 雖然設置了線程的優先級,但是具體的實現取決於底層的操作系統的實現(最大的優先級是10 ,最小的1 , 默認是5)。
*/
public ThreadDemo2(String name){
super(name);//調用了Thread類的一個 參數的構造方法。
}
@Override
public void run() {
/*System.out.println("this:"+ this); //this對象與當前線程對象是同一個
System.out.println("當前線程對象:" + Thread.currentThread()); */
for (int i = 0; i < 100 ; i++) {//自定義線程目標代碼
System.out.println(Thread.currentThread().getName()+":"+i);
/*try {
Thread.sleep(100); //爲什麼在這裏不能拋出異常,只能捕獲?? Thread類的run方法沒有拋出異常類型,所以子類不能拋出異常類型(子類不能超過父類)
} catch (InterruptedException e) {
e.printStackTrace();
} */
}
}
public static void main(String[] args) throws InterruptedException {
//創建了一個線程對象
ThreadDemo2 d = new ThreadDemo2("狗剩哥");
d.setPriority(10); //設置線程 的優先級。 優先級的數字越大,優先級越高 , 優先級的範圍是1~10
d.start();
for (int i = 0; i < 100 ; i++) {//主線程目標代碼
System.out.println(Thread.currentThread().getName()+":"+i);
}
/*
System.out.println("自定義線程的優先級:"+d.getPriority()); //線程的優先級默認是5
System.out.println("主線程的優先級:"+Thread.currentThread().getPriority());
d.start();
d.setName("鐵蛋"); //setName設置線程的名字
d.start(); //開啓線程
Thread mainThread = Thread.currentThread();
System.out.println("主線程的名字:"+ mainThread.getName());
*/
}
}
4 .好吧,經典的多線程賣票問題,當時懵逼的有木有!,現在看來,不過爾爾…
package sh.thread;
/*
1. 需求: 模擬3個窗口同時在售50張 票 。
問題1 :爲什麼50張票被賣出了150次?
出現 的原因: 因爲num是非靜態的,非靜態的成員變量數據是在每個對象中都會維護一份數據的,三個線程對象就會有三份。
2. 解決方案:把num票數共享出來給三個線程對象使用。使用static修飾。
問題2: 出現了線程安全問題 ?
3. 線程 安全問題的解決方案:sun提供了線程同步機制讓我們解決這類問題的。
java線程同步機制的方式:
方式一:同步代碼塊
同步代碼塊的格式:
synchronized(鎖對象){
需要被同步的代碼...
}
4. 同步代碼塊要注意事項:
1. 任意的一個對象都可以做爲鎖對象。
2. 在同步代碼塊中調用了sleep方法並不是釋放鎖對象的。
3. 只有真正存在線程安全問題的時候才使用同步代碼塊,否則會降低效率的。
4. 多線程操作的鎖 對象必須 是唯一共享 的。否則無效。
5. 出現線程安全問題的根本原因:
1. 存在兩個或者兩個以上 的線程對象,而且線程之間共享着一個資源。
2. 有多個語句操作了共享資源。
*/
class SaleTicket extends Thread{
static int num = 50;//票數 非靜態的成員變量,非靜態的成員變量數據是在每個對象中都會維護一份數據的。
static Object o = new Object();//定義一個唯一共享的鎖對象
public SaleTicket(String name) {
super(name);
}
@Override
public void run() {
while(true){
//同步代碼塊
synchronized (o) {
if(num>0){
System.out.println(Thread.currentThread().getName()+"售出了第"+num+"號票");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
}else{
System.out.println("賣完了..");
break;
}
}
}
}
}
public class ThreadDemo3 {
public static void main(String[] args) {
//創建三個線程對象,模擬三個窗口
SaleTicket thread1 = new SaleTicket("窗口1");
SaleTicket thread2 = new SaleTicket("窗口2");
SaleTicket thread3 = new SaleTicket("窗口3");
//開啓線程售票
thread1.start();
thread2.start();
thread3.start();
}
}
5.線程通訊: 一個線程完成了自己的任務時,要通知另外一個線程去完成另外一個任務.
例子:生產者-消費者
package sh.thread;
/*
線程通訊: 一個線程完成了自己的任務時,要通知另外一個線程去完成另外一個任務.
生產者與消費者
wait(): 等待 如果線程執行了wait方法,那麼該線程會進入等待的狀態,等待狀態下的線程必須要被其他線程調用notify方法才能喚醒。
notify(): 喚醒 喚醒線程池等待線程其中的一個。
notifyAll() : 喚醒線程池所有等待 線程。
wait與notify方法要注意的事項:
1. wait方法與notify方法是屬於Object對象 的。
2. wait方法與notify方法必須要在同步代碼塊或者是同步函數中才能 使用。
3. wait方法與notify方法必需要由鎖對象調用。
*/
//產品類
class Product{
String name;
double price;
boolean flag = false; //判斷產品是否已生產,默認沒有生產完成。
}
//生產者
class Producter extends Thread{
//1.引入產品類
Product p;
Producter(Product p){
this.p = p;
}
@Override
public void run() {
int i = 0;
while (true) {
synchronized (p) {
if(p.flag==false){
if(i%2==0){
p.name = "蘋果";
p.price = 6.8;
}else {
p.name = "橡膠";
p.price = 2.0;
}
System.out.println("生產者生產出來:"+p.name+" 價格:"+p.price);
p.flag = true;
i++;
p.notifyAll();//喚醒消費者去消費,鎖對象調用
}else {
//已經生產了一個了,等待消費者先去消費
try {
p.wait(); //生產者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
//消費者
class Customer extends Thread{
//引入產品類
Product p;
public Customer(Product p) {
this.p = p;
}
@Override
public void run() {
while(true){
synchronized (p) {
if(p.flag){
System.out.println("消費者消費了:"+p.name+" 價格:"+p.price);
p.flag = false;//消費一下就停了
p.notifyAll();
}else {
try {//停止消費
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class ThreadDemo5 {
public static void main(String[] args){
Product p = new Product();
Producter producter = new Producter(p);
Customer customer = new Customer(p);
producter.start();
customer.start();
}
}
6.線程的停止的 兩個方法
package sh.thread;
/*
* 線程的停止:
* 1.停止一個線程 我們一般都會通過一個變量去控制的。
* 2.如果需要停止一個處於等待狀態下的線程,那麼我們需要通過變量配合
* notify方法或者interrupt()來使用。
*notify:是喚醒它,它運行完了,自然就停止了,必須在鎖對象內
*interrupt:直接把它的等待狀態強制清除!
* */
public class Thread6 extends Thread{
boolean flag = true;//停止線程的標識
public Thread6(String name){
super(name);
}
//自定義一個一開始就等待的線程,來停掉它!
@Override
public synchronized void run() {
while(flag){
try {
this.wait();
} catch (InterruptedException e) {
System.out.println("已停止,且捕獲異常了");
}
}
}
public static void main(String[] args) {
Thread6 t6 = new Thread6("小二");
t6.start();//開始自定義線程
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
if(i==8){
t6.flag = false;//讓t6線程跳出循環
//停止線程方法1:喚醒它,自然停止
/*synchronized (t6) {
t6.notify();
}*/
//停止線程方法2:interrupt強制清除它的等待狀態:會拋出一個異常
t6.interrupt();
}
}
}
}
7 . 守護線程詳解
package sh.thread;
/*
守護線程(後臺線程):在一個進程中如果只剩下 了守護線程,那麼守護線程也會死亡。
//如果一個線程隨着你的主線程存在就存在,隨着主線程消亡就消亡,那就可以將它設置爲後臺線程
//守護線程:做下載更新包...
需求: 模擬QQ下載更新包。
一個線程默認都不是守護線程。
*/
public class Thread7 extends Thread {
public Thread7(String name){
super(name);
}
@Override
public void run() {
for(int i = 1 ; i<=10 ; i++){
System.out.println("更新包目前下載"+i+"%");
if(i==10){
System.out.println("更新包下載完畢,準備安裝..");
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread7 d = new Thread7("後臺線程");
d.setDaemon(true); //setDaemon() 設置線程是否爲守護線程,true爲守護線程, false爲非守護線程。
// System.out.println("是守護線程嗎?"+ d.isDaemon()); //判斷線程是否爲守護線程。
d.start();
for(int i = 1 ; i<=10 ; i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
8 . 線程中的 join方法
package cn.itcast.thread;
/*
join方法。 加入
*/
//老媽
class Mon extends Thread{
public void run() {
System.out.println("媽媽洗菜");
System.out.println("媽媽切菜");
System.out.println("媽媽準備炒菜,發現沒有醬油了..");
//叫兒子去打醬油
Son s= new Son();
s.start();
try {
s.join(); //加入。 一個線程如果執行join語句,那麼就有新的線程加入,執行該語句的線程必須要讓步給新加入的線程先完成任務,然後才能繼續執行。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("媽媽繼續炒菜");
System.out.println("全家一起吃飯..");
}
}
class Son extends Thread{
@Override
public void run() {
System.out.println("兒子下樓..");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("兒子一直往前走");
System.out.println("兒子打完醬油了");
System.out.println("上樓,把醬油給老媽");
}
}
public class Demo8 {
public static void main(String[] args) {
Mon m = new Mon();
m.start();
}
}