一、线程
:::tip
- 线程是由进程创建的,线程就是进程的实体
- 线程可以分为单线程 和 多线程
- 单线程: 同一时刻,只允许执行一个线程;
- 多线程: 同一时刻,可以执行多个线程;
- 并发: 同一时刻,多个任务交替执行,单核 CPU 实现多任务并发;
- 并行: 同一时刻,多个任务同时执行,多核 CPU 可以实现并行;
- 并行 与 并发 也可能同时存在
:::
1. 线程使用
:::tip
创建线程的两种方法:
- 继承 Thread 类,重写 run 方法;
- 实现 Runnable 接口,重写 run 方法;
:::

:::tip
继承 Thread 类,创建线程:
- 主线程结束后,子线程任然在执行时,进程不会结束;
- 调用 run() 方法并不会启动线程,而且会造成主线程阻塞
- 调用 start() 方法可以启动线程,不会造成线程阻塞,主线程和子线程会同时执行
:::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Thread01{ public static void main(String[] args) { Cat cat = new Cat(); cat.start(); } }
class Cat extends Thread{ @Override public void run() { try { for (int i = 0; i < 10; i++) { System.out.println("第"+(i+1) +"次,喵呜~~~"); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } }
|
:::tip
实现 Runnable 接口,创建线程:
- 先创建 Thread 类 : Thread thread = new Thread(实现类);
- 调用 start() 方法,启动线程
:::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Thread02 { public static void main(String[] args) { Dog dog = new Dog(); Thread thread = new Thread(dog); thread.start(); } }
class Dog implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("第" + (i+1)+ "次, 旺财: 汪~~~"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
Thread 和 Runnable 的区别:
:::tip
- Thread 和 Runnable 在 Java 底层没有区别,都是调用 start();
- 实现 Runnable 接口的方式更加适合多线程共享一个资源的情况,并且可以避免单继承的限制;
:::
2. 线程终止、线程同步
:::tip
- 当线程完成任务后,会自动退出;
- 通过使用 变量 来控制 run 方法退出的方式来停止线程;
- 使用synchronized同步器来修饰买票的方法(防止出现一张票被两人同时抢到)
:::
:::tip
线程同步(synchronized):
- 在多线程编程中,某些数据不允许被多个线程同时访问,此时就使用线程同步访问技术,保证线程在同一时刻最多只能被一个线程访问,以保证线数据完整性;
- 线程同步: 即 当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作;
- 同步代码块:synchronized(对象){需要被同步的代码};
- synchronized 还可以放在方法声明中,表示整个方法 为 同步方法;
:::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public class Thread03 { public static void main(String[] args) { Ticket1 t = new Ticket1();
Thread r1 = new Thread(t); Thread r2 = new Thread(t); Thread r3 = new Thread(t);
r1.start(); r2.start(); r3.start();
} }
class Ticket1 implements Runnable{ int count = 100;
@Override public void run() { sell(); }
public synchronized void sell(){ while (true) { if (count > 0) { System.out.println("售出 1 张票,剩余" + (--count) + "--->" + Thread.currentThread().getName() ); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } else { System.out.println("已售空"); break; } } } }
|
3. 线程常用方法
setName | 设置线程名称,使之与参数 name 相同 |
---|
getName | 返回线程名称 |
start | 使该线程开始执行, |
run | 调用线程对象 run 方法,run 方法不会创建新线程 |
setPriority | 更改线程优先级,MAX_PRIORITY = 10, MIN_PRIORITY=1,NORM_PRIORITY=5; |
getPriority | 获取线程优先级 |
sleep | 在指定的毫秒数内,线程休眠 |
interrupt | 中断线程, 线程没有关闭,一般用于中断正在休眠的线程 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class Thread04 { public static void main(String[] args) throws InterruptedException { T t = new T(); Thread thread = new Thread(t); thread.setName("线程 1"); thread.start();
Thread.sleep(2000); thread.interrupt(); } }
class T implements Runnable{ int count = 0;
@Override public void run() { while (true) { if (count < 3) { for (int i = 0; i < 10; i++) { System.out.println("吃了" + (i + 1) + "个包子~ " + Thread.currentThread().getName()); } System.out.println("休息 10 秒中...."); try { Thread.sleep(5000); } catch (InterruptedException e) { System.out.println("休眠被中断"); } ++count; }else { break; } } } }
|
| |
---|
yield | 线程礼让,让出 CPU让其他线程先执行,因时间不确定,不一定礼让成功 |
join | 线程插队,线程一旦插队成功,则需要先完成插队线程的所有任务 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class Thread04 { public static void main(String[] args) throws InterruptedException { T t = new T(); Thread t1 = new Thread(t); t1.setName("线程 1"); t1.start();
for (int i = 0; i < 10; i++) { System.out.println("吃了" + (i + 1) + "个包子~ " + Thread.currentThread().getName()); Thread.sleep(1000); if (i == 5){ t1.join(); } } } }
class T implements Runnable{
@Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("吃了" + (i + 1) + "个包子~ " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println(""); } } } }
|
4.用户线程和守护线程
:::tip
- 用户线程: 也叫工作线程,当线程的任务执行完 或以 通知的方式结束;
- 守护线程 : 一般是为工作线程服务的,当所有的用户线程结束时,守护线程自动结束。
- 常见的守护线程: 垃圾回收机制
- 设置守护线程: thread.setDaemon(true)
:::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class Thread05 { public static void main(String[] args) throws InterruptedException { R r = new R(); Thread thread = new Thread(r); thread.setDaemon(true); thread.start(); for (int i = 0; i < 5; i++) { System.out.println("主线程执行中~~~"); Thread.sleep(1000); } } }
class R implements Runnable{ @Override public void run() { for (;;){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("这是一个无限循环~~~"); } } }
|
5.互斥锁
:::tip
- 互斥锁: 用来保证共享数据操作的完整性;
- 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象;
- 关键字 synchronized 来与对象的互斥锁联系;
- 当某个对象用了 synchronized 修饰时,表示该对象在任一时刻只能由一个线程访问;
- 同步的局限性:导致程序的执行效率要降低;
- 同步方法(非静态的)的锁 可以是 this,也可以是其他对象(要求是同一对象);
- 同步方法(静态的)的锁为当前类本身;
:::
:::warning
注意事项:
- 同步方法如果没有使用 static 修饰:默认锁对象为 this;
- 如果方法使用 static 修饰,默认锁对象为 当前类.class
- 实现步骤:
- 分析需要上锁的代码;
- 选择同步代码块或同步方法;
- 要求多个线程的锁对象为同一个;
:::
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class Ticket implements Runnable{ static int count = 10;
@Override public void run() {}
public synchronized void sell1(){ if (count > 0) { System.out.println("售出 1 张票,剩余" + (--count) + "--->" + Thread.currentThread().getName()); } else { System.out.println("已售空"); } }
public void sell2(){ synchronized (this) { if (count > 0) { System.out.println("售出 1 张票,剩余" + (--count) + "--->" + Thread.currentThread().getName()); } else { System.out.println("已售空"); } } }
public static void sell3(){ synchronized (Ticket.class) { if (count > 0) { System.out.println("售出 1 张票,剩余" + (--count) + "--->" + Thread.currentThread().getName()); } else { System.out.println("已售空"); } } } }
|
6.死锁
:::tip
线程死锁:
- 多个线程都占用了对象的锁资源,但不肯相让,导致了死锁,在编程里一定要避免死锁的发生;
- 多个线程互相获取了对方想要获取的线程锁,即为死锁
:::
7. 释放锁
:::tip
释放锁的情形:
- 当前线程的同步方法,同步代码块执行结束;
- 当前线程在同步代码块、同步方法中遇到了 break、return;
- 当前线程在同步代码块、同步方法中出现了未处理的 Error 或 Exception,导致异常结束;
- 当前线程在同步代码快、同步方法中执行了线程对象的 wait() 方法,当前线程暂停,并释放锁;
不会释放锁的情形:
- 线程执行同步代码块或同步方法时,程序调用 Thread.sleep()、Thread.yield() 方法暂停当前线程执行,不会释放锁;
- 线程执行同步代码块时,其他线程调用类该线程的 suspend() 方法,将该线程挂起,该线程不会释放锁;
:::
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| public class HomeWork01 { public static void main(String[] args) { RandomInt randomInt = new RandomInt(); Keyboard keyboard = new Keyboard(randomInt); Thread t1 = new Thread(randomInt); Thread t2 = new Thread(keyboard); t1.start(); t2.start(); } }
class RandomInt implements Runnable{ public boolean num = true; @Override public void run() { while (num){ System.out.println((int)(Math.random()*100)); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("线程 1 退出"); } }
class Keyboard implements Runnable{ public RandomInt r; Scanner s = new Scanner(System.in);
public Keyboard(RandomInt r) { this.r = r; }
@Override public void run() { while (true) { System.out.println("请输入: "); char print = s.next().toUpperCase().charAt(0); if (print == 'Q'){ r.num = false; System.out.println("线程 2 退出"); break; } } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public class HomeWork02 { public static void main(String[] args) { money money = new money(); Thread t1 = new Thread(money); t1.setName("线程 1 "); Thread t2 = new Thread(money); t2.setName("线程 2 "); t1.start(); t2.start(); } }
class money implements Runnable{ private double m = 10000;
public double getM() { return m; }
public void setM(double m) { this.m = m; }
@Override public void run() { while (true){ if (m < 1000){ System.out.println("钱被取完了."); break; } synchronized (this){ setM(getM()-1000); System.out.println(Thread.currentThread().getName() + "取出了 1000 大洋, 剩余" + getM()); }
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|