多線程
1.1 多線程介紹
進程指正在運行的程序。確切的來說,當一個程序進入內存運行,即變成一個進程,進程是處於運行過程中的程序,並且具有一定獨立功能。
1.2 Thread類
通過API中搜索,查到Thread類。通過閱讀Thread類中的描述。Thread是程序中的執行線程。Java 虛擬機允許應用程序並發地運行多個執行線程。
l 構造方法
l 常用方法
發現創建新執行線程有兩種方法。
一種方法是將類聲明為 Thread 的子類。該子類應重寫 Thread 類的 run 方法。創建對象,開啟線程。run方法相當於其他線程的main方法。
另一種方法是聲明一個實現 Runnable 接口的類。該類然后實現 run 方法。然后創建Runnable的子類對象,傳入到某個線程的構造方法中,開啟線程。
創建線程的步驟:
1 定義一個類繼承Thread。
2 重寫run方法。
3 創建子類對象,就是創建線程對象。
4 調用start方法,開啟線程並讓線程執行,同時還會告訴jvm去調用run方法。
package com.oracle.xiancheng;
public class Demo01 extends Thread {
public static void main(String[] args) {
//創建線程
MyThread mt=new MyThread();
//創建線程
mt.start();
//獲取正在執行的對象名稱 調用 getname
for(int i=0;i<100;++i){
System.out.println("main-------"+i);
}
}
}
自定義線程類
package com.oracle.Runnable;
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println("run-----"+i);
}
}
}
1.2.1 實現Runnable的原理
實現Runnable接口,避免了繼承Thread類的單繼承局限性。覆蓋Runnable接口中的run方法,將線程任務代碼定義到run方法中。
創建Thread類的對象,只有創建Thread類的對象才可以創建線程。線程任務已被封裝到Runnable接口的run方法中,而這個run方法所屬於Runnable接口的子類對象,所以將這個子類對象作為參數傳遞給Thread的構造函數,這樣,線程對象創建時就可以明確要運行的線程的任務。
1.2.2 實現Runnable的好處
第二種方式實現Runnable接口避免了單繼承的局限性,所以較為常用。實現Runnable接口的方式,更加的符合面向對象,線程分為兩部分,一部分線程對象,一部分線程任務。繼承Thread類,線程對象和線程任務耦合在一起。一旦創建Thread類的子類對象,既是線程對象,有又有線程任務。實現runnable接口,將線程任務單獨分離出來封裝成對象,類型就是Runnable接口類型。Runnable接口對線程對象和線程任務進行解耦。
1.3 線程的匿名內部類使用
package com.oracle.Runnable;
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println("run-----"+i);
}
}
}
package com.oracle.Runnable;
public class Demo02 {
public static void main(String[] args) {
//創建線程子類對象
//匿名內部類對象
//創建線程對象時,直接重寫Thread類中的run方法
Thread th=new Thread(){
public void run() {
System.out.println(Thread.currentThread().getName()+"run");
};
};
//開啟線程
th.start();
//使用匿名內部類的方式實現Runnable接口,重新Runnable接口中的run方法
/*Runnable r=new Runnable(){
public void run() {
System.out.println(Thread.currentThread().getName()+"run");
};
};
//創建線程任務對象
Thread th=new Thread(r);
//開啟線程
th.start();*/
}
}
運行結果:
線程池
2.1 線程池概念
線程池,其實就是一個容納多個線程的容器,其中的線程可以反復使用,省去了頻繁創建線程對象的操作,無需反復創建線程而消耗過多資源
2.2 使用線程池方式--Runnable接口
l Executors:線程池創建工廠類
public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象
l ExecutorService:線程池類
Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行
l Future接口:用來記錄線程任務執行完畢后產生的結果。線程池創建與使用
l 使用線程池中線程對象的步驟:
創建線程池對象
創建Runnable接口子類對象
提交Runnable接口子類對象
關閉線程池
package com.oracle.Runnable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo03 {
public static void main(String[] args) {
// Executors 線程池工廠類
// ExecutorService 線程池工廠類
// 獲取線程池對象
ExecutorService es = Executors.newFixedThreadPool(2);
// 創建線程任務對象
MyRunnable mr = new MyRunnable();
// 執行線程任務
es.submit(mr);
es.submit(mr);
es.submit(mr);
//釋放資源
es.shutdown();
}
}
package com.oracle.Runnable;
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println("run-----"+i);
}
}
}
運行結果:
等等
2.3 使用線程池方式—Callable接口
Callable接口:與Runnable接口功能相似,用來指定線程的任務。其中的call()方法,用來返回線程任務執行完畢后的結果,call方法可拋出異常。
ExecutorService:線程池類
<T> Future<T> submit(Callable<T> task):獲取線程池中的某一個線程對象,並執行線程中的call()方法
Future接口:用來記錄線程任務執行完畢后產生的結果。線程池創建與使用
使用線程池中線程對象的步驟:
創建線程池對象
創建Callable接口子類對象
提交Callable接口子類對象
關閉線程池
package com.oracle.Runnable;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "abc";
}
}
package com.oracle.Runnable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Demo04 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//創建線程任務
MyCallable mc=new MyCallable();
//獲取線程池工廠
ExecutorService es=Executors.newFixedThreadPool(2);
Future<String> f=es.submit(mc);
//創建返回值
String str=f.get();
System.out.println(str);
}
}
運行結果:
2.4 線程池練習:返回兩個數相加的結果和乘積的結果
和
package com.oracle.Demo01;
import java.util.concurrent.Callable;
public class MyCallables implements Callable<Integer> {
private int num1;
private int num2;
public MyCallables(int num1,int num2) {
this.num1=num1;
this.num2=num2;
}
@Override
public Integer call() throws Exception {
return num1+num2;
}
}
package com.oracle.Demo01;
import java.math.BigInteger;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//和
MyCallables mc1=new MyCallables(100,150);
MyCallables mc2=new MyCallables(10,15);
ExecutorService es=Executors.newFixedThreadPool(2);
Future<Integer> num1=es.submit(mc1);
Future<Integer> num2=es.submit(mc2);
int s1=num1.get();
int s2=num2.get();
System.out.println(s1);
System.out.println(s2);
es.shutdown();
}
}
運行結果:
積:
package com.oracle.Demo01;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.concurrent.Callable;
public class MyCallablesr implements Callable<String > {
private int num;
public MyCallablesr(int num) {
this.num=num;
}
@Override
public String call() throws Exception {
String base="1";//超long的范圍
for(int i=1;i<=num;i++){
//用BigDecimal轉換
BigDecimal stra=new BigDecimal(base);
BigDecimal end=new BigDecimal(i);
BigDecimal re=end.multiply(stra);
base=re.toString();
}
return base ;
}
}
package com.oracle.Demo01;
import java.math.BigInteger;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test1 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//積
MyCallablesr ms1=new MyCallablesr(100);
MyCallablesr ms2=new MyCallablesr(200);
ExecutorService es=Executors.newFixedThreadPool(2);
Future<String> base1=es.submit(ms1);
Future<String> base2=es.submit(ms2);
String s1=base1.get();
String s2=base2.get();
System.out.println(s1);
System.out.println(s2);
es.shutdown();
}
}
運行結果:
多線程
3.1 線程安全
如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。程序每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
l 我們通過一個案例,演示線程的安全問題:
電影院要賣票,我們模擬電影院的賣票過程。假設要播放的電影是 “功夫熊貓3”,本次電影的座位共100個(本場電影只能賣100張票)。
我們來模擬電影院的售票窗口,實現多個窗口同時賣 “功夫熊貓3”這場電影票(多個窗口一起賣這100張票)
需要窗口,采用線程對象來模擬;需要票,Runnable接口子類來模擬
3.2 線程同步(線程安全處理Synchronized)
java中提供了線程同步機制,它能夠解決上述的線程安全問題。
線程同步的方式有兩種:
方式1:同步代碼塊
方式2:同步方法
3.2.1 同步代碼塊
同步代碼塊: 在代碼塊聲明上 加上synchronized
synchronized (鎖對象) {
可能會產生線程安全問題的代碼
}
同步代碼塊中的鎖對象可以是任意的對象;但多個線程時,要使用同一個鎖對象才能夠保證線程安全。
模擬售票:
package com.oracle.xianchengchi;
public class MyRunnable implements Runnable {
// 賣電影票
private int ticket = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "張票");
}
}
}
}
}
測試:
package com.oracle.xianchengchi;
public class Test01 {
public static void main(String[] args) {
//明確線程任務
MyRunnable mr=new MyRunnable();
Thread t0=new Thread(mr);
Thread t1=new Thread(mr);
Thread t2=new Thread(mr);
//開啟線程
t0.start();
t1.start();
t2.start();
}
}
運行結果:
3.2.2 同步方法
l 同步方法:在方法聲明上加上synchronized
public synchronized void method(){
可能會產生線程安全問題的代碼
}
同步方法中的鎖對象是 this
使用同步方法,對電影院賣票案例中Ticket類進行如下代碼修改:
package com.oracle.xianchengchi;
public class MyRunnables implements Runnable {
// 賣電影票
private int ticket = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
sale();
}
}
public synchronized void sale() {
if (ticket > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "張票");
}
}
}
package com.oracle.xianchengchi;
public class Test02 {
public static void main(String[] args) {
//明確線程任務
MyRunnables mr=new MyRunnables();
Thread t0=new Thread(mr);
Thread t1=new Thread(mr);
Thread t2=new Thread(mr);
//開啟線程
t0.start();
t1.start();
t2.start();
}
}
運行結果:
3.3 Lock接口
查閱API,查閱Lock接口描述,Lock
實現提供了比使用 synchronized
方法和語句可獲得的更廣泛的鎖定操作。
l Lock接口中的常用方法
Lock提供了一個更加面對對象的鎖,在該鎖中提供了更多的操作鎖的功能。
我們使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,對電影院賣票案例中Ticket類進行如下代碼修改:
package com.oracle.xianchengchi;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyRunnable2 implements Runnable {
// 賣電影票
private int ticket = 100;
private Lock lock=new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "張票");
}
lock.unlock();
}
}