多线程核心

多线程核心知识梳理

线程状态

在这里插入图片描述

6个状态定义:java.lang.Thread.State

  1. new: 新建的尚未启动的线程的线程状态。
  2. Runnable: 可运行线程的线程状态,等待CPU调度。
  3. Blocked: 线程阻塞等待监视器锁定的线程状态。处于synchronized同步代码块或方法中被阻塞。
  4. Waiting: 等待线程的线程状态,需其他线程唤醒才执行。下列不带超时的方式:Object.wait、Thread.join、LockSupport.park。
  5. Timed Wating: 具有指定等待时间的等待线程的线程状态。下列带超时的方式:Thread.sleep、Object.wait、Thread.join、LockSupport.partNanos、LockSupport.parkUtil。
  6. Teminated: 终止线程的线程状态。线程正常完成执行或者出现异常。

线程中止

不正确的线程中止–Stop

Stop: 中止线程,并且清除监控器锁的信息,但是可能导致线程安全问题,JDK不建议用。

Destroy: JDK未实现该方法。

public class StopThread extends Thread {
		private int i = 0, j = 0;
		@Override
		public void run() {
				synchronized (this) {
						// 增加同步锁,确保线程安全
						++i;
						try {
								// 休眠10秒钟,模拟耗时操作
								Thread.sleep(10000);
						}catch(InterruptedException e) {
								e.printStackTrace();
						}
						++j;
				}
		}
		public void print() {
				System.out.println("i=" + i + ",j=" + j);
		}
}
public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        StopThread thread = new StopThread();
        thread.run();

        // 休眠1秒,确定i变量自增成功
        Thread.sleep(1000);
        // 终止线程
        thread.stop();
        while (thread.isAlive()){
            // 确保线程终止
        }
        thread.print();
    }
}
理想输出: i = 1, j = 1
程序执行结果: i = 1, j = 0
没有保证同步代码块里面数据的一致性,破坏了线程安全

正确的线程中止–interrupt

如果目标线程在调用Object class的wait0、wait(long)或wait(long, int)方法、join()、join(long, int) 或sleep(long, int)方法时被阻塞,那么Interrupt会生效,该线程的中断状态将被清除,拋出InterruptedException异常。

如果目标线程是被I/O或者NIO中的Channel所阻塞,同样,I/O操作会被中断或者返回特殊异常值。达到终止线程的目的。
如果以上条件都不满足,则会设置此线程的中断状态。

对于Demo3中的示例,stop改成interrupt后,最终输出为“i=1,j=1”,数据一致。

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        StopThread thread = new StopThread();
        thread.run();

        // 休眠1秒,确定i变量自增成功
        Thread.sleep(1000);
        // 终止线程
        thread.interrupt();
        while (thread.isAlive()){
            // 确保线程终止
        }
        thread.print();
    }
}

正确的线程中止–标志位

代码逻辑中,增加一个判断,用来控制线程执行的中止。如下所示:

public class Demo4 {
    public volatile static boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            try {
                while (flag) {
                    System.out.println("运行中。。。");
                    Thread.sleep(1000L);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        Thread.sleep(3000L);
        flag = false;
        System.out.println("程序运行结束");
    }
}

线程通信

线程通信的方式

要想实现多个线程之间的协同,如:线程执行先后顺序、获取某个线程执行的结果等等。

涉及到线程之间相互通信,分为以下四类:

  • 文件共享
  • 网络共享
  • 共享变量
  • jdk提供的线程协调API

细分为:suspend/resume、wait/notify、park/unpark

文件共享

在这里插入图片描述

共享变量

在这里插入图片描述

线程协调–JDK API

JDK中对于需要多线程写作完成某一任务的场景,提供了对应API支持。

多线程协作的典型场景是:生产者-消费者模型。(线程阻塞、线程唤醒)

在这里插入图片描述

API–被弃用的suspend和resume

作用:调用suspend挂起目标线程,通过resume可以恢复线程执行。

