拆解Java并发编程!

39 阅读4分钟

1. Java线程

Java线程的执行顺序是由底层的任务调度器去实现的,不由我们控制

1.1. 线程的创建方式

1.1.1. 直接使用Thread

Thread:将线程的创建和任务的创建合并在一起

public static void main(String[] args) {
    Thread t1 = new Thread("t1"){
        @Override
        public void run() {
            log.info("t1");
        }
    };
    t1.start();
    log.info("main");
}

1.1.2. Runnable配合Thread

Runnable:将任务的创建和线程的创建分开,脱离了Thread继承体系,更容易与线程池等高级API结合

public static void main(String[] args) {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            log.info("t1");
        }
    };
    Thread t1 = new Thread(runnable,"t1");
    t1.start();
    log.info("main");
}

1.1.3. FutureTask配合Thread

    FutureTask<String> task = new FutureTask<>(()->{
        log.debug("task");
        return "task";
    });
    Thread t1 = new Thread(task,"t1");
    t1.start();
    log.debug("main");
    log.debug("结果是{}",task.get());
}

1.1.4. lambda表达式

JDK8后会将只有一个抽象方法的接口(例如Runnable)加上@FunctionalInterface表示当前接口可以使用lambda表达式

public static void main(String[] args) {
    Thread t1 = new Thread(()-> log.info("t1"),"t1");
    t1.start();
    log.info("main");
}

1.2. 查看进程线程 1.2.1. window tasklist:查看进程列表 tasklist | findstr xxx:查看指定字符串的进程列表 task kill /F /PID:强制杀死指定PID的进程 1.2.2. Linux ps -ef:查看进程列表 ps -ef | grep java:查看java进程命令 kill -f pid:杀死指定pid的进程 top:查看详细进程信息 1.2.3. java jps:查看所有java进程 jstack pid:查看这个时刻指定pid的java进程所有线程状态 jconsole:图形化界面连接Java进程中的线程运行情况 1.3. 线程运行原理 1.3.1. 栈与栈帧 JavaVirtualMachineStacks(Java虚拟机栈),JVM中由堆、栈、方法区所组成,其中栈内存是给线程用的

每个线程启动后,虚拟机就会为其分配一块栈内存。· 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存· 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法 断点右键指定线程可以调试多个线程运行情况

1.3.2. 线程上下文切换 线程会因为一些原因导致CPU不能继续执行当前线程,转而执行另外一个线程:

当前线程的CPU时间片用完 垃圾回收时,只有执行垃圾回收的线程能运行,其他的工作线程都会被阻塞 有比当前线程更高优先级的线程需要运行 当前线程内部调用了sleep,wait,lock,synchronized,yield,join,park等方法 当线程发生上下文切换(Context Switch)时,操作系统会保留当前线程的运行状态,同时恢复另外一个线程的状态,Java中的完成这个过程的是程序计数器,它会记录下一条字节码指令的地址,线程上下文切换时,由程序计数器提供地址给Java找到继续运行的线程

Java中的线程状态包括:程序计数器,栈帧信息,例如局部变量,操作数栈,返回地址等等 频繁发生线程上下文切换会影响程序的性能 1.4. 线程方法对比 1.4.1. start与run start:

开启线程,并用开启的线程执行run方法. 调用start方法会让线程状态从new状态进入runnable状态 start方法只能调用一次,因为线程处于runnable状态时不能再次开启线程 run:

执行run方法,不开启新的线程,调用的只是普通方法. 调用run方法不会对线程状态产生影响 1.4.2. sleep与yield sleep:

调用sleep方法会让线程状态从running状态进入timewaiting状态 其他线程可以调用interrupt方法打断正在睡眠的线程,此时sleep方法会抛出InterruptedException 睡眠结束的线程可能不会立即被调用 TimeUnit的sleep方法比Thread的sleep方法有更好的可读性 yield:

调用yield方法会让线程状态从running状态进入runnable状态,然后调度其他同优先级的runnable状态线程,如果没有同优先级的线程,当前线程还是会运行,相当于是当前线程做出一次让步 具体的实现依赖于操作系统的任务调度器 ————————————————