Java基础--多线程
前言
- JAVA线程状态经常有人搞混,说5种6种甚至7种都有。其实5种是操作系统的线程状态,JAVA有6种,Thread源码的枚举类型statue有提现。
-
NEW: 被创建,还没有调用start()方法;
-
RUNNABLE: 运行中,JAVA中把操作系统的就绪(ready),运行(running)统称为”运行中“。 线程对象被创建后,其他线程(如main)调用了该对象的start方法,该状态的线程位于可运行线程池中,等待被线程调度选择,获取cpu权限,此时是就绪(ready)。 就绪状态的线程获取cpu时间片后变为运行中(running)状态。
-
BlOCKED: 表示线程进入等待状态,也就是线程因为某种原因放弃了 CPU 使用权。
- 等待阻塞:运行的线程执行了Thread.sleep() 、wait()、 join() 等方法, JVM 会把当前线程设置为等待状态,当 sleep 结束、join 线程终止或者线程被唤醒后,该线程从等待状态进入到阻塞状态,重新抢占锁后进行线程恢复;
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么jvm会把当前的线程放入到锁池中 ;
- 其他阻塞:发出了 I/O请求时,JVM 会把当前线程设置为阻塞状态,当 I/O处理完毕则线程恢复;
-
WAITING: 等待状态,没有超时时间,要被其他线程或者有其它的中断操作。 无条件等待,当线程 调用wait()、join()、LockSupport.park() 不加超时时间的方法之后所处的状态,如果没有被唤醒或等待的线程没有结束,那么将一直等待,当前状态的线程不会被分配CPU资源和持有锁;
-
TIMED_WAITING: 超时等待状态,超时以后自动返回; 有条件的等待,当线程调用 sleep(long)、wait(long)、join(long)、LockSupport.park(long)、LockSupport.parkNanos(long)、LockSupport.parkUntil(long)方法之后所处的状态,在指定的时间没有被唤醒或者等待线程没有结束,会被系统自动唤醒,正常退出。
-
TERMINATED: 终止状态,表示当前线程执行完毕 。 执行完了run()方法。其实这只是Java语言级别的一种状态,在操作系统内部可能已经注销了相应的线程,或者将它复用给其他需要使用线程的请求,而在Java语言级别只是通过Java代码看到的线程状态而已。
源码
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
*Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
状态切换
-
《并发编程的艺术》
-
代码示例
package shen.example.demo.mutithread;
import java.util.concurrent.locks.LockSupport;
/**
* 线程状态demo
*/
public class ThreadStatusDemo {
public static void main(String[] args) throws InterruptedException {
//创建一个线程
System.out.println("创建子线程new");
Thread thread = new Thread(()->{
//2.线程状态:RUNNABLE
System.out.println(Thread.currentThread().getState());
System.out.println("子线程运行中...开始调用park()");
LockSupport.park();
//睡眠
try {
System.out.println("子线程运行中...调用sleep(long)");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取同步锁
System.out.println("子线程运行中...进入同步代码块");
synchronized (ThreadStatusDemo.class){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//1.线程状态:NEW
System.out.println(thread.getState());
System.out.println("子线程启动start");
//启动线程
thread.start();
//主线程睡眠,等待子线程thread调用park()
Thread.sleep(500);
//3.线程状态:WAITING
Thread.sleep(500);
System.out.println(thread.getState());
//唤醒子线程,使子线程调用sleep(long)
LockSupport.unpark(thread);
//4.TIMED_WAITING
System.out.println(thread.getState());
//主线程获取当前类锁
synchronized (ThreadStatusDemo.class){
Thread.sleep(1200);//等待子线程进入同步代码块阻塞
//5.BLOCKED
System.out.println(thread.getState());
}
Thread.sleep(1200);
System.out.println("子线程运行结束");
//5.TERMINATED
System.out.println(thread.getState());
}
}
QA
-
程序计数器为什么私有?
- 为了多线程中线程切换后能够恢复到正确的执行位置。
- 字节码解释器通过改程序计数器来依次执行指令,从而实现代码的流程控制。
-
虚拟机栈为什么私有
- java方法执行时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用信息等,从方法调用到完成,对应栈帧入栈出栈。
-
本地方法栈为什么私有
- 与虚拟机栈类似,区别为native方法服务
-
什么是上下文切换
- CPU分配时间片轮转模拟多核心,当一个时间片用完重新进入就绪状态让其他线程执行,这就是一次上下文切换。
- 任务从保存到加载的过程。
-
死锁的四个条件
- 互斥
- 请求与保持
- 不剥夺
- 循环等待
package com.example.demo.Lock;
/**
* TODO
*
* @author Skiray
* @date 2021/6/17 10:51
*/
public class DeadLock {
private static Object re1 = new Object();
private static Object re2 = new Object();
public static void main(String[] args){
new Thread(()->{
synchronized (re1){
System.out.println(Thread.currentThread().getName() + "get re1");
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get re2");
synchronized (re2){
System.out.println(Thread.currentThread() + "get re2");
}
}
},"线程 1 ").start();
new Thread(()->{
synchronized (re2){
System.out.println(Thread.currentThread()+"get re2");
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get re1");
synchronized (re1){
System.out.println(Thread.currentThread() + "get re1");
}
}
},"线程2").start();
}
}
-
sleep与 wait
- 区别sleep没有释放锁,wait释放锁
- wait常用于线程间交互/通信,sleep常用于暂停。
- wait需要手动唤醒,notify,notifyAll。
- sleep执行完成自动苏醒,超时等待wait(long xx)也会自动苏醒。
-
为什么调用start时会执行run
- new一个Thread,线程进入新建状态,start后,启动线程并进入就绪,等分配到时间片就可运行。start会执行线程的相应准备工作,然后自动执行run内容。直接执行run会把run方法当成一个main线程下的普通方法去执行,并不会在某个线程中执行。
-
synchronized
- 保证修饰的方法或代码块在任意时刻只能有一个线程执行。早期版本属于重量级锁。低效。
- 因监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock实现的,java的线程是映射到OS的原生线程之上的,挂起或唤醒线程都需要OS帮忙,而OS实现线程间切换需要从用户态转换到内核态,相对比较耗时,耗成本。
- 6之后引入了自旋锁、适应性自旋锁、锁消除、锁粗话、偏向锁、轻量级锁等减少锁的开销。现在开源很多用了synchronized。
-
synchronized使用
- 修饰实例方法
对当前对象实例加锁,进入同步代码前需要获得当前对象实例的锁。 synchronized void f(){}
- 静态方法
给当前类加锁,会作用于类的所有对象实例,进入同步代码前要获得当前calss的锁。因静态成员变量不属于任何实例对象,是类成员(不管new多少,static只有一份),所以,一线程A调用一个实例对象的非静态synchronized()方法,线程B需要调用这个实例对象所属类的静态synchronized方法是允许的,不会发送互斥现象,因访问静态synchronized()方法占用的锁是当前类的锁,而非静态synchronized()方法占用的锁是当前实例的锁。
- 代码块
指定加锁对象,对给定对象/类加锁。synchronized(this)表示进入同步代码块前要获得给定对象的锁,synchronized(类.class)表示进入同步代码块前要获得当前class的锁。 synchronized(this){}
-
总结
- synchronized加static和synchronized(class)代码块都是给Class类上锁。
- synchronized关键字加到实例方法是给对象实例上锁。
- 尽量不用synchronized(String a )字符串缓冲池具有缓存功能。
package com.example.demo.Lock;
/**
* TODO
*
* @author Skiray
* @date 2021/6/17 17:18
*/
//双重校验锁实现单例(线程安全)
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance(){
// 先判断是否已经实例化过,没有才加锁
if (null == uniqueInstance ){
synchronized (Singleton.class){
if (null== uniqueInstance){
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
- volatile关键字修饰也很重要,uniqueInstance = new Singleton(); 分三步
- 1为uniqueInstance分配空间;
- 2初始化 uniqueInstance;
- 3将 uniqueInstance 指向分配的内存地址;
但是JVM具有指令重排的特性,执行顺序不一定是123.指令重排多线程下会导致一个线程还没有初始化。如线程1执行1和3,此时线程2调用方法,发下uniqueInstance不为空 null == uniqueInstance不成立,因此返回uniqueInstance,但此时uniqueInstance还没初始化。
-
构造方法可synchronized 修饰吗?
- 不,构造方法本身就是线程安全的。
-
为什么要CPU缓存?
- 解决内存cpu速度不匹配的问题。
-
synchronized 和 volatile区别
- volatile是线程同步的轻量级实现,性能好,只能用于变量。
- volatile能保证数据的可见性,但不能保证数据的原子性,synchronized都能保证。
- volatile主要用于解决变量在多个线程之间的可见性,synchronized解决的是多个线程之间访问资源的同步性。
-
ThreadLocal
- 类的作用是创建线程私有的变量。
- 若创建了一个ThreadLocal变量,访问这个变量的每个线程都会有这个变量的本地副本,可get/set获取默认值或将其更改为当前线程所存的副本的值,避免了线程安全的问题。
-
Runnable 和 Callbale
- Runnbale没有返回值,或抛出检查异常
-
execute 和submit区别
-
execute体检不需要返回值的任务,无法判断是否成功。
-
submit提交返回值任务,线程会返回一个Future类,通过Fulture可判断任务是否执行成功,get获取返回值
-
转载自:https://juejin.cn/post/7270792087435214848