1.程序、進(jìn)程、線程
進(jìn)程可以細(xì)化為多個(gè)線程;每個(gè)線程擁有自己獨(dú)立的:棧、程序計(jì)數(shù)器;多個(gè)線程共享一個(gè)進(jìn)程中的結(jié)構(gòu):方法區(qū)、堆
2.單核cpu、多核cpu
單核cpu:是一種假的多線程;多核cpu,能夠更好的發(fā)揮多線程的效率
一個(gè)java.exe至少有3個(gè)線程:main()主線程、gc()垃圾回收線程、異常處理線程
3.并行、并發(fā)
并行:多個(gè)cpu同時(shí)執(zhí)行多個(gè)任務(wù)
并發(fā):一個(gè)cpu同時(shí)執(zhí)行多個(gè)任務(wù)
4.創(chuàng)建線程的方式
(1)創(chuàng)建線程的方式一
/*
*創(chuàng)建多線程步驟:
* 1.繼承Thread類
* 2.重寫THread類的run方法--》將線程執(zhí)行的操作聲明在run()中
* 3.創(chuàng)建Thread的子類的對(duì)象
* 4.使用子類調(diào)用start()方法
*
* */
class MyThread extends Thread{
@Override
public void run() {
for(int i = 0;i < 20;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() +"...."+ i);
}
}
}
}
public static void main(String[] args) {
MyThread thread_01 = new MyThread();
//thread_01線程中執(zhí)行
thread_01.start();
}
使用匿名類創(chuàng)建多個(gè)線程
public static void main(String[] args) {
//new Thread().start();//此種方式并不是創(chuàng)建匿名的thread對(duì)象,而是開啟Thread這個(gè)父類線程
new Thread(){//創(chuàng)建匿名子類線程1
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "....");
}
}.start();
new Thread(){//創(chuàng)建匿名子類線程2
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "....");
}
}.start();
}
Thread中常用的方法
1.設(shè)置name
方式1.setName()
MyThread thread_01 = new MyThread();
thread_01.setName("xiancheng");
方式2.構(gòu)造器中調(diào)用父類構(gòu)造器
MyThread thread_01 = new MyThread("xian");
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
2.yield:線程讓步,即釋放當(dāng)前cpu執(zhí)行權(quán)
3.join:在線程a中調(diào)用線程b的join(),此時(shí)線程a就進(jìn)入阻塞狀態(tài),直到線程b完全執(zhí)行完以后,線程a才結(jié)束阻塞狀態(tài)
4.sleep(time):讓當(dāng)前線程睡眠指定毫秒,在指定時(shí)間內(nèi),當(dāng)前線程是阻塞狀態(tài)
5.isAlive:判斷當(dāng)前線程是否存活
線程的調(diào)度
線程優(yōu)先級(jí):
1.獲取線程優(yōu)先級(jí):thread_01.getPriority();
2.設(shè)置線程優(yōu)先級(jí): MAX_PRIORITY=10;MIN_PRIORITY=1,NORM_PRIORITY=5(默認(rèn)值)
thread_01.setPriority(Thread.MAX_PRIORITY);
同優(yōu)先級(jí)線程:先到先服務(wù)
對(duì)高優(yōu)先級(jí):高優(yōu)先級(jí)的線程搶占cpu,也有可能搶不到,只是概率高而已
(2)創(chuàng)建線程的方式2:實(shí)現(xiàn)Runnable接口
/*
* 方式二創(chuàng)建多線程
* 1.實(shí)現(xiàn)Runnable接口
* 2.重寫run方法
* 3.創(chuàng)建實(shí)現(xiàn)類的對(duì)象
* 4.將此對(duì)象作為參數(shù),傳遞到Thread類的構(gòu)造器中
* 5.使用Thread類對(duì)象調(diào)用start方法
* */
public class Thread_02 {
public static void main(String[] args) {
//創(chuàng)建實(shí)現(xiàn)類對(duì)象
MyThread_02 my = new MyThread_02();
//將對(duì)象作為參數(shù),放入Thread類的構(gòu)造器中
Thread t = new Thread(my);
//使用Thread類的對(duì)象調(diào)用start
t.start();
}
}
class MyThread_02 implements Runnable{
@Override
public void run() {
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
// System.out.println(getName()+".........");不能直接getName,原因是該類并沒有繼承Thread類
System.out.println(Thread.currentThread().getName()+"........."+i);
}
}
}
}
售票:
/*
* 創(chuàng)建3個(gè)賣票窗口,一共有100張票
* 存在線程安全問題
* */
//實(shí)現(xiàn)接口方式
public class WindowTest_02 {
public static void main(String[] args) {
//創(chuàng)建1個(gè)Window_02
Window_02 w = new Window_02();
Thread t1 = new Thread(w);//不同線程使用同一個(gè)對(duì)象
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
//保證任意一個(gè)窗口售票時(shí),其他窗口不運(yùn)行,且獲得公有的對(duì)象,即static
t1.setName("1號(hào)");
t2.setName("2號(hào)");
t3.setName("3號(hào)");
t1.start();
t2.start();
t3.start();
}
}
class Window_02 implements Runnable {
private int i = 100;//未使用static方法
......
}
//繼承方式
public class WindowTest {
public static void main(String[] args) {
//創(chuàng)建3個(gè)賣票窗口
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
//保證任意一個(gè)窗口售票時(shí),其他窗口不運(yùn)行,且獲得公有的對(duì)象,即static
w1.setName("1號(hào)");
w2.setName("2號(hào)");
w3.setName("3號(hào)");
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread {
private static int i = 100;
.......
}
比較線程的兩種方式
開發(fā)中,優(yōu)先選擇實(shí)現(xiàn)Runnable接口的方式
原因:1.實(shí)現(xiàn)方式?jīng)]有單繼承的局限性
2.實(shí)現(xiàn)的方式更適合處理多個(gè)線程有共享數(shù)據(jù)的情況
聯(lián)系:public class Thread implements Runnable
相同點(diǎn):兩種方式都要重寫run方法
線程通信:
wait()、notify()、notifyAll():此三種方法定義在Object類中
線程分類:守護(hù)線程、分類線程
線程的生命周期

