共计 11161 个字符,预计需要花费 28 分钟才能阅读完成。
创立线程和罕用办法
过程与线程的概念
- 过程
过程是程序执行时的一个实例。程序运行时零碎就会创立一个过程,并为它分配资源,而后把该过程放入过程就绪队列,过程调度器选中它的时候就会为它调配 CPU 工夫,程序开始真正运行。
- 线程
线程是一条执行门路,是程序执行时的最小单位,它是过程的一个执行流,是 CPU 调度和分派的根本单位,一个过程能够由很多个线程组成,线程间共享过程的所有资源,每个线程有本人的堆栈和局部变量。线程由 CPU 独立调度执行,在多 CPU 环境下就容许多个线程同时 (并发) 运行。
一个正在运行的软件 (如迅雷) 就是一个过程,一个过程能够同时运行多个工作(迅雷软件能够同时下载多个文件,每个下载工作就是一个线程), 能够简略的认为过程是线程的汇合。
为什么要应用多线程
多线程能够进步程序的效率。
理论生存案例:村长要求气冲冲在一个小时内打 100 桶水,能够气冲冲一个小时只能打 25 桶水,如果这样就须要 4 个小时能力实现工作,为了在一个小时可能实现,气冲冲就请美洋洋、懒洋洋、沸洋洋,来帮忙,这样 4 只羊同时干活,在一小时内实现了工作。本来用 4 个小时实现的工作当初只须要 1 个小时就实现了,如果把每只羊看做一个线程,多只羊即多线程能够进步程序的效率。
并发编程的概念
- 程序编程
public class Main {
// 程序编程:当吃饭吃不完的时候,是不能喝酒的,只能吃完晚能力喝酒
public static void main(String[] args) throws Exception {
// 先吃饭再喝酒
eat();
drink();}
private static void eat() throws Exception {System.out.println("开始吃饭...\t" + new Date());
Thread.sleep(5000);
System.out.println("完结吃饭...\t" + new Date());
}
private static void drink() throws Exception {System.out.println("开始喝酒...\t" + new Date());
Thread.sleep(5000);
System.out.println("完结喝酒...\t" + new Date());
}
}
- 并发编程
public class Main {public static void main(String[] args) {
// 一边吃饭一边喝酒
new EatThread().start();
new DrinkThread().start();
}
}
class EatThread extends Thread{
@Override
public void run() {System.out.println("开始吃饭?...\t" + new Date());
try {Thread.sleep(5000);
} catch (InterruptedException e) {e.printStackTrace();
}
System.out.println("完结吃饭?...\t" + new Date());
}
}
class DrinkThread extends Thread {
@Override
public void run() {System.out.println("开始喝酒?️...\t" + new Date());
try {Thread.sleep(5000);
} catch (InterruptedException e) {e.printStackTrace();
}
System.out.println("完结喝酒?...\t" + new Date());
}
}
并发编程,一边吃饭一边喝酒总共用时 5 秒,比程序编程更快,因为并发编程能够同时运行,而不用等后面的代码运行完之后才容许前面的代码。
同一个时刻一个 CPU 只能做一件事件,即同一时刻只能一个线程中的局部代码,如果有两个线程,Thread- 0 和 Thread-1,刚开始 CPU 说 Thread- 0 你先执行,给你 3 毫秒工夫,Thread- 0 执行了 3 毫秒工夫,然而没有执行完,此时 CPU 会暂停 Thread- 0 执行并记录 Thread- 0 执行到哪行代码了,过后的变量的值是多少,而后 CPU 说 Thread- 1 你能够执行了,给你 2 毫秒的工夫,Thread- 1 执行了 2 毫秒也没执行完,此时 CPU 会暂停 Thread- 1 执行并记录 Thread- 1 执行到哪行代码了,过后的变量的值是多少,此时 CPU 又说 Thread- 0 又该你,这次我给你 5 毫秒工夫,去执行吧,此时 CPU 就找出上次 Thread- 0 线程执行到哪行代码了,过后的变量值是多少,而后接着上次继续执行,后果用了 2 毫秒就 Thread- 0 就执行完了,就终止了,而后 CPU 说 Thread- 1 又轮到你,这次给你 4 毫秒,同样 CPU 也会先找出上次 Thread- 1 线程执行到哪行代码了,过后的变量值是多少,而后接着上次持续开始执行,后果 Thread- 1 在 4 毫秒内也执行完结了,Thread- 1 也完结了终止了。CPU 在来回扭转线程的执行机会称之为线程上下文切换。
线程的状态
创立(new)状态: 筹备好了一个多线程的对象,即执行了 new Thread(); 创立实现后就须要为线程分配内存
就绪(runnable)状态: 调用了 start()办法, 期待 CPU 进行调度
运行(running)状态: 执行 run()办法
阻塞(blocked)状态: 临时进行执行线程,将线程挂起 (sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程应用
死亡(terminated)状态: 线程销毁 (失常执行结束、产生异样或者被打断 interrupt() 都会导致线程终止)
创立线程的形式
1. 子类继承 Thread 类,重写 Thread 类 run 办法,new Thread 子类创立线程对象,调用线程对象的 start() 办法。
public class Main {public static void main(String[] args) {new MyThread().start();}
}
class MyThread extends Thread {
@Override
public void run() {System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
}
}
2. 实现类实现 Runnable 接口,重写 Runnable 接口 run()办法,实例化实现类并将此实例作为 Thread 类的 target 来创立线程对象,调用线程对象的 start()办法。
public class Main {public static void main(String[] args) {
// 将 Runnable 实现类作为 Thread 的结构参数传递到 Thread 类中,而后启动 Thread 类
MyRunnable runnable = new MyRunnable();
new Thread(runnable).start();}
}
class MyRunnable implements Runnable {
@Override
public void run() {System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
}
}
3. 实现类实现 Callable 接口,重写 Callable 带有返回值的 call()办法,用 FutureTask 包装 Callable 实现类的实例,将 FutureTask 的实例作为 Thread 类的 target 来创立线程对象,调用线程对象的 start()办法
public class Main {public static void main(String[] args) throws Exception {
// 将 Callable 包装成 FutureTask,FutureTask 也是一种 Runnable
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
// get 办法会阻塞调用的线程
Integer sum = futureTask.get();
System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + "\tstarting...");
int sum = 0;
for (int i = 0; i <= 100000; i++) {sum += i;}
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + "\tover...");
return sum;
}
}
三种形式比拟:
1.Thread: 继承形式, 不倡议应用, 因为 Java 是单继承的,继承了 Thread 就没方法继承其它类了,不够灵便
2.Runnable: 实现接口,比 Thread 类更加灵便,没有单继承的限度
3.Callable: Thread 和 Runnable 都是重写的 run() 办法并且没有返回值,Callable 是重写的 call()办法并且有返回值并能够借助 FutureTask 类来判断线程是否曾经执行结束或者勾销线程执行
4. 当线程不须要返回值时应用 Runnable,须要返回值时就应用 Callable
罕用办法
1.Thread.currentThread()
public static void main(String[] args) {Thread thread = Thread.currentThread();
// 线程名称
String name = thread.getName();
// 线程 id
long id = thread.getId();
// 线程曾经启动且尚未终止
// 线程处于正在运行或筹备开始运行的状态,就认为线程是“存活”的
boolean alive = thread.isAlive();
// 线程优先级
int priority = thread.getPriority();
// 是否守护线程
boolean daemon = thread.isDaemon();
// Thread[name=main,id=1,alive=true,priority=5,daemon=false]
System.out.println("Thread[name=" + name + ",id=" + id + ",alive=" + alive + ",priority=" + priority + ",daemon=" + daemon + "]");
}
2.sleep() 与 interrupt()
sleep(): 睡眠指定工夫,即让程序暂停指定工夫运行,工夫到了会继续执行代码,如果工夫未到就要醒须要应用 interrupt()来随时唤醒
interrupt(): 唤醒正在睡眠的程序,调用 interrupt() 办法,会使得 sleep()办法抛出 InterruptedException 异样,当 sleep()办法抛出异样就中断了 sleep 的办法,从而让程序持续运行上来
public static void main(String[] args) throws Exception {Thread thread0 = new Thread(()-> {
try {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t 太困了,让我睡 10 秒,两头有事叫我,zZZ。。。");
Thread.sleep(10000);
} catch (InterruptedException e) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t 被叫醒了,又要持续干活了");
}
});
thread0.start();
// 这里睡眠只是为了保障先让下面的那个线程先执行
Thread.sleep(2000);
new Thread(()-> {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t 醒醒,醒醒,别睡了,起来干活了!!!");
// 无需获取锁就能够调用 interrupt
thread0.interrupt();}).start();}
3.wait() 与 notify()
wait、notify 和 notifyAll 办法是 Object 类的 final 办法,这些办法不能被子类重写。因而在程序中能够通过 this 或者 super 来调 this.wait(), super.wait()。
wait(): 导致线程进入期待阻塞状态,会始终期待直到它被其余线程通过 notify()或者 notifyAll 唤醒。该办法只能在同步办法或同步块外部调用。如果以后线程不是锁的持有者,该办法抛出一个 IllegalMonitorStateException 异样。wait(long timeout): 工夫到了主动执行,相似于 sleep(long millis)
notify(): 该办法只能在同步办法或同步块外部调用,随机抉择一个 (留神:只会告诉一个) 在该对象上调用 wait 办法的线程,解除其阻塞状态
notifyAll(): 唤醒所有的 wait 对象
wait()是让程序暂停执行,线程进入以后实例的期待队列,这个队列属于该实例对象,所以调用 notify 也必须应用该对象来调用,不能应用别的对象来调用。调用 wait 和 notify 必须应用同一个对象来调用。
留神:
- Object.wait()和 Object.notify()和 Object.notifyall()必须写在 synchronized 办法外部或者 synchronized 块外部
- 让哪个对象期待 wait 就去告诉 notify 哪个对象,不要让 A 对象期待,后果却去告诉 B 对象,要操作同一个对象
public class WaitNotifyTest {public static void main(String[] args) throws Exception {WaitNotifyTest waitNotifyTest = new WaitNotifyTest();
new Thread(() -> {
try {waitNotifyTest.printFile();
} catch (InterruptedException e) {e.printStackTrace();
}
}).start();
new Thread(() -> {
try {waitNotifyTest.printFile();
} catch (InterruptedException e) {e.printStackTrace();
}
}).start();
new Thread(() -> {
try {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t 睡觉 1 秒中,目标是让下面的线程先执行,即先执行 wait()");
Thread.sleep(1000);
} catch (InterruptedException e) {e.printStackTrace();
}
waitNotifyTest.notifyPrint();}).start();}
private synchronized void printFile() throws InterruptedException {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t 期待打印文件...");
this.wait();
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t 打印完结。。。");
}
private synchronized void notifyPrint() {this.notify();
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t 告诉实现...");
}
}
notifyAll()
4.sleep() 与 wait()
Thread.sleep(long millis): 睡眠时不会开释锁
package com.example.demo;
import java.util.Date;
public class Main {public static void main(String[] args) throws InterruptedException {Object lock = new Object();
new Thread(() -> {synchronized (lock) {for (int i = 0; i < 5; i++) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
try {Thread.sleep(1000); } catch (InterruptedException e) {}}
}
}).start();
Thread.sleep(1000);
new Thread(() -> {synchronized (lock) {for (int i = 0; i < 5; i++) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
}
}
}).start();}
}
因 main 办法中 Thread.sleep(1000)所以下面的线程 Thread- 0 先被执行,当循环第一次时就会 Thread.sleep(1000)睡眠,因为 sleep 并不会开释锁,所以 Thread- 1 得不到执行的机会,所以直到 Thread- 0 执行结束开释锁对象 lock,Thread- 1 能力拿到锁,而后执行 Thread-1;
object.wait(long timeout): 会开释锁
public class Main {public static void main(String[] args) throws InterruptedException {Object lock = new Object();
new Thread(() -> {synchronized (object) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t 期待打印文件...");
try {object.wait(5000);
} catch (InterruptedException e) {e.printStackTrace();
}
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t 打印完结。。。");
}
}).start();
// 先下面的线程先执行
Thread.sleep(1000);
new Thread(() -> {synchronized (object) {for (int i = 0; i < 5; i++) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
}
}
}).start();}
}
sleep 与 wait 的区别
sleep 在 Thread 类中,wait 在 Object 类中
sleep 不会开释锁,wait 会开释锁
sleep 应用 interrupt() 来唤醒,wait 须要 notify 或者 notifyAll 来告诉
5.join()
public class JoinTest {public static void main(String[] args) {new Thread(new ParentRunnable()).start();}
}
class ParentRunnable implements Runnable {
@Override
public void run() {
// 线程处于 new 状态
Thread childThread = new Thread(new ChildRunable());
// 线程处于 runnable 就绪状态
childThread.start();
try {
// 当调用 join 时,parent 会期待 child 执行结束后再持续运行
// 将某个线程退出到以后线程
childThread.join();} catch (InterruptedException e) {e.printStackTrace();
}
for (int i = 0; i < 5; i++) {System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "父线程 running");
}
}
}
class ChildRunable implements Runnable {
@Override
public void run() {for (int i = 0; i < 5; i++) {try { Thread.sleep(1000); } catch (InterruptedException e) {}
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "子线程 running");
}
}
}
程序进入主线程,运行 Parent 对应的线程,Parent 的线程代码分两段,一段是启动一个子线程,一段是 Parent 线程的线程体代码,首先会将 Child 线程退出到 Parent 线程,join()办法会调用 join(0)办法 (join() 办法是一般办法并没有加锁,join(0)会加锁),join(0)会执行 while(isAlive()) {wait(0);} 循环判断线程是否处于活动状态,如果是持续 wait(0)晓得 isAlive=false 完结掉 join(0), 从而完结掉 join(), 最初回到 Parent 线程体中继续执行其它代码。
6.yield()
交出 CPU 的执行工夫,不会开释锁,让线程进入就绪状态,期待从新获取 CPU 执行工夫,yield 就像一个坏蛋似的,当 CPU 轮到它了,它却说我先不急,先给其余线程执行吧, 此办法很少被应用到
public class Main {public static void main(String[] args) {new Thread(new Runnable() {
int sum = 0;
@Override
public void run() {long beginTime=System.currentTimeMillis();
for (int i = 0; i < 99999; i++) {
sum += 1;
// 去掉该行执行用 2 毫秒,加上 271 毫秒
Thread.yield();}
long endTime=System.currentTimeMillis();
System.out.println("用时:"+ (endTime - beginTime) + "毫秒!");
}
}).start();}
}
sleep(long millis) 与 yeid()
sleep(long millis): 须要指定具体睡眠的工夫,不会开释锁,睡眠期间 CPU 会执行其它线程,睡眠工夫到会立即执行
yeid(): 交出 CPU 的执行权,不会开释锁,和 sleep 不同的时当再次获取到 CPU 的执行,不能确定是什么时候,而 sleep 是能确定什么时候再次执行。两者的区别就是 sleep 后再次执行的工夫能确定,而 yeid 是不能确定的
yield 会把 CPU 的执行权交出去,所以能够用 yield 来控制线程的执行速度,当一个线程执行的比拟快,此时想让它执行的略微慢一些能够应用该办法,想让线程变慢能够应用 sleep 和 wait, 然而这两个办法都须要指定具体工夫,而 yield 不须要指定具体工夫,让 CPU 决定什么时候能再次被执行,当放弃到下次再次被执行的两头工夫就是间歇期待的工夫
7.setDaemon(boolean on)
线程分两种:
1. 用户线程:如果主线程 main 进行掉,不会影响用户线程,用户线程能够持续运行。
2. 守护线程:如果主线程死亡,守护线程如果没有执行结束也要跟着一块死(就像皇上死了,带刀侍卫也要一块死),GC 垃圾回收线程就是守护线程
public class Main {public static void main(String[] args) {Thread thread = new Thread() {
@Override
public void run() {IntStream.range(0, 5).forEach(i -> {
try {Thread.sleep(1000);
} catch (InterruptedException e) {e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\ti=" + i);
});
}
};
thread.start();
for (int i = 0; i < 2; i++) {System.out.println(Thread.currentThread().getName() + "\ti=" + i);
}
System.out.println("主线程执行完结,子线程依然继续执行,主线程和用户线程的生命周期各自独立。");
}
}
public class Main {public static void main(String[] args) {Thread thread = new Thread() {
@Override
public void run() {IntStream.range(0, 5).forEach(i -> {
try {Thread.sleep(1000);
} catch (InterruptedException e) {e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\ti=" + i);
});
}
};
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 2; i++) {System.out.println(Thread.currentThread().getName() + "\ti=" + i);
}
System.out.println("主线程死亡,子线程也要陪着一块死!");
}
}
参考:
多线程(一):创立线程和线程的罕用办法