11.线程
大约 8 分钟学习笔记Java基础
一、线程
提示
- 线程是由进程创建的,线程就是进程的实体
- 线程可以分为单线程 和 多线程
- 单线程: 同一时刻,只允许执行一个线程;
- 多线程: 同一时刻,可以执行多个线程; - 并发: 同一时刻,多个任务交替执行,单核 CPU 实现多任务并发; - 并行: 同一时刻,多个任务同时执行,多核 CPU 可以实现并行; - 并行 与 并发 也可能同时存在
1. 线程使用
提示
创建线程的两种方法:
- 继承 Thread 类,重写 run 方法;
- 实现 Runnable 接口,重写 run 方法;
提示
继承 Thread 类,创建线程:
- 主线程结束后,子线程任然在执行时,进程不会结束;
- 调用 run() 方法并不会启动线程,而且会造成主线程阻塞
- 调用 start() 方法可以启动线程,不会造成线程阻塞,主线程和子线程会同时执行
// 继承 Thread 类 - 案例
public class Thread01{
public static void main(String[] args) {
Cat cat = new Cat();
// 调用 run() 方法并不会启动线程,而且会造成主线程阻塞
// 调用 start() 方法可以启动线程,不会造成线程阻塞,主线程和子线程会同时执行
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();
}
}
}
提示
实现 Runnable 接口,创建线程:
- 先创建 Thread 类 : Thread thread = new Thread(实现类);
- 调用 start() 方法,启动线程
// 实现 Runnable 接口 - 案例
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 {
// option + command + t 快捷键, 快速实现 try ... catch
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread 和 Runnable 的区别:
提示
- Thread 和 Runnable 在 Java 底层没有区别,都是调用 start();
- 实现 Runnable 接口的方式更加适合多线程共享一个资源的情况,并且可以避免单继承的限制;
2. 线程终止、线程同步
提示
- 当线程完成任务后,会自动退出;
- 通过使用 变量 来控制 run 方法退出的方式来停止线程;
- 使用 synchronized 同步器来修饰买票的方法(防止出现一张票被两人同时抢到)
提示
线程同步(synchronized):
- 在多线程编程中,某些数据不允许被多个线程同时访问,此时就使用线程同步访问技术,保证线程在同一时刻最多只能被一个线程访问,以保证线数据完整性;
- 线程同步: 即 当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作;
- 同步代码块:synchronized(对象){需要被同步的代码};
- synchronized 还可以放在方法声明中,表示整个方法 为 同步方法;
// 售票系统 - 案例
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();
}
//使用synchronized同步器来修饰买票的方法(防止出现一张票被两人同时抢到)
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 | 中断线程, 线程没有关闭,一般用于中断正在休眠的线程 |
// 线程常用方法 - 案例
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 | 线程插队,线程一旦插队成功,则需要先完成插队线程的所有任务 |
// 线程插队 - 案例
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.用户线程和守护线程
提示
- 用户线程: 也叫工作线程,当线程的任务执行完 或以 通知的方式结束;
- 守护线程 : 一般是为工作线程服务的,当所有的用户线程结束时,守护线程自动结束。
- 常见的守护线程: 垃圾回收机制
- 设置守护线程: thread.setDaemon(true)
// 守护线程 - 案例
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.互斥锁
提示
- 互斥锁: 用来保证共享数据操作的完整性;
- 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象;
- 关键字 synchronized 来与对象的互斥锁联系;
- 当某个对象用了 synchronized 修饰时,表示该对象在任一时刻只能由一个线程访问;
- 同步的局限性:导致程序的执行效率要降低;
- 同步方法(非静态的)的锁 可以是 this,也可以是其他对象(要求是同一对象);
- 同步方法(静态的)的锁为当前类本身;
注意
注意事项:
- 同步方法如果没有使用 static 修饰:默认锁对象为 this;
- 如果方法使用 static 修饰,默认锁对象为 当前类.class
- 实现步骤:
- 分析需要上锁的代码;
- 选择同步代码块或同步方法;
- 要求多个线程的锁对象为同一个;
// 互斥锁 - 案例
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(){
// 同步代码块(非静态), 锁为 this
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.死锁
提示
线程死锁:
- 多个线程都占用了对象的锁资源,但不肯相让,导致了死锁,在编程里一定要避免死锁的发生;
- 多个线程互相获取了对方想要获取的线程锁,即为死锁
7. 释放锁
提示
释放锁的情形:
- 当前线程的同步方法,同步代码块执行结束;
- 当前线程在同步代码块、同步方法中遇到了 break、return;
- 当前线程在同步代码块、同步方法中出现了未处理的 Error 或 Exception,导致异常结束;
- 当前线程在同步代码快、同步方法中执行了线程对象的 wait() 方法,当前线程暂停,并释放锁;
不会释放锁的情形:
- 线程执行同步代码块或同步方法时,程序调用 Thread.sleep()、Thread.yield() 方法暂停当前线程执行,不会释放锁;
- 线程执行同步代码块时,其他线程调用类该线程的 suspend() 方法,将该线程挂起,该线程不会释放锁;
案例
// 输入字母 Q ,暂停随机数
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;
}
}
}
}
// 两个线程分别取钱
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();
}
}
}
}