java并发基础知识

1.为什么要使用多线程
  • 充分发挥多核CPU的性能
  • 方便进行业务拆分,提升服务性能
2. java多线程有什么缺点

(1) 频繁的上下文切换
线程在切换过程中,CPU需要保存当前线程的状态,以便切换回来时能够恢复到当前状态,这个过程会损耗CPU性能。频繁的上下文切换无法发挥多线程的优势。为了减少上下文切换,可以采用无锁并发编程、CAS算法、使用最少的线程或是使用协程

  • 无锁并发编程:在有锁并发场景中,线程会因为没有竞争到锁而阻塞,让出CPU,提前进行线程切换
  • CAS操作,只有一个线程能够执行成功,其他线程会循环竞争锁,直到时间片执行完成
  • 使用少量线程:任务较少时,避免创建过多的线程,以至于多个线程处于等待状态
  • 使用协程
    协程概念:基于线程之上,但是比线程更轻量级的存在,由程序员自己写程序控制
    协程的目的:当线程出现长时间IO时,由程序控制,挂起当前任务,并保存当前栈信息,去执行另一个任务,等待任务完成或是达到某个条件时,再还原原先的栈信息,并继续执行。
    协程特点:
    (1)线程有OS进行调度,协程由用户自己进行调度。且协程是在同一线程内部操作,所以可以减少线程上下文的切换
    (2)线程默认的stack是1M,而协程默认是接近1k,所以一个线程内部可以有多个协程
    (3)协程适用于存在阻塞的并发场景,而不适用于大量计算的场景

(2) 线程安全问题
在多线程环境下,无论线程以何种顺序执行,都能保证程序的正确性。线程安全问题,本质就是对共享数据的访问问题

3. java线程状态

(1) 新建(new):创建后尚未启动的线程处于这个状态
(2) 运行(runable): ready + running

  • ready:就绪状态,等待cpu分配时间片即可运行
  • running:正在运行

(3) 无限等待(waiting):处于这个状态的线程不会被cpu分配时间片,其等待其他线程显示唤醒。有如下方法可以进入该状态:

  • 没有设置timeout的Object.wait()方法
  • 没有设置timeout的Thread.join()方法
  • LockSupport.park()方法

(4) 超时等待(time_waiting):该状态下,线程也不会被CPU配时间片,但是与Waiting不同的是,该状态无需等待其他线程显示唤醒,超过超时时间之后,系统会自动唤醒线程。有如下方法可以让线程进入超时等待状态

  • Thread.sleep()
  • 设置了timeout的Object.wait()方法
  • 设置了timeout的Thread.join()方法
  • LockSupport.parkNanos()方法
  • LockSupport.parkUtils()方法

(5) 阻塞(Blocked): 线程因为某种原因放弃CPU使用权,暂时停止运行。阻塞状态分两种

  • 同步阻塞:线程在进入同步代码块(synchronize)时,未获得锁将进入这个状态
  • 其他阻塞:正在运行的线程发出IO请求,JVM会把该线程置为阻塞状态

(6) 终止(Terminated):已经终止的线程状态。线程run()方法或是main()方法执行结束,或是线程因异常退run方法,该线程结束生命周期

4. 线程生命周期内的一些操作

除了新建线程之外,线程生命周期内还有一些其他操作,这些操作可以作为线程间的一种通信方式

  1. 中断 interrupt
    (1) 中断是什么
    interrupt()就是中断某个线程,主要用于线程间的协作,如果A线程需要中断B线程,就调用B.interrupt()
    (2) interrupt()一定会中断线程吗
    interrupt()可以看做是线程的一个标志位,某个线程被中断之后,就会记录该中断状态。所以调用某个线程的interrupt()之后,并不一定会真正中断该线程,仅仅是告知该线程你该中断了,线程是否中断应该由线程自身判断,而不是由外部线程决定
    (3) 线程中断状态怎么用
    原则上,在设计线程的执行流程时,首先要判断线程的中断状态来决定执行内容
    (4) 什么时候会抛出InterrupedException
    线程处于wait()、sleep()、join()等状态,此时调用线程的interrupt()会抛出InterrupedException,且中断状态会被清除
    (5) 抛出的InterruptException该怎么处理
    • 继续上抛,交由上游处理
    • catch异常,调用interrupt()方法,恢复当前线程的中断状态(调用线程的interrupt方法,需要通过中断状态判断线程是否被中断,如果不恢复,就可能会导致状态判断失败)