		Object baozipu = null;
    /** 正常的suspend和resume */
    public void suspenResumeTest() throws InterruptedException {
        Thread consumerThread = new Thread(() -> {
            while (baozipu == null) {//如果没有包子,进入等待
                System.out.println("1. 进入等待");
                Thread.currentThread().suspend();
            }
            System.out.println("2.买到包子,回家!");
        });
        consumerThread.start();
        // 3秒之后,产生一个包子
        Thread.sleep(3000L);
        baozipu = new Object();
        consumerThread.resume();
        System.out.println("3.通知消费者");
    }
运行结果:
1. 进入等待
3.通知消费者
2.买到包子,回家!

被弃用的主要原因是容易写出死锁的代码。

suspend和resume死锁示例

同步代码中使用

 /** 同步代码中使用 */
    public void suspenResumeDeadLockTest() throws InterruptedException {
        Thread consumerThread = new Thread(() -> {

            System.out.println("1. 进入等待");
            synchronized (this) {
                Thread.currentThread().suspend();
            }
            System.out.println("2.买到包子,回家!");
        });
        consumerThread.start();
        // 3秒之后,产生一个包子
        Thread.sleep(3000L);
        baozipu = new Object();
        // 争取到锁以后再恢复consumerThread
        synchronized (this) {
            consumerThread.resume();
        }
        System.out.println("3.通知消费者");
    }
运行结果:
1. 进入等待

suspend比resume后执行

