likes
comments
collection
share

Java基础--多线程

作者站长头像
站长
· 阅读数 52

前言

  • JAVA线程状态经常有人搞混,说5种6种甚至7种都有。其实5种是操作系统的线程状态,JAVA有6种,Thread源码的枚举类型statue有提现。
  1. NEW: 被创建,还没有调用start()方法;

  2. RUNNABLE: 运行中,JAVA中把操作系统的就绪(ready),运行(running)统称为”运行中“。 线程对象被创建后,其他线程(如main)调用了该对象的start方法,该状态的线程位于可运行线程池中,等待被线程调度选择,获取cpu权限,此时是就绪(ready)。 就绪状态的线程获取cpu时间片后变为运行中(running)状态。

  3. BlOCKED: 表示线程进入等待状态,也就是线程因为某种原因放弃了 CPU 使用权。

    • 等待阻塞:运行的线程执行了Thread.sleep() 、wait()、 join() 等方法, JVM 会把当前线程设置为等待状态,当 sleep 结束、join 线程终止或者线程被唤醒后,该线程从等待状态进入到阻塞状态,重新抢占锁后进行线程恢复;
    • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么jvm会把当前的线程放入到锁池中 ;
    • 其他阻塞:发出了 I/O请求时,JVM 会把当前线程设置为阻塞状态,当 I/O处理完毕则线程恢复;
  4. WAITING: 等待状态,没有超时时间,要被其他线程或者有其它的中断操作。 无条件等待,当线程 调用wait()、join()、LockSupport.park() 不加超时时间的方法之后所处的状态,如果没有被唤醒或等待的线程没有结束,那么将一直等待,当前状态的线程不会被分配CPU资源和持有锁;

  5. TIMED_WAITING: 超时等待状态,超时以后自动返回; 有条件的等待,当线程调用 sleep(long)、wait(long)、join(long)、LockSupport.park(long)、LockSupport.parkNanos(long)、LockSupport.parkUntil(long)方法之后所处的状态,在指定的时间没有被唤醒或者等待线程没有结束,会被系统自动唤醒,正常退出。

  6. 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;
}

状态切换

  • 《并发编程的艺术》 Java基础--多线程

  • 代码示例

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获取返回值


参考1 参考2

转载自:https://juejin.cn/post/7270792087435214848
评论
请登录