線程同步,解決線程安全
1.以上述售票為例,會(huì)出現(xiàn)的問題:重票、錯(cuò)票---》即線程安全問題
2.問題出現(xiàn)原因:當(dāng)某個(gè)線程還未完成操作時(shí),有新的線程進(jìn)入,也對(duì)其進(jìn)行操作
3.解決辦法:當(dāng)某個(gè)線程還未完成操作時(shí),其他線程必須在外等待,知道該線程完成操作才其它線程才可以運(yùn)行。這種情況即使在線程出現(xiàn)阻塞時(shí),也不能被改變
4.在java中,使用同步機(jī)制,解決線程安全問題:
(1)同步代碼塊:要求多個(gè)線程必須共用同一把鎖;壞處,相當(dāng)于單線程,效率低
sychronized(同步監(jiān)視器){
//需要被同步的代碼,不能多也不能少,即操作共享數(shù)據(jù)(多個(gè)線程共同操作的變量)的代碼
}
同步監(jiān)視器:俗稱鎖。任何一個(gè)對(duì)象都可以充當(dāng)鎖
1).實(shí)現(xiàn)Runnable接口方式的鎖
class Window_02 implements Runnable {
private int i = 100;//未使用static方法
Object obj = new Object();//所有線程共享一個(gè)
@Override
public void run() {
while(true){
synchronized(obj){//鎖;
synchronized(this){//此時(shí)的this,表示當(dāng)前Window_02對(duì)象,implements Runnable接口時(shí),多個(gè)線程公用一個(gè)線程子類對(duì)象
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i > 0){
System.out.println(Thread.currentThread().getName()+"賣票:票號(hào)為"+i);
i--;
}else{
break;
}
}
}
}
2).繼承Thread方式的鎖
class Window extends Thread {
private static int i = 100;//必須使用static
private static Object obj = new Object();//必須使用static
@Override
public void run() {
while(true){
synchronized(obj){//正確
synchronized(Window.class){//正確,以類為對(duì)象,只會(huì)加載一次,即Class clazz = Window.class
//synchronized(this){//錯(cuò)誤,因?yàn)閑xtends Thread時(shí),會(huì)創(chuàng)建多個(gè)線程對(duì)象,即,t1,t2,t3,他們不共用一個(gè)線程
......
}
}
}
}
(2)同步方法
如果操作共享數(shù)據(jù)的代碼完整的聲明在一個(gè)方法中,則將此方法聲明為同步的,此方法為同步方法
總結(jié):同步方法不需要顯示的聲明;非靜態(tài)同步方法,同步監(jiān)視器是this,靜態(tài)同步方法,同步監(jiān)視器是當(dāng)前類本身
1)繼承Thread
class Window extends Thread {
private static int i = 100;
@Override
public void run() {
show();
}
private static synchronized void show() {//使用static關(guān)鍵字修飾該鎖,則所有成員共享同一個(gè)鎖;該鎖是當(dāng)前類Window.class
// private synchronized void show() {//錯(cuò)誤,此鎖代表this,但創(chuàng)建的子類Thread對(duì)象創(chuàng)建了好幾個(gè),表示不同對(duì)象
while(true){
if(i > 0){
System.out.println(Thread.currentThread().getName()+"賣票:票號(hào)為"+i);
i--;
}else{
break;
}
}
}
}
2)實(shí)現(xiàn)Runnable接口
class Window_02 implements Runnable {
private int i = 100;//未使用static方法
@Override
public void run() {
show();
}
private synchronized void show() {//此時(shí)鎖代表this
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i > 0){
System.out.println(Thread.currentThread().getName()+"賣票:票號(hào)為"+i);
i--;
}else{
break;
}
}
}
}
死鎖
1.不同線程分別占用對(duì)方需要的同步資源不放棄,都在等待對(duì)方釋放資源;出現(xiàn)死鎖后,不會(huì)出現(xiàn)異常和提示,只是所有線程都阻塞,無法繼續(xù)
2.解決辦法:盡量避免同步或者使用算法
5.lock鎖解決線程安全(jdk5.0新特性)
同步鎖使用Lock充當(dāng)對(duì)象;使用ReentranLock的對(duì)象實(shí)現(xiàn)Lock
class MyLock extends Thread {
private static int i = 40;
private ReentrantLock lock = new ReentrantLock();//創(chuàng)建lock鎖對(duì)象
@Override
public void run() {
try {
//打開鎖
lock.lock();
while (true) {
if (i > 0) {
System.out.println(getName() + "...." + i);
i--;
} else {//跳出來,否則程序一直在運(yùn)行
break;
}
}
}finally {
//釋放鎖
lock.unlock();
}
}
}
synchronized和lock異同:
相同點(diǎn):都能解決線程安全問題
不同點(diǎn):synchronized機(jī)制在執(zhí)行完相應(yīng)的同步代碼塊后,自動(dòng)釋放同步監(jiān)視器;lock需要手動(dòng)啟動(dòng)(lock)或結(jié)束(unlock)同步;lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
優(yōu)先使用順序:lock--》同步代碼塊--》同步方法
例題:
銀行有一個(gè)賬戶,有兩個(gè)用戶分別向同一個(gè)賬戶存3000元,每次存1000,存3次。每次存完打印賬戶余額
public class Bank {
public static void main(String[] args) {
//此種方式類似于實(shí)現(xiàn)runnable接口的創(chuàng)建調(diào)用start方法
Account acct = new Account(0);
Customer c1 = new Customer(acct);//創(chuàng)建名為甲的客戶
Customer c2 = new Customer(acct);
c1.start();
c2.start();
}
}
//存儲(chǔ)用戶,每個(gè)用戶共存款3次
class Customer extends Thread {
private Account acct;//創(chuàng)建公有的Account賬戶
//創(chuàng)建Account的構(gòu)造方法
public Customer(Account acct) {
this.acct = acct;
}
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
//調(diào)用Account存錢的方法
acct.deposit(1000);
}
}
}
//銀行賬戶,共存到6000,即balance是共有數(shù)據(jù)
class Account {
private int balance = 0;
//創(chuàng)建Account的構(gòu)造器,即一創(chuàng)建Account賬戶,便初始化其balance
Account(int balance){
this.balance = balance;
}
//方式一:lock
鎖
private static ReentrantLock lock = new ReentrantLock();
void deposit(int money){
try{
lock.lock();
if(money >= 0){
balance += money;
System.out.println(Thread.currentThread().getName() + "存款,共存" + balance);
}
}finally {
lock.unlock();
}
}
//方式二,同步方法
synchronized void deposit(int money){
if(money >= 0){
balance += money;
System.out.println(Thread.currentThread().getName() + "存款,共存" + balance);
}
}
}
線程通信
wait()、notify()/notifyAll()必須搭配使用,且都屬于Object方法;必須使用在同步代碼塊或同步方法中;調(diào)用者必須是同步代碼塊或同步方法中的同步監(jiān)視器,否則出現(xiàn)IllegalMonitorStateException
wait:一旦執(zhí)行此方法,當(dāng)前線程就進(jìn)入阻塞狀態(tài),并釋放同步監(jiān)視器
notify:喚醒被wait的一個(gè)線程,若有多個(gè)線程,則喚醒優(yōu)先級(jí)高的
notifyAll:喚醒所有wait的線程
/*
* 使用線程1,2交替打印1~100的數(shù)
* */
public class Communication {
public static void main(String[] args) {
new Number().start();
new Number().start();
}
}
class Number extends Thread{
private static int num = 1;
private static Object obj = new Object();
@Override
public void run() {
while(true){
//繼承方式的不能使用this,因?yàn)槊看蝨his的對(duì)象不同
synchronized (obj){
obj.notify();
if(num <= 20){
System.out.println(getName() + "打印" + num);
num++;
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
例題:
sleep和wait的異同:
相同點(diǎn):都能使當(dāng)前線程進(jìn)入阻塞狀態(tài)
不同點(diǎn):(1)位置不同,Thread類中聲明sleep,Object類中聲明wait
(2)調(diào)用要求不同,sleep可以在任意場景,wait必須在同步代碼塊或同步方法中
(3)是否釋放同步監(jiān)視器,若兩者都使用在同步代碼塊或同步方法中,sleep不會(huì)釋放鎖,wait會(huì)釋放鎖
/*
* 線程通信的應(yīng)用:生產(chǎn)者/消費(fèi)者問題(經(jīng)典例題)
* 生產(chǎn)者(Product)將產(chǎn)品交給店員(Clerk),而消費(fèi)者(Customer)從店員處取走產(chǎn)品,店員一次只能持有固定數(shù)量的產(chǎn)品(如:20)
* 如果生產(chǎn)者試圖生產(chǎn)更多的產(chǎn)品,店員會(huì)讓生產(chǎn)者停止,若店中有空位再通知生產(chǎn)者繼續(xù)生產(chǎn);如果店中沒有產(chǎn)品,店員會(huì)讓消費(fèi)者
* 等一下,若有產(chǎn)品了再通知消費(fèi)者取走產(chǎn)品
*
* 分析:
* 1.多線程:生產(chǎn)者、消費(fèi)者
* 2.線程安全:共享數(shù)據(jù),產(chǎn)品或店主
* 3.解決線程安全:同步(3種)
* 4.線程間相互通信:無產(chǎn)品,無空位時(shí)
*
* */
public class ProductList {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor p = new Productor(clerk);
Thread t = new Thread(p);
Customer_P c = new Customer_P(clerk);
t.setName("生產(chǎn)");
c.setName("顧客");
t.start();
c.start();
}
}
//共享數(shù)據(jù)
class Clerk{
private int num = 0;
//消費(fèi)者消費(fèi)產(chǎn)品
public synchronized void cust_Product() {//解決同步問題
if(num > 0){
System.out.println(Thread.currentThread().getName() + "..."+num);
num--;
notify();//消費(fèi)者消費(fèi)產(chǎn)品后,就喚醒生產(chǎn)者
}else {
try {
wait();//當(dāng)沒有產(chǎn)品,消費(fèi)者處于等待狀態(tài)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//生產(chǎn)者生產(chǎn)產(chǎn)品
public synchronized void pro_Product() {//解決同步問題
if(num <= 20){
num++;
System.out.println(Thread.currentThread().getName() + "...."+num);
notify();//生產(chǎn)了產(chǎn)品,就喚醒消費(fèi)者
}else {
try {
wait();//當(dāng)產(chǎn)品多于20,生產(chǎn)者停止生產(chǎn)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//implements Runnable設(shè)置Productor線程
class Productor implements Runnable{
private Clerk clerk;
Productor(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
while (true){
clerk.pro_Product();
}
}
}
//extends Thread設(shè)置Customer線程
class Customer_P extends Thread{
private Clerk clerk;
Customer_P(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
while (true){
clerk.cust_Product();
}
}
}
jdk5.0新增線程創(chuàng)建方式
新增方式1:實(shí)現(xiàn)Callable接口
與實(shí)現(xiàn)Runnable接口相比較,Callable功能更強(qiáng)大
(1)相比run方法,可以有返回值
(2)方法可以拋出異常
(3)支持泛型返回值
(4)需要借助FutureTask類,比如獲得返回結(jié)果
/*
* 創(chuàng)建多線程方式3:實(shí)現(xiàn)Callable接口
* 1.實(shí)現(xiàn)Callable接口
* 2.重寫call方法
* 3.創(chuàng)建接口實(shí)現(xiàn)類的對(duì)象
* 4.將該對(duì)象傳入FutureTask的構(gòu)造器中
* 5.將FutureTask對(duì)象傳遞到Thread類的構(gòu)造器中
* 6.使用該對(duì)象的get方法得到重寫call的返回值
*
* */
public class Callable_01 {
public static void main(String[] args) {
//3.創(chuàng)建接口實(shí)現(xiàn)類的對(duì)象
NumThread num = new NumThread();
//4.將接口實(shí)現(xiàn)類的對(duì)象作為參數(shù)傳遞到FutureTask構(gòu)造器中,創(chuàng)建FutureTask的對(duì)象
FutureTask task = new FutureTask(num);
//5.將FutureTask的對(duì)象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread對(duì)象,并調(diào)用start方法
new Thread(task).start();
try {
//6.獲得FutureTask構(gòu)造器參數(shù)Callable實(shí)現(xiàn)類的重寫方法call的返回值
Object o = task.get();
System.out.println("總和為" + o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class NumThread implements Callable{
private int sum = 0;
@Override
public Object call() throws Exception {
for (int i = 0; i <= 100; i++) {
if(i % 2 == 0) {
sum += i;
System.out.println( sum);
}
}
return sum;
}
}
新增方式2:使用線程池(開發(fā)中常用)
好處:響應(yīng)速度提高;提高資源重用率;便于管理
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定數(shù)量的線程池
ExecutorService service = Executors.newFixedThreadPool(5);//創(chuàng)建線程的個(gè)數(shù)
//ExecutorService是一個(gè)接口,使用子類ThreadPoolExecutor可以調(diào)用其屬性方法
// ThreadPoolExecutor service1= (ThreadPoolExecutor) service;
// service1.getMaximumPoolSize();
System.out.println(service.getClass());
//2.執(zhí)行指定線程的操作,需要提供Runnable或Callable接口
service.execute(new NumPool());//適合Runnable接口
//service.submit(new Callable);//適合Callable接口
//3.關(guān)閉線程池
service.shutdown();
}
}