文章
问答
冒泡
JAVA的多线程与高并发(一)

JAVA的多线程与高并发(一)

以前也陆陆续续看过很多java的多线程的知识,写这个主要是想自己系统地整理一下,加深自己地理解,方便以后使用,同时也做个分享。


根据个人了解,分享分为五个板块(后续可能会调整):

  • 基础(基本概念、synchronized关键字)

  • volatile 和 CAS

  • Atomic类和线程同步新机制

  • LockSupport 工具类

  • 并发容器

  • 线程池

  • 高频面试题

一、线程的基本概念

[TOC]

1.区分进程、线程、协程

进程: 进程就是运行中的程序,进程是操作系统资源分配和调度的基本单位,进程之间不共享资源。对于Java而言,一个JVM进程就是一个运行中的main方法。


线程: 线程存在于进程中,是进程中的实体,也可以称为轻量级进程,是CPU调度执行的最小单位。


两者间的区别与联系:

  1. 进程是相互独立的,线程存在于进程中。

  2. 进程内有共享的资源,可以供其内部的线程共享。

  3. 线程共享进程间的内存,其通信相对于进程要简单。比如多个线程可以共享一个变量

协程: 协程是一种基于线程之上,但又比线程更加轻量级的存在,协程不是被操作 系统内核所管理,而是在用户态执行,具有对内核来说不可见的特性。


管程: 管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发,Java中synchronized就是基于Monitor管程来实现的。

2. 线程的简单例子

package com.rongyu;

import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        // 线程启动的一种方式
        new T1().start();

        for (int i = 0; i < 5; i++) {
            try {
                TimeUnit.MICROSECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main第"+i+"次执行");
        }
    }

    private static class T1 extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    TimeUnit.MICROSECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("T1第"+i+"次执行");
            }
        }
    }

}

执行结果如下:

main第0次执行
T1第0次执行
main第1次执行
T1第1次执行
T1第2次执行
main第2次执行
main第3次执行
T1第3次执行
main第4次执行
T1第4次执行

观察结果可以看到T1和main交替输出,表示程序中有两条线程。

3.创建线程和启动线程的几种方式

package com.rongyu;

import java.util.concurrent.*;

public class CreatThread {

    public static void main(String[] args) {
        //启动线程的几种方式
        //first
        new T1().start();
        //second
        new Thread(new T2()).start();
        //third
        new Thread(()->{
           System.out.println("The fourth way: Lambda");
        }).start();
        //fourth
        Thread t = new Thread(new FutureTask<String>(new T3()));
        t.start();
        //fifth
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(()->{
            System.out.println("By ThreadPool");
        });
        service.shutdown();
    }

    //创建线程的方法
    static class T1 extends Thread {
        @Override
        public void run() {
            System.out.println("The first way: extends Thread");
        }
    }

    //创建线程的方法
    static class T2 implements Runnable {
        @Override
        public void run() {
            System.out.println("The second way: implements Runnable");
        }
    }

    //创建线程的方法
    static class T3 implements Callable<String> {
        @Override
        public String call() {
            System.out.println("The third  way: implements Callable");
            return "success";
        }
    }
}

输出结果:

"C:\Program Files\Eclipse Adoptium\jdk-11.0.14.101-hotspot\bin\java.exe" "-javaagent:D:\ideaFamily\IntelliJ IDEA 2021.1.3\lib\idea_rt.jar=51611:D:\ideaFamily\IntelliJ IDEA 2021.1.3\bin" -Dfile.encoding=UTF-8 -classpath D:\workspace\thread-demo\out\production\thread-demo com.rongyu.CreatThread
The first way: extends Thread
The second way: implements Runnable
The fourth   way: Lambda
The third  way: implements Callable
By ThreadPool

Process finished with exit code 0

4.线程常用方法

**sleep:**当前线程暂停一段时间让给别的线程去运行。 sleep是怎么复活的?由设定的睡眠时间决定,等睡眠到规定的时间自动复活.


**Yield:**当前线程正在执行的时候停下来进入等待队列,回到等待队列中 在系统的调度算法里还是有可能把你刚yield的线程拿回来继续执行,当然,更大的可能是把其他正在等待的线程拿来执行。 Yield的意思就是我让出一下cpu,后面谁能抢到我不管


**join:**就是在自己当前线性加入你调用join的线程,当前线程进入等待,等join的线程执行完,再去执行自己当前线程。(自己joiin自己没有意义)


代码示例:

package com.rongyu;

public class ThreadMethod {
    public static void main(String[] args) {
//        testSleep();
//        testYield();
        testJoin();
    }

