進程 : 正在執行的程序稱作為一個進程。 進程負責了內存空間的劃分。
問題: windows號稱是多任務的操作系統,那么windows是同時運行多個應用程序嗎?
- 從宏觀的角度: windows確實是在同時運行多個應用程序。
- 從微觀角度: cpu是做了一個快速切換執行的動作,由于速度態度,所以我感覺不到在切換 而已。
線程: 線程在一個進程 中負責了代碼的執行,就是進程中一個執行路徑,
多線程: 在一個進程中有多個線程同時在執行不同的任務。
疑問 :線程負責了代碼 的執行,我們之前沒有學過線程,為什么代碼可以執行呢?
運行任何一個java程序,jvm在運行的時候都會創建一個main線程執行main方法中所有代碼。
一個java應用程序至少有幾個線程?
至少有兩個線程, 一個是主線程負責main方法代碼的執行,一個是垃圾回收器線程,負責了回收垃圾。
多線程的好處:
- 解決了一個進程能同時執行多個任務的問題。
- 提高了資源的利用率。
多線程 的弊端:
- 增加cpu的負擔。
- 降低了一個進程中線程的執行概率。
- 引發了線程安全 問題。
- 出現了死鎖現象。
如何創建多線程:
方式一:
- 自定義一個類繼承Thread類。
- 重寫Thread類的run方法 , 把自定義線程的任務代碼寫在run方法中
- 創建Thread的子類對象,并且調用start方法開啟線程
疑問: 重寫run方法的目的是什么?
每個線程都有自己的任務代碼,jvm創建的主線程的任務代碼就是main方法中的所有代碼, 自定義線程的任務代碼就寫在run方法中,自定義線程負責了run方法中代碼。
。
注意: 一個線程一旦開啟,那么線程就會執行run方法中的代碼,run方法千萬不能直接調用,直接調用run方法就相當調用了一個普通的方法而已并沒有開啟新的線程。
public class Demo1 extends Thread {
@Override //把自定義線程的任務代碼寫在run方法中。
public void run() {
for(int i = 0 ; i < 100 ; i++){
System.out.println("自定義線程:"+i);
}
}
public static void main(String[] args) {
//創建了自定義的線程對象。
Demo1 d = new Demo1();
//調用start方法啟動線程
d.start();
for(int i = 0 ; i < 100 ; i++){
System.out.println("main線程:"+i);
}
}
}
需求: 模擬QQ視頻與聊天同時在執行。
class TalkThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("hi,你好!開視頻唄...");
}
}
}
class VideoThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("視頻視頻....");
}
}
}
public class Demo2 {
public static void main(String[] args) {
TalkThread talkThread = new TalkThread();
talkThread.start();
VideoThread videoThread = new VideoThread();
videoThread.start();
}
}
方式二:
- 自定義一個類實現Runnable接口。
- 實現Runnable接口 的run方法,把自定義線程的任務定義在run方法上。
- 創建Runnable實現類對象。
- 創建Thread類 的對象,并且把Runnable實現類的對象作為實參傳遞。
- 調用Thread對象 的start方法開啟一個線程。
問題1: 請問Runnable實現類的對象是線程對象嗎?
Runnable實現類的對象并 不是一個線程對象,只不過是實現了Runnable接口 的對象而已。
只有是Thread或者是Thread的子類才是線程 對象。
問題2:為什么要把Runnable實現類的對象作為實參傳遞給Thread對象呢?作用是什么?
作用就是把Runnable實現類的對象的run方法作為了線程的任務代碼去執行了。
推薦使用: 第二種。 實現Runable接口的。
原因: 因為java單繼承 ,多實現的。
public class Demo3 implements Runnable{
@Override
public void run() {
/*System.out.println("this:"+ this);
System.out.println("當前線程:"+ Thread.currentThread());
這個在實現Thread類中是一樣的,但是在這里是不一樣的,this是Runnable實現類
但是當前線程是主方法中創建的Thread對象啟用的線程
*/
for(int i = 0 ; i < 100 ; i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) {
//創建Runnable實現類的對象
Demo3 d = new Demo3();
//創建Thread類的對象, 把Runnable實現類對象作為實參傳遞。
Thread thread = new Thread(d,"狗娃"); //Thread類使用Target變量記錄了d對象,
//調用thread對象的start方法開啟線程。
thread.start();
for(int i = 0 ; i < 100 ; i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
/*
Thread類 的run方法
* @Override
public void run() {
if (target != null) {
target.run(); //就相當于Runnable實現類的對象的run方法作為了Thread對象的任務代碼了。
}
}
*/
}
線程常用的方法:
Thread(String name) 初始化線程的名字
setName(String name) 設置線程對象名
getName() 返回線程的名字
sleep() 線程睡眠指定的毫秒數。 靜態的方法, 那個線程執行了sleep方法代碼那么就是那個線程睡眠。
currentThread() 返回當前的線程對象,該方法是一個靜態的方法, 注意: 那個線程執行了currentThread()代碼就返回那個線程 的對象。
getPriority() 返回當前線程對象的優先級 默認線程的優先級是5
setPriority(int newPriority) 設置線程的優先級 雖然設置了線程的優先級,但是具體的實現取決于底層的操作系統的實現(最大的優先級是10 ,最小的1 , 默認是5)。
public class Test07 extends Thread {
public Test07(String name){
super(name); //調用了Thread類的一個參數的構造方法.(設置線程名稱)
}
@Override
public void run() {
/*System.out.println("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 {
//創建了一個線程對象
Test07 d = new Test07("狗娃");
//d.sleep(1000); 雖然是d調用的sleep方法,但是該方法是在主線程中執行的,所以是主線程睡眠
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());
*/
}
}
需求: 模擬3個窗口同時在售50張 票 。
問題1 :為什么50張票被賣出了150次?
原因: 因為num是非靜態的,非靜態的成員變量數據是在每個對象中都會維護一份數據的,三個線程對象就會有三份。
解決方案:把num票數共享出來給三個線程對象使用。使用static修飾。
問題2: 出現了線程安全問題 ?
線程 安全問題的解決方案:sun提供了線程同步機制讓我們解決這類問題的。
java線程同步機制的方式:
方式一:同步代碼塊
同步代碼塊的格式:
synchronized(鎖對象){
需要被同步的代碼...
}
同步代碼塊要注意事項:
1. 任意的一個對象都可以做為鎖對象。
2. 在同步代碼塊中調用了sleep方法并不是釋放鎖對象的。
3. 只有真正存在線程安全問題的時候才使用同步代碼塊,否則會降低效率的。
4. 多線程操作的鎖 對象必須 是唯一共享 的。否則無效。
需求: 一個銀行賬戶5000塊,兩夫妻一個拿著 存折,一個拿著卡,開始取錢比賽,每次只能取一千塊,要求不準出現線程安全問題。
方式二:同步函數 : 同步函數就是使用synchronized修飾一個函數。
同步函數要注意的事項 :
1. 如果是一個非靜態的同步函數的鎖 對象是this對象,如果是靜態的同步函數的鎖 對象是當前函數所屬的類的字節碼文件(class對象)。
2. 同步函數的鎖對象是固定的,不能由你來指定 的。
推薦使用: 同步代碼塊。
原因:
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 ("鎖") { //靜態字符常量也可以,因為常量區只會存在一份
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 Demo4 {
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();
}
}
/*
java中同步機制解決了線程安全問題,但是也同時引發死鎖現象。
死鎖現象:
死鎖現象出現 的根本原因:
1. 存在兩個或者兩個以上的線程。
2. 存在兩個或者兩個以上的共享資源。
死鎖現象的解決方案: 沒有方案。只能盡量避免發生而已。
*/
class DeadLock extends Thread{
public DeadLock(String name){
super(name);
}
public void run() {
if("張三".equals(Thread.currentThread().getName())){
synchronized ("遙控器") {
System.out.println("張三拿到了遙控器,準備 去拿電池!!");
synchronized ("電池") {
System.out.println("張三拿到了遙控器與電池了,開著空調爽歪歪的吹著...");
}
}
}else if("狗娃".equals(Thread.currentThread().getName())){
synchronized ("電池") {
System.out.println("狗娃拿到了電池,準備去拿遙控器!!");
synchronized ("遙控器") {
System.out.println("狗娃拿到了遙控器與電池了,開著空調爽歪歪的吹著...");
}
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
DeadLock thread1 = new DeadLock("張三");
DeadLock thread2 = new DeadLock("狗娃");
//開啟線程
thread1.start();
thread2.start();
}
}