 /** suspend比resume后执行*/
    public void suspenResumeDeadLockTest1() throws InterruptedException {
        Thread consumerThread = new Thread(() -> {

            System.out.println("1. 进入等待");
            try {
                Thread.sleep(5000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Thread.currentThread().suspend();

            System.out.println("2.买到包子,回家!");
        });
        consumerThread.start();
        // 3秒之后,产生一个包子
        Thread.sleep(3000L);
        baozipu = new Object();
        consumerThread.resume();
        System.out.println("3.通知消费者");
    }
运行结果:
1. 进入等待
3.通知消费者
wait/notify机制

这些方法只能由同一个对象锁的持有者线程调用,也就是写在同步代码块里面,否则会抛出IllegalMonitorStateException异常。

wait方法导致当前线程等待,加入该对象的等待集合中,并且释放当前持有的对象锁。

notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。

注意: 虽然会wait自动解锁,但是对于顺序有要求, 如果在notify被调用之后才开始wait方法的调用,线程会永远处于WAITING状态。

  /** 正常的wait和notify */
    public void waitNotifyTest() throws InterruptedException {
        Thread consumerThread = new Thread(() -> {
            while (baozipu == null) {//如果没有包子,进入等待
                synchronized (this) {
                    try {
                        System.out.println("1. 进入等待");
                        this.wait(); // wait后释放当前对象锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
            System.out.println("2.买到包子,回家!");
        });
        consumerThread.start();
        // 3秒之后,产生一个包子
        Thread.sleep(3000L);
        baozipu = new Object();
      	// 拿到当前对象锁
        synchronized (this) {
            this.notify();
        }
        System.out.println("3.通知消费者");
    }
运行结果:
1. 进入等待
3.通知消费者
2.买到包子,回家!

会导致永久等待的wait和notify的示例

/** 会导致永久等待的wait和notify */
public void waitNotifyDeadLockTest() throws InterruptedException {
    Thread consumerThread = new Thread(() -> {
        while (baozipu == null) {//如果没有包子,进入等待
            try {
                Thread.sleep(5000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this) {
                try {
                    System.out.println("1. 进入等待");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
        System.out.println("2.买到包子,回家!");
    });
    consumerThread.start();
    // 3秒之后,产生一个包子
    Thread.sleep(3000L);
    baozipu = new Object();
    synchronized (this) {
        this.notify();
    }
    System.out.println("3.通知消费者");
}
运行结果:
3.通知消费者
1. 进入等待
Park/unpark机制

线程调用park则等待“许可”,unpark方法为指定线程提供“许可(permit)”。

不要求park和unpark方法的调用顺序

多次调用unpark之后,再调用park线程会直接运行。但是不会叠加,也就是说,连续多次调用park方法,第一次会拿到“许可”直接运行,后续调用会进入等待。

/** 正常的park和unpark */
public void parkUnparkTest() throws InterruptedException {
    Thread consumerThread = new Thread(() -> {
        while (baozipu == null) {//如果没有包子,进入等待
            System.out.println("1. 进入等待");
            LockSupport.park();
        }
        System.out.println("2.买到包子,回家!");
    });
    consumerThread.start();
    // 3秒之后,产生一个包子
    Thread.sleep(3000L);
    baozipu = new Object();
    LockSupport.unpark(consumerThread);
    System.out.println("3.通知消费者");
}
运行结果:
1. 进入等待
3.通知消费者
2.买到包子,回家!

park拿到锁之后不会自动释放锁,会导致死锁

死锁的park和unpark

/** 死锁的park和unpark */
public void parkUnparkDeadLockTest() throws InterruptedException {
    Thread consumerThread = new Thread(() -> {
        while (baozipu == null) {//如果没有包子,进入等待
            System.out.println("1. 进入等待");
            // 拿到锁后挂起
            synchronized (this) {
                LockSupport.park();
            }
        }
        System.out.println("2.买到包子,回家!");
    });
    consumerThread.start();
    // 3秒之后,产生一个包子
    Thread.sleep(3000L);
    baozipu = new Object();
    synchronized (this) {
        LockSupport.unpark(consumerThread);
    }
    System.out.println("3.通知消费者");
}
运行结果:
1. 进入等待

注意:不可以使用if语句判断,官方建议应该在循环中检查等待条件,原因是处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。伪唤醒是指线程并非因为notify、notifyall、 unpark等api调用而唤醒,是更底层原因导致的。以上代码均相同。

线程封闭

多线程访问共享可变数据时,涉及到线程间数据同步的问题。并不是所有时候,都要用到共享数据,所以线程封闭的概念就被提出来了。

数据都被封闭在各自的线程之中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术成为线程封闭。

线程封闭具体的体现有:

  • ThreadLocal

  • 局部变量

ThreadLocal

ThreadLocal是Java中一种特殊的变量。

他是一个线程级别的变量,每个线程都有一个ThreadLocal,就是每个线程都拥有了自己独立的变量,竞争条件被彻底消除了,在并发模式下是绝对安全的变量。

用法:

ThreadLocal<T>  var = new ThreadLocal<T>();

会自动在每一个线程上创建一个T的副本,副本之间彼此独立,互不影响。

可以用ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来替代方法传参的做法。

/**
 * 封闭线程示例
 */
public class Demo7 {
    // ThreadLocal变量,每一个线程都有一个副本,互不干扰
    public static ThreadLocal<String> value = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        value.set("这是主线程设置的123");
        String v = value.get();
        System.out.println("线程1执行之前,主线程取到的值:" + v);
        new Thread(() -> {
            String v1 = value.get();
            System.out.println("线程1取到的值:" + v1);
            // 设置线程1的ThreadLocal
            value.set("这是线程1设置的456");
            v1 = value.get();
            System.out.println("重新设置后,线程1取到的值:" + v1);
            System.out.println("线程1执行结束!");
        }).start();
        // 等待所有线程执行结束后
        Thread.sleep(5000L);
        v = value.get();
        System.out.println("线程1执行之后,主线程取到的值:" + v);
    }

}
运行结果:
线程1执行之前,主线程取到的值:这是主线程设置的123
线程1取到的值:null
重新设置后,线程1取到的值:这是线程1设置的456
线程1执行结束!
线程1执行之后,主线程取到的值:这是主线程设置的123
栈封闭

局部变量的固有属性之一就是封闭在线程中。
它们位于执行线程的栈中,其他线程无法访问这个栈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值