    /*
      sleep,当前线程暂停一段时间让给别的线程去运行。 sleep是怎么复活的?由设定的睡眠时间决定,等睡眠到规定的时间自动复活
    */
    static void testSleep(){
        new Thread(()->{
            for(int i = 0;i<10;i++){
                System.out.println("A"+i);
                try {
                    Thread.sleep(5000);//毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }).start();
    }

    /*
       Yield,当前线程正在执行的时候停下来进入等待队列,回到等待队列中 在系统的调度算法里还是有可能把你刚yield的线程拿回来继续执行,当然,更大的可能是把其他正在等待的线程拿来执行。
        Yield的意思就是我让出一下cpu,后面谁能抢到我不管
     */
    static void testYield(){
        new Thread(()->{
            for(int i = 0;i<10;i++){
                System.out.println("A"+i);
                if(i%2==0)Thread.yield();
            }
        }).start();

        new Thread(()->{
            for(int i = 0;i<10;i++){
                System.out.println("B"+i);
                if(i%2==0)Thread.yield();
            }
        }).start();
    }

    /*
       join 就是在自己当前线性加入你调用join的线程,当前线程进入等待,等join的线程执行完,再去执行自己当前线程。(自己joiin
       自己没有意义)
     */
    static void testJoin(){
        Thread t1 = new Thread(()->{
            for(int i = 0;i<10;i++){
                System.out.println("Join thread"+i);
                try {
                    Thread.sleep(500);//毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(()->{
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for(int i = 0;i<10;i++){
                System.out.println("Current thread"+i);
                try {
                    Thread.sleep(500);//毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //注意,虽然t2中join的t1,但是想要执行t1,还是要执行t1的start方法的
        t1.start();
        t2.start();
    }
}

5.常见的六种线程状态

线程状态


通常,线程状态分为六种:

  • **(1)NEW:**当我们new一个线程时,还没有调用start()方法,该线程处于新建状态

  • **(2)Runnable(Ready、Running):**当线程对象调用start()方法的时候,线程会被线程调度器来执行,也就是通常说的交给系统来执行,这整个的状态叫做Runable。Runable有两个状态,Ready就绪状态/Running运行状态。就绪状态是说线程被扔到cpu的等待队列中排队等待cpu运行,等正真扔到cpu中运行的时候叫做Running状态。(上文说到的Yeild方法执行后,线程就会由Running状态跑到Ready状态)

  • 在Runnable状态下还有一些状态的变迁,(3)TimedWaiting计时等待(4)Waiting无限等待(5)Blocked阻塞,在同步代码块中,没有得到锁的下线程就会进入阻塞状态,获得锁的时候是Ready就绪状态+Running运行状态。在运行时候如果调用了o.wait(),t.join(),LockSupport.park()就会进入waiting状态,调用o.notify()、o.notifiAll()、LockSupport.unpark()就会再回到Running状态。TimedWaiting按照时间等待,等时间结束自己就回到Running状态(Thread.sleep(time)、o.wait(time)、t.join(time)、LockSupport.parkNanos()、LockSupport.parkUntil()这些都是关于时间等待的方法,具体是进入Waiting还是TimedWaiting,可以自己测试)

  • **(6)Teminated:**结束状态,线程顺利执行完后就会进入Teminated状态。(注意:线程Teminated状态结束后不可再回到new状态,也就不可以再调用start方法。结束就是结束了)

6.知识点

  1. 线程各种状态下 哪些是jvm管理,哪些是操作系统管理?

    上文提到的六种常见状态都由jvm管理,因为jvm管理的时候也要通过操作系统,jvm是跑在操作系统上的一个程序

  2. 线程在什么状态下会被挂起?

    Running的时候,在一个cpu上会跑很多个线程,cpu隔一段时间就会执行这个线程一下,再隔一段时间执行一下另一个线程,这是cpu内部的一个调度。cpu把正在执行的线程抛出去(从Running状态扔回去就叫做线程被挂起)

  3. run()和start()方法的区别

    相当于玩游戏机,只有一个游戏机(cpu),可是有很多人要玩,于是,start是排队!等CPU选中你就是轮到你,你就run(),当CPU的运行的时间片执行完,这个线程就继续排队,等待下一次的run()。

    注:调用start()后,线程会被放到等待队列,等待CPU调度,并不一定要马上开始执行,只是将这个线程置于可动行状态。然后通过JVM,线程Thread会调用run()方法,执行本线程的线程体。

--- 下一章 将 synchronized关键字---

java基础知识

关于作者

BenbobaBigKing
获得点赞
文章被阅读