public class ThreadInterruptDemo {
    public static void main(String[] args) {
        Thread sleepThread1 = new Thread(()-> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        });

        Thread sleepThread2 = new Thread(()-> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                //重置中断状态
                Thread.interrupted();
                e.printStackTrace();
            }
        });

        Thread busyThread = new Thread(()-> {
            while(true){

            }
        });
        sleepThread1.start();
        sleepThread2.start();
        busyThread.start();
        sleepThread1.interrupt();
        sleepThread2.interrupt();
        busyThread.interrupt();

        System.out.println("sleepThread1 isInterrupt = " + sleepThread1.isInterrupted());
        System.out.println("sleepThread2 isInterrupt = " + sleepThread2.isInterrupted());
        System.out.println("busyThread isInterrupt = " + busyThread.isInterrupted());
    }
}

  1. join
    join可以看作是线程间的一种协作方式,在很多时候,一个线程能否执行,依赖另一个线程的执行结果,当A线程依赖B线程时,可以调用B.jion(),阻塞一直到B线程执行完成
    join方法的核心源码,判断锁等待线程isAlive,如果存活,则无限期等待,当B线程执行完成退出时,会调用notifyAll()方法,通知所有线程
public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        //无超时的join(),无限期等待
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            //有超时的等待
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
//java join demo
public class THreadJoinDemo {
    public static void main(String[] args) {
        Thread previousThread = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            TestJoin testJoin = new TestJoin(previousThread);
            testJoin.start();
            previousThread = testJoin;
        }
    }
}

class TestJoin extends Thread {

    Thread currentThread;

    public TestJoin(Thread currentThread) {
        this.currentThread = currentThread;
    }

    @Override
    public void run() {
        try {
            currentThread.join();
            System.out.println(currentThread.getName() + " terminated");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  1. sleep
    sleep就是按照当前指定的时间休眠,时间精度取决于处理器的计时器和调度器。
    sleep对比wait
    (1) sleep是Thread类的静态方法,wait是Object类的实例方法
    (2) sleep可以在任意地方使用;wait只能在同步代码块或是同步方法中使用,也就是对象已经获得锁。调用wait之后,会释放锁,线程进入线程池,等待下一次获取资源;sleep不会释放锁,仅仅让出CPU
    (3) sleep的线程在时间结束之后,只需获得CPU时间片就会继续执行;而wait的线程,必须等待其他线程调用notify或是notifyAll才会离开线程池,在获得时间片之后才能继续执行

  2. yield
    yield()是Thread的静态方法,执行之后表示该线程让出CPU,但是让出CPU不代表该线程就不运行了,如果在下次的竞争中,该线程获得CPU,将继续执行。
    yield对比sleep
    (1) 相同:都是Thread的静态方法,执行之后都是让出CPU
    (2) 不同:Thread让出CPU之后,交由其他线程去竞争,yield让出CPU之后,交由与自己相同或是更高优先级的线程去竞争,自己有可能继续获取cpu并执行

  3. 守护线程deamon
    守护线程是一种特殊的线程,在后台为系统提供服务。与之对应的是用户线程,只有当最后一个用户线程退出时,守护线程才会结束,JVM也才会终止运行。
    注意:
    在deamon线程退出时,并不会执行finally代码块
    在deamon线程退出时,并不会执行finally代码块
    在deamon线程退出时,并不会执行finally代码块

public class DeamonThreadDemo {

    public static void main(String[] args) {
        Thread deamonThread = new Thread(()->{
            while (true) {
                System.out.println("i am alive");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("i am finally quit");
                }
            }
        });

        deamonThread.setDaemon(true);
        deamonThread.start();

        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

demo中,设置了守护线程deamonThread,主线程sleep(800),会让守护线程获得一次执行机会,打印一次“i am alive”和“i am finally quit”,主线程sleep(500)之后,守护线程继续执行一次“i am alive”,接着主线程退出,守护线程也跟着退出,并没有执行finally代码块。

你可能感兴趣的:(java并发基础知识)