多線程
線程組
線程池
匿名內部類
定時器
1.多線程
a.JDK5以後的針對線程的鎖定操作和釋放操作
使用同步機制解決了線程的安全問題,但是我們並沒有看到具體的鎖對象是誰,JDK5以後java提供了接口Lock裏面又提供了一些方法:Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作由於該Lock接口不能實例化,提供了子實現類:ReentrantLockpublic void lock() 獲取鎖public void unlock() 釋放鎖
b.死鎖
i.雖然使用Lock鎖定操作或者是同步鎖synchronized來解決線程安全的問題,線程安全解決了,線程安全的弊端:
1)效率低2)如果出現了同步嵌套,就容易產生死鎖問題
ii.死鎖問題的產生:
兩個或兩個以上的線程,搶佔CPU的執行權,然後出現了互相等待的情況:使用線程間的通信問題解決!
iii.死鎖問題及其代碼
是指兩個或者兩個以上的線程在執行的過程中,因爭奪資源產生的一種互相等待現象
c.線程間通信問題:
不同種類的線程間針對同一個資源的操作。分析:當前的資源情況:
Student: 共同的資源setThread:設置學生數據(生成者)getThread:獲取學生數據(消費者)StudentDemo(測試類)
問題1:
按照生產消費模式:分別進行產生數據和消費數據,通過測試打印出來:
null---0
問題2:
爲了數據的效果好,加入循環和判斷,給出不同的值,這個時候產生新的問題。a.同一個數據出現多次
原因: CPU的一點點時間片的執行權,就足夠執行很多次。
b.年齡和姓名不匹配
原因:線程運行的隨機性
解決方法:
加鎖
注意:
i.不同種類的線程都要加鎖ii.不同種類的線程必須加同一把鎖
問題3:線程安全問題解決了,但是會存在如下問題
i.如果消費者先搶到了CPU的執行權,就會去消費數據,但是現在的數據是默認值。沒有意義應該等待數據有意義再去消費。ii.如果生產者搶到CPU的執行權,就會去生產數據,但是它生產完數據後依然擁有執行權,那麼它繼續生產數據。這樣就有問題,應該等消費者把數據消費完,再生產。
正常思路:
1)生產者:先看是否有數據,有就等待,沒有就生產,生產完後通知消費者來消費數據。2)消費者:先看是否有數據,有就消費,沒有就等待,通知生產者生產數據。
爲了處理這樣的問題,Java提供了一種機制:等待喚醒機制。
d.等待喚醒機制
i.Object類中提供了一些方法:
wait() 線程等待public final void notify() 喚醒正在等待的單個線程public final void notifyAll() 喚醒所有線程
ii.(面試題)這幾個方法都是線程有關的方法,爲什麼把這個方法不定一在Thread類裏面?
剛纔這個案例,使用的鎖對象進行調用,鎖對象可以是任意對象.而Object本身就是代表所有的類根類:代表所有對象.
e.(問題)wait(),notify(),notifyAll(),用來操作線程爲什麼定義在了Object類中?
i.這些方法存在與同步中。ii.使用這些方法時必須要標識所屬的同步的鎖。iii.鎖可以是任意對象,所以任意對象調用的方法一定定義Object類中。
f.線程的狀態轉移圖:
死鎖:
public class MyLock {
public static final Object objectA = new Object();
public static final Object objectB = new Object();
}
public class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag) {
this.flag=flag;
}
@Override
public void run() {
if(flag){
synchronized (MyLock.objectA) {
System.out.println("if ObjectA");
synchronized (MyLock.objectB){
System.out.println("if ObjectB");
}
}
}
else{
synchronized (MyLock.objectB){
System.out.println("else ObjectB");
synchronized (MyLock.objectA){
System.out.println("else Object");
}
}
}
}
}
public class DieLockDemo {
public static void main(String[] args) {
//創建線程對象
DieLock d1 = new DieLock(true);
DieLock d2 = new DieLock(false);
//啓動線程
d1.start();
d2.start();
}
}
等待喚醒機制:
//生產者
public class SetThread implements Runnable {
private Student s ;
private int x = 0;
public SetThread(Student s ){
this.s=s;
}
@Override
public void run() {
while(true){
synchronized (s){
if(s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(x%2==0){
s.name="Ash";
s.age=24;
}else{
s.name="Ying";
s.age=22;
}
x++;
//修改標記
s.flag=true;
//喚醒線程
s.notify();
}
}
}
}
//消費者
public class GetThread implements Runnable {
private Student s ;
public GetThread(Student s ){
this.s=s;
}
@Override
public void run() {
while(true){
synchronized(s){
if(!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name+"---"+s.age);
//修改標記
s.flag=false;
//喚醒線程
s.notify();
}
}
}
}
public class Student {
String name;
int age;
boolean flag;
}
public class StudentDemo {
public static void main(String[] args) {
//創建共同資源對象
Student s = new Student();
//創建每個線程對象,針對同一個資源進行操作
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
//創建Thread類對象
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
//分別啓動線程
t1.start();
t2.start();
}
}
2.線程組
a.概述
Java中使用ThreadGroup來表示線程組,它可以對一批線程進行分類管理,Java允許程序直接對線程組進行控制。
b.常用方法:
public ThreadGroup(String name) 構造一個新線程組。public Thread(ThreadGroup group,Runnable target, String name)public final void setDaemon(boolean daemon) 設置線程組是否是一個守護線程public final ThreadGroup getThreadGroup() 返回該線程所在的線程組。默認情況下,所有的線程都屬於主線程組。public final String getName() 返回此線程組的名稱。
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class ThreadGroupDemo {
public static void main(String[] args) {
//fun1();
fun2();
}
private static void fun2() {
//不知道t1,t2線程是屬於哪一個線程組的?
//如何該線程所在線程組的名稱
MyRunnable my = new MyRunnable() ;
Thread t1 = new Thread(my, "張三") ;
Thread t2 = new Thread(my, "李四") ;
//Thread類中提供了另外一個方法:
// public final ThreadGroup getThreadGroup():返回該線程所在的線程組
//使用線程對象調用
ThreadGroup tg1 = t1.getThreadGroup() ;
ThreadGroup tg2 = t2.getThreadGroup() ;
//public final String getName()返回此線程組的名稱。
String name1 = tg1.getName() ;
String name2 = tg2.getName() ;
//輸出這兩個線程所在的線程組名稱
System.out.println(name1);
System.out.println(name2);
//通過測試:發現線程默認情況線程組屬於main線程:
System.out.println(Thread.currentThread().getThreadGroup().getName());
}
private static void fun1() {
//如何設置線程組名稱?
// public ThreadGroup(String name)構造一個新線程組。
ThreadGroup tg = new ThreadGroup("這是一個新的線程組") ;
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my ,"Ash");
Thread t2 = new Thread(my ,"Glaz");
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
String name1 = tg1.getName() ;
String name2 = tg1.getName() ;
System.out.println(name1);
System.out.println(name2);
tg.setDaemon(true);
}
}
3.線程池
a.概述:
程序啓動一個新線程成本是比較高的,因爲它涉及到要與操作系統進行交互。而使用線程池可以很好的提高性能,尤其是當程序中要創建大量生存期很短的線程時,更應該考慮使用線程池。
b.使用線程池可以解決很多問題:
1)如何創建線程池對象:
Executors工廠:專門用來創建線程池的:提供了一個方法public static ExecutorService newFixedThreadPool(int nThreads)
2)這些方法的返回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程。它提供瞭如下方法
Future<?> submit(Runnable task) Runnable接口作爲一個參數:要該類的子實現類對象<T> Future<T> submit(Callable<T> task)
3)線程池可以結束嗎?
void shutdown()
c.多線程程序實現方法三:
步驟:1)自定義一個類,實現Callable接口2)實現裏面的call方法3)主線程中創建線程對象,4)用線程池對象提交任務(例如:MyCallable這個任務實現0--100之間的循環)5)提交後結束線程池
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorsDemo {
public static void main(String[] args) {
//創建線程池對象
ExecutorService pool = Executors.newFixedThreadPool(3);
//使用ExecutorsService接口中有要給方法:
//Future<?> submit(Runnable task):Runnable接口作爲一個參數:要該類的子實現類對象
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//結束線程池
pool.shutdown();
}
}
4.匿名內部類
匿名內部類的方式實現多線程程序:
new 類名或者接口名{(
重寫/實現方法;
}
本質:繼承該類或者或者實現該接口的子類對象!
5.定時器
a.概述:
定時器是一個應用十分廣泛的線程工具,可用於調度多個定時任務以後臺線程的方式執行。在Java中,可以通過Timer和TimerTask類來實現定義調度的功能
b.構造方法:a
public Timer() 創建一個新計時器。public void schedule(TimerTask task, Date time) 安排在指定的時間執行指定的任務
參數1:task - 所要安排的任務參數2:time - 執行該任務的時間毫秒值
c.其他方法
public boolean cancel() 取消此計時器任務
public void schedule(TimerTask task, Date firstTime,long period) 每隔多少毫秒進行重複性的 任務
import java.util.Timer;
import java.util.TimerTask;
public class Demo01_無參調用 {
public static void main(String[] args) {
Timer t = new Timer();
t.schedule(new Fun(), 3000);
}
}
class Fun extends TimerTask{
public Fun(){
}
@Override
public void run() {
System.out.println("boom!!!");
}
}
import java.util.Timer;
import java.util.TimerTask;
public class Demo02_有參調用 {
public static void main(String[] args) {
//創建定時器對象
Timer t = new Timer();
t.schedule(new Hun(t), 3000);
}
}
class Hun extends TimerTask{
private Timer t;
public Hun(){
}
public Hun(Timer t){
this.t=t;
}
@Override
public void run() {
System.out.println("boom!!!");
t.cancel();
}
}
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
//在指定的時間刪除我們的指定目錄(你可以指定c盤,但是我不建議,我使用項目路徑下的demo)
public class test {
public static void main(String[] args) {
File f = new File("C:\\Users\\Administrator\\Desktop\\Demo");
Timer t = new Timer();
t.schedule(new Fun3(t,f), 3000);
}
}
class Fun3 extends TimerTask{
private Timer t ;
private File f;
public Fun3(Timer t, File f) {
this.t=t;
this.f=f;
}
@Override
public void run() {
hun(f);
t.cancel();
}
public void hun(File f){
File[] fa = f.listFiles();
//非空判斷
if(fa!=null){
for (File file : fa) {
if(file.isDirectory()){
hun(file);
}else{
System.out.println(file.getName()+":"+file.delete());
}
}
System.out.println(f.getName()+":"+f.delete());
}
}
}