线程相关概念
线程基本使用
第一种方法:继承 Thread 类
public class Test {
public static void main(String[] args) throws Exception {
/*
* 当运行 Java 程序时,相当于启动了一个进程
* 而这个进程首先会创建一个主线程(即进入 main 方法,也叫 main 进程)
* Cat 对象继承了 Thread ,可以当做线程使用
* cat.start(); 又启动了一个新的线程,即主线程中又开启了一个子线程
* 此时主线程和子线程交替进行,这就是并发(单核 CPU)
* 注意点:
* 当主线程执行完毕后,如果子线程没有执行完,则子线程会继续执行
* 而进程会等所有的线程执行完后才会销毁,即主线程销毁时子线程有可能还在运行
* 子线程也可以再启动线程
* start() 方法的源码:
* public synchronized void start(){
start0();
* }
* // start0 是一个本地方法,由 JVM 调用,底层是 c/c++ 实现
* // 所以真正实现多线程的是 start0() ,而不是 run()
* private native void start0();
*/
// 创建 Cat 对象,当做线程使用
Cat cat = new Cat();
/*
* 如果直接调用 cat.run(); 实际上执行的还是 main 线程,不会开启一个新的线程
* 此时程序将会阻塞在 run 方法处,不会往下执行代码
* 即将会把 cat.run(); 方法执行完毕后才会继续往下执行
*/
cat.start(); // 启动线程 -> 最终会执行 cat.run()
// 当 main 线程启动一个子线程 Thread-0 ,主线程不会阻塞,会继续执行
// 这时,主线程和子线程是交替执行的
// Thread.currentThread().getName() 返回线程的名字
System.out.println("主线程继续执行" + Thread.currentThread().getName()); // 主线程继续执行main
for (int i = 0; i < 10; i++) {
System.out.println("主线程 i=" + i);
// 让主线程休眠 1s
Thread.sleep(1000);
}
}
}
/*
* 当一个类直接继承了 Thread 类,该类就可以当做线程使用
* 此时重写 run 方法,写上自己的业务逻辑代码
* run 方法是 Thread 类实现了 Runnable 接口里的 run 方法
* run 方法源码如下:
*
* @Override
* public void run() {
if(target != null){
target.run();
}
* }
*/
class Cat extends Thread {
int times = 0;
@Override
public void run() { // 重写 run 方法,写自己的业务逻辑
while (true) {
// 该线程每隔一秒在控制台输出“喵喵,我是小猫咪”
System.out.println(Thread.currentThread().getName() + "喵喵,我是小猫咪" + (++times));
// 让该线程休眠一秒
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
if (times == 80) {
break; // 当 times 到 80,退出 while 循环,退出线程
}
}
}
}
第二种方法:实现 Runnable 接口
public class Test {
public static void main(String[] args) throws Exception {
Dog dog = new Dog();
// dog.start(); // Runnable 接口只有一个 run 方法,不能调用 start 方法
// dog.run(); // 也不能直接调用 run 方法,会造成阻塞,并不会真正开启线程,原因同上个案例
// 应该通过创建 Thread 对象进而调用 start 方法
// 此处是底层使用了一种设计模式(代理模式)实现的
Thread thread = new Thread(dog);
thread.start();
// 简单模拟代理模式
Tiger tiger = new Tiger(); // Tiger 实现了 Runnable 接口,同上面的 Dog 类
// 因为 Tiger 实现了 Runnable 接口,所以可以作为参数传给 ThreadProxy 类
ThreadProxy threadProxy = new ThreadProxy(tiger);
// 方法的调用顺序: start() -> start0() -> run()
// 所以 thread.start(); 同理
threadProxy.start();
}
}
class Dog implements Runnable { // 通过实现 Runnable 接口,开发线程
int count = 0;
@Override
public void run() { // 重写 run 方法,写自己的业务逻辑
while (true) {
// 该线程每隔一秒在控制台输出“喵喵,我是小猫咪”
System.out.println(Thread.currentThread().getName() + " hi~ 小狗汪汪叫" + (++count));
// 让该线程休眠一秒
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
if (count == 10) {
break; // 当 count 到 10,退出 while 循环,退出线程
}
}
}
}
class Animal {
}
class Tiger extends Animal implements Runnable {
@Override
public void run() {
System.out.println("老虎嗷嗷叫");
}
}
// 模拟实现 Runnable 接口开发线程的机制
// 线程代理类,模拟了一个极简的 Thread 类
class ThreadProxy implements Runnable {
private Runnable target = null; // 设置一个 Runnable 类型的属性
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start() {
// start0 方法才是真正实现多线程的方法
// 它其实是由 JVM 调用的,在这里只是简单的模拟
start0();
}
public void start0() {
// 在 start0 方法中调用 run 方法
run();
}
@Override
public void run() {
if (target != null) {
target.run(); // 动态绑定
}
}
}
public class Test {
public static void main(String[] args) {
// 创建两个线程
T1 t1 = new T1();
T2 t2 = new T2();
// 启动线程 t1
Thread thread = new Thread(t1);
thread.start();
// 启动线程 t2
t2.start();
}
}
class T1 implements Runnable {
int num = 0;
@Override
public void run() {
while (true) {
System.out.println("hello world!" + (++num));
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
if (num == 10) {
break;
}
}
}
}
class T2 extends Thread {
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("hi~" + (++count));
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
if (count == 5) {
break;
}
}
}
}
继承 Thread VS 实现 Runnable 接口的区别
对第二点的说明:
如以上 T1 类为例:
// 首先要先创建 T1 类对象
T1 t1 = new T1();
// 启动线程 t1
Thread thread1 = new Thread(t1);
thread1.start();
// 再次启动线程
Thread thread2 = new Thread(t1);
thread2.start();
说明:此时 thread1 和 thread2 都是利用 t1 创建的线程,所以共享 t1 里的 run 方法,达到了共享资源的目的,而且是采用实现接口的形式,打破了单继承的限制
线程终止
public class Test {
public static void main(String[] args) throws Exception {
// 创建线程
T t = new T();
// 启动线程 t
t.start();
// 让主线程休眠 5s
Thread.sleep(5000);
/*
* 如果希望 main 线程去控制 t 线程的终止,必须修改 loop 变量
* 让 t 退出 run 方法,从而终止 t 线程
* 这种方式称为通知方式
*/
t.setLoop(false);
}
}
class T extends Thread {
private int count = 0;
private boolean loop = true; // 控制变量
@Override
public void run() {
while (loop) {
System.out.println("hi~" + (++count));
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
线程常用的方法
public class Test {
public static void main(String[] args) throws Exception {
// 创建线程
T t = new T();
t.setName("zs"); // 设置线程名称
t.setPriority(Thread.MIN_PRIORITY); // 设置线程优先级,此处设置为最低的优先级
System.out.println(t.getName() + "的优先级是" + t.getPriority()); // 获取线程名称
t.start(); // 启动线程 t
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi~ " + i);
}
t.interrupt(); // 中断 t 线程的休眠
}
}
class T extends Thread { // 自定义线程类
int num = 0;
@Override
public void run() {
while (true) {
for (int i = 0; i < 10; i++) {
// Thread.currentThread().getName() 获取当前线程的名字
System.out.println(Thread.currentThread().getName() + " 吃包子~~~" + i);
}
try {
System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
Thread.sleep(10000); // 休眠20秒
num++;
} catch (InterruptedException e) {
// 当线程执行到一个 interrupt 方法时,就会 catch 一个异常
// InterruptedException 捕获到了一个中断异常
// 如果希望让某个线程提前终止休眠,可以调用 interrupt 方法
// 可以在此处编写业务逻辑
System.out.println(Thread.currentThread().getName() + "被 interrupt 了");
}
if (num == 3) {
break;
}
}
}
}
对于 yield 方法,在资源很丰富的情况下,不需要礼让,比如有多个 CPU ,能够同时运行多个线程,不需要让某一个线程礼让另一个线程,因为完全可以同时执行多个线程
public class Test {
public static void main(String[] args) throws Exception {
// 创建线程
T t = new T();
t.start(); // 启动线程 t
for (int i = 1; i <= 20; i++) {
Thread.sleep(1000);
System.out.println("主线程吃了 " + i + " 个包子");
// 如果不加以控制,主线程和子线程将会交替进行
if (i == 5) {
System.out.println("主线程吃了5个包子,然后先让子线程先吃");
// Thread.yield(); // 礼让,不一定成功
t.join(); // 让 t 线程插队,即先让 t 线程先执行, t 线程执行完毕后主线程才继续执行
System.out.println("子线程已经吃完包子了,主线程可以继续吃了");
}
}
}
}
class T extends Thread { // 自定义线程类
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
try {
Thread.sleep(1000); // 休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程吃了 " + i + " 个包子");
}
}
}
用户线程和守护线程
public class Test {
public static void main(String[] args) throws Exception {
// 创建线程
DaemonThread daemonThread = new DaemonThread();
// 正常情况下,主线程结束时,子线程还在运行(因为子线程目前是一个无限循环的)
// 如果希望当主线程结束后,子线程可以自动结束,只需将子线程设置成守护线程即可
// 需要先设置,再启动
// daemonThread.setDaemon(true);
daemonThread.start();
for (int i = 1; i <= 10; i++) {
Thread.sleep(1000);
System.out.println(i + "主线程在运行...");
}
}
}
class DaemonThread extends Thread { // 把一个线程设置为守护线程
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("子线程无限循环输出中....");
}
}
}
线程 7 大状态
线程状态转换图
public class Test {
public static void main(String[] args) throws Exception {
// 验证线程的状态
// 创建线程
T t = new T();
System.out.println(t.getName() + "的状态:" + t.getState());
t.start();
while (Thread.State.TERMINATED != t.getState()) { // 只要 t 线程不处于终止装填
System.out.println(t.getName() + "的状态:" + t.getState());
Thread.sleep(1000);
}
System.out.println(t.getName() + "的状态:" + t.getState());
}
}
class T extends Thread { // 把一个线程设置为守护线程
@Override
public void run() {
while (true) {
for (int i = 0; i < 10; i++) {
System.out.println("hi " + i);
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
break;
}
}
}
线程同步机制
public class Test {
public static void main(String[] args) throws Exception {
SellTicket sellTicket = new SellTicket();
new Thread(sellTicket).start(); // 线程1 —— 窗口1
new Thread(sellTicket).start(); // 线程2 —— 窗口2
new Thread(sellTicket).start(); // 线程3 —— 窗口3
}
}
// 售票问题:当有多个线程同时购买车票时,可能会出现车票为负数的情况
// 实现接口方式,使用 synchronized 实现线程同步,解决售票问题
class SellTicket implements Runnable {
private int ticketNum = 30; // 假设有 100 张票
private boolean loop = true;
/*
* 添加了 synchronized 关键字后,将 sell 方法变成了一个同步方法
* 此时,在同一时刻,只能有一个线程来执行 sell 方法
*/
public synchronized void sell() {
if (ticketNum <= 0) {
System.out.println("车票已经售罄...");
loop = false;
return;
}
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("窗口:" + Thread.currentThread().getName() +
"售出 1 张票,还剩 " + (--ticketNum) + " 票");
}
@Override
public void run() {
while (loop) {
sell(); // sell 是同步方法
}
}
}
互斥锁
说明:在选择上锁方式时,选择同步代码块要比选择同步方法效率要高,因为同步代码块的范围相对同步方法来说要小,效率也就高一些
public class Test {
public static void main(String[] args) throws Exception {
SellTicket sellTicket = new SellTicket();
new Thread(sellTicket).start(); // 线程1 —— 窗口1
new Thread(sellTicket).start(); // 线程2 —— 窗口2
new Thread(sellTicket).start(); // 线程3 —— 窗口3
}
}
// 售票问题:当有多个线程同时购买车票时,可能会出现车票为负数的情况
// 实现接口方式,使用 synchronized 实现线程同步,解决售票问题
class SellTicket implements Runnable {
private int ticketNum = 30; // 假设有 100 张票
private boolean loop = true;
/*
* SellTicket sellTicket = new SellTicket(); 创建了一个对象
* 再把该对象传给三个线程:
* new Thread(sellTicket).start();
* new Thread(sellTicket).start();
* new Thread(sellTicket).start();
* 所以三个线程操作的是同一个对象
*/
Object obj = new Object();
/*
* 同步方法为静态时,互斥锁为当前类本身
* 如以下的 func 方法的互斥锁是加在 SellTicket.class 上的
*/
public synchronized static void func() {
}
/*
* 在静态方法中实现一个同步代码块,不能用 this
* 而是需要用 SellTicket.class
*/
public static void func2() {
synchronized (SellTicket.class) {
System.out.println("func2...");
}
}
/*
* 添加了 synchronized 关键字后,将 sell 方法变成了一个同步方法
* 此时,在同一时刻,只能有一个线程来执行 sell 方法
* 此时锁在 this 对象身上
*/
public /* synchronized */ void sell() {
// 也可以在代码块上写 synchronized ,这种方式称为同步代码块,以下这种写法互斥锁还是在 this 身上
// 也可以把互斥锁放在其他对象身上(前提要求要是同一个对象)
synchronized (/* this */ obj) {
if (ticketNum <= 0) {
System.out.println("车票已经售罄...");
loop = false;
return;
}
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("窗口:" + Thread.currentThread().getName() +
"售出 1 张票,还剩 " + (--ticketNum) + " 票");
}
}
@Override
public void run() {
while (loop) {
sell(); // sell 是同步方法
}
}
}
线程的死锁
public class Test {
public static void main(String[] args) throws Exception {
// 模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B线程");
A.start();
B.start();
}
}
// 模拟死锁现象
class DeadLockDemo extends Thread {
// 保证多线程共享同一个对象,故使用 static
static Object o1 = new Object();
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
/*
* 假设 flag 为 true ,此时线程A 就会先得到/持有 o1 对象锁,然后尝试获得 o2 对象锁
* 如果线程A 得不到 o2 对象锁,就会 Blocked (阻塞)
* 假如此时 flag 为 false ,线程B 会得到 o2 对象锁,然后尝试获得 o1 对象锁
* 如果线程B 得不到 o1 对象锁,就会 Blocked (阻塞)
* 那么此时线程A 和线程B 互相等待对方所持有的锁,就处于死锁状态
*/
if (flag) {
synchronized (o1) { // 对象互斥锁,下面就是同步代码
System.out.println(Thread.currentThread().getName() + "进入1");
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + "进入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + "进入3");
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + "进入4");
}
}
}
}
}
释放锁