一:線程概述
1,線程和進程
進程:正在運行的程序,是系統進行資源分配和調用的獨立單位。 每一個進程都有它自己的內存空間和系統資源。
線程:進程的執行單元,執行路徑。
2,多線程和多進程的意義
多進程:提高CPU的使用率
多線程: 提高應用程序的使用率
3,線程的生命週期
二:實現多線程
實現方式一;編寫一個類繼承Thread類,,重寫run()方法
public class MyThread extends Thread {
@Override
public void run() {
for (int x = 0; x < 200; x++) {
System.out.println(x);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.start();
my2.start();
}
實現方式二:編寫一個類實現Runnable接口;重寫run()方法;創建MyRunnable對象;創建Thread類對象,將MyRunnable對象作爲形參進行傳遞
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable mr1 = new MyRunnable();
Thread t1 = new Thread (mr1);
Thread t2 = new Thread (mr1);
Thread t1 = new Thread (mr1,"線程一");
Thread t2 = new Thread (mr1,"線程二");
t1.start();
t2.start();
}
}
class MyRunnable implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+":"+"yangjinbiao");
}
}
實現方式三: 實現Callable 接口和使用FutureTask接收返回值
Callable方式依賴線程池,線程的的submit方法可以接收一個Callable接口的實現對象,同時放回一個FutureTask,如果Callable的call方法出錯,將會拋出一個異常。
首先查看API
Interface Callable<V>
發現這個接口有個泛型,在查看接口內的方法:
V call() 計算一個結果,如果不能這樣做,就會拋出一個異常。
發現接口方法的返回值有個泛型值,可以得出該接口的泛型是call()的放回值。在通過API查看FutureTask類。
Class FutureTask<V>
這個類泛型也是接收Callable接口的call()方法的放回值,通過查看FutureTask類的方法。
V get() 等待計算完成,然後檢索其結果。
protected void set(V v) 將此未來的結果設置爲給定值,除非此未來已被設置或已被取消。
protected void setException(Throwable t)
導致這個未來報告一個ExecutionException與給定的可拋棄的原因,除非這個未來已經被設置或被取消。
第一個方法表示獲取call()方法的放回值存貯在FutureTask的值,下面是實現代碼。
public class CallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 創建線程池對象
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以執行Runnable對象或者Callable對象代表的線程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
// 結束
pool.shutdown();
}
}
public class MyCallable implements Callable<Integer> {
private int number;
public MyCallable(int number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int x = 1; x <= number; x++) {
sum += x;
}
return sum;
}
}
三:與線程相關的注意事項
1,run和start的區別:
run():僅僅是封裝被線程執行的代碼,直接調用是普通方法
start():首先啓動了線程,然後再由jvm去調用該線程的run()方法。
2,爲何需要重寫run方法
不是類中的所有代碼都需要被線程執行的。而這個時候,爲了區分哪些代碼能夠被線程執行,java提供了Thread 類中的run()用來包含那些被線程執行的代碼。
3,多次啓動線程會怎麼樣?
出現異常:在第二次調用start()方法的時候,線程可能處於終止或者其它(非NEW)狀態,但是不論如何,都是不可以再次啓動的。
四:與線程相關的方法
1,線程名的獲取和設置
設置名稱: public final String setName()或者構造方法來設置線程的名稱。
獲取名稱: public final String getName():獲取線程的名稱。public static Thread currentThread():返回當前正在執行的線程對象
2,線程優先級:線程優先級默認爲5,範圍爲1至10;
通過: public final int getPriority():獲取優先級
通過:public final void setPriority(int newPriority):設置優先級
五:線程控制
1,線程休眠: public static void sleep(long millis)
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep();
ThreadSleep ts2 = new ThreadSleep();
ThreadSleep ts3 = new ThreadSleep();
ts1.setName("林青霞");
ts2.setName("林志玲");
ts3.setName("林志穎");
ts1.start();
ts2.start();
ts3.start();
}
}
public class ThreadSleep extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x + ",日期:" + new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2,加入線程:public final void join():等待該線程終止。
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
ThreadJoin tj3 = new ThreadJoin();
tj1.setName("李淵");
tj2.setName("李世民");
tj3.setName("李元霸");
tj1.start();
try {
tj1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
tj2.start();
tj3.start();
}}
public class ThreadJoin extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}}}
3,禮讓線程:public static void yield():暫停當前正在執行的線程對象,並執行其他線程。 讓多個線程的執行更和諧,但是不能靠它保證一人一次。
public class ThreadYieldDemo {
public static void main(String[] args) {
ThreadYield ty1 = new ThreadYield();
ThreadYield ty2 = new ThreadYield();
ty1.setName("林青霞");
ty2.setName("劉意");
ty1.start();
ty2.start();
}}
public class ThreadYield extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
Thread.yield();
}}}
4, 守護線程: public final void setDaemon(boolean on):將該線程標記爲守護線程或用戶線程。當正在運行的線程都是守護線程時,Java 虛擬機退出。 該方法必須在啓動線程前調用。
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();
td1.setName("關羽");
td2.setName("張飛");
// 設置收穫線程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
Thread.currentThread().setName("劉備");
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}}}
public class ThreadDaemon extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}}}
5,線程中斷:
public final void stop():讓線程停止,過時了,但是還可以使用。
public void interrupt():中斷線程。 把線程的狀態終止,並拋出一個InterruptedException。
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();
try {
Thread.sleep(3000);
// ts.stop();
ts.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}}
public class ThreadStop extends Thread {
@Override
public void run() {
System.out.println("開始執行:" + new Date());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("線程被終止了");
}
System.out.println("結束執行:" + new Date());
}
}
六:線程組
線程組是用於對功能相同的進行管理,可以對線程組內的全部線程進行統一管理。實現方式如下所示,只要是通過線程的構造方法實現的
查看API
java.lang.ThreadGroup
構造方法
ThreadGroup(String name) 構造一個新的線程組。
ThreadGroup(ThreadGroup parent, String name) 創建一個新的線程組。
主要方法:
void destroy() 銷燬此線程組及其所有子組。
int getMaxPriority() 返回此線程組的最大優先級。
String getName() 返回此線程組的名稱。
void setMaxPriority(int pri) 設置組的最大優先級。
代碼代碼如下:
public class ThreadGroupDemo {
public static void main(String[] args) {
method2();
}
private static void method2() {
// ThreadGroup(String name)
ThreadGroup tg = new ThreadGroup("這是一個新的組");
MyRunnable my = new MyRunnable();
// Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, my, "林青霞");
Thread t2 = new Thread(tg, my, "劉意");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通過組名稱設置後臺線程,表示該組的線程都是後臺線程
tg.setDaemon(true);
}
}
七:線程池
1,線程池概述
A,爲什麼使用線程池:因爲每次啓動一個新的線程,系統都會給它開闢一個新的資源空間,會耗費資源,特別是在需要開闢大量的線程的時候,更是如此,所以使用線程池。
B:線程池:系統會開闢一個空間,專門用於存在被執行之後的線程,等到下次需要調用的時候,無需重新創建線程就可以從線程池內建對象拿出來使用。
2,實現
JDK5新增了一個Executors工廠類來產生線程池,有如下幾個方法
public static ExecutorService newCachedThreadPool() :創建具有緩存功能的線程
public static ExecutorService newFixedThreadPool(int nThreads):創建存貯多個線程的線程池
public static ExecutorService newSingleThreadExecutor():創建存貯單個線程的線程池
這些方法的返回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程。它提供瞭如下方法
Future<?> submit(Runnable task) <T>
Future<T> submit(Callable<T> task)
實現代碼:
public class ExecutorsDemo {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以執行Runnable對象或者Callable對象代表的線程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//結束線程池
pool.shutdown();
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}
3,實現原理(來源:瘋狂哈丘 )
就是一個線程集合workerSet和一個阻塞隊列workQueue。當用戶向線程池提交一個任務(也就是線程)時,線程池會先將任務放入workQueue中。workerSet中的線程會不斷的從workQueue中獲取線程然後執行。當workQueue中沒有任務的時候,worker就會阻塞,直到隊列中有任務了就取出來繼續執行。
八,線程安全
1,爲什麼會導致線程安全問題
多線程環境,共享數據,對共享數據進行操作。
2,解決線程安全問題
A: 同步代碼塊
synchronized(對象){需要同步的代碼;}
注意:可以是任意鎖,但是必須使用的是同一個對象。同步方法鎖對象爲;this;靜態同步方法鎖對象:類的字節碼文件對象。
B:Lock鎖(接口):Lock void lock():加鎖; void unlock() :釋放鎖;ReentrantLock(子實現類);
如果被加鎖的代碼塊出現異常,就不會在執行 unlock(),就無法釋放鎖,這時候需要將釋放鎖的操作早finally中,保證出問題還能將鎖釋放。
public class SellTicketDemo {
public static void main(String[] args) {
// 創建資源對象
SellTicket st = new SellTicket();
// 創建三個窗口
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
// 啓動線程
t1.start();
t2.start();
t3.start();
}}
public class SellTicket implements Runnable {
// 定義票
private int tickets = 100;
// 定義鎖對象
private Lock lock = new ReentrantLock();
public void run() {
while (true) {
try {
// 加鎖
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "張票");
}
} finally {
// 釋放鎖
lock.unlock();
}}}}
3,死鎖問題: 是指兩個或者兩個以上的線程在執行的過程中,因爭奪資源產生的一種互相等待現象,例如同步代碼塊的嵌套
public class DieLockDemo {
public static void main(String[] args) {
DieLock dl1 = new DieLock(true);
DieLock dl2 = new DieLock(false);
dl1.start();
dl2.start();
}
}
public class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (MyLock.objA) {
System.out.println("if objA");
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
} else {
synchronized (MyLock.objB) {
System.out.println("else objB");
synchronized (MyLock.objA) {
System.out.println("else objA");
}
}
}
}
}
public class MyLock {
// 創建兩把鎖對象
public static final Object objA = new Object();
public static final Object objB = new Object();
}
這時候,會出現相互等待的問題。
解決方案:生產消費者模式之等待喚醒機制
生產方有資源,就通知並等待消費者來消費;消費者需要消費,就通知生產者生產。
共享的資源:
共同資源
public class Student {
private String name;
private int age;
private boolean flag; // 默認情況是沒有數據,如果是true,說明有數據
public synchronized void set(String name, int age) {
// 如果有數據,就等待
if (this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 設置數據
this.name = name;
this.age = age;
// 修改標記
this.flag = true;
this.notify();
}
public synchronized void get() {
// 如果沒有數據,就等待
if (!this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 獲取數據
System.out.println(this.name + "---" + this.age);
// 修改標記
this.flag = false;
this.notify();
}
}
生產者:
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
public void run() {
while (true) {
if (x % 2 == 0) {
s.set("林青霞", 27);
} else {
s.set("劉意", 30);
}
x++;
}}}
消費者:
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
s.get();
}
}
測試:
public class StudentDemo {
public static void main(String[] args) {
//創建資源
Student s = new Student();
//設置和獲取的類
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//線程類
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//啓動線程
t1.start();
t2.start();
}
}