Java 并发编程基本概念

192 阅读3分钟

并发编程

并发和并行

并行:不同程序交替执行,只有多核CPU才可以达到并行。

并发:不同程序同时执行,通过CPU快速切换,感觉上是同时在运行。

进程、线程

进程(Process):是计算机中的程序关于某数据集合上的一次运行活动,是对于程序的抽象。是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

线程(Thread):是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程中可以有多个线程并发执行,每条线程可以并行执行不同的任务。

进程和线程的区别:

  1. 根本区别:进程是操作系统进行资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
  2. 一个进程由一个或多个线程组成。进程之间是独立的,但线程之间会共享内存,文件等等。

线程安全

当对个线程同时读写同一对象时,会使得结果并非预期。

运行结束后,CNT 并非预期为 0 ,此时是线程不安全。

public class Main {
    private static int CNT = 0;
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                CNT++;
            }).start();
        }
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                CNT--;
            }).start();
        }
        System.out.println(CNT);
    }
}

临界区与竞争条件

临界区:一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区,此代码需要考虑线程安全问题。

竞态条件:多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。

局部变量的线程安全性

局部变量保存在各自线程中,是独有的,不存在线程安全问题。

局部变量引用的对象保存在中,对于所有线程都是可见的,因此需要考虑线程安全问题。

线程活跃性

死锁

进程互相等待对方所占的资源,于是两者都不能执行而处于永远等待状态,此现象称为死锁。

活锁

线程互相改变对方线程退出的条件,导致退出条件永远不能得到满足,导致死锁。

饥饿

线程无法获得足够的资源运行,比如无法获得时间片,因此发生饥饿现象。

线程的启动方式

可以通过继承Thread类或者实现Runable接口两种方法启动线程。

注意项:

  1. run()相当于直接调用对象的run()方法,并不会新启线程。start()会新启动一个线程间接调用run()方法。

  2. 线程的start()方法的含义:当前线程告诉 JVM ,只要线程规划器空闲,立刻启动调用start()方法的线程。

继承Thread类

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        new Thread(new MyThread()).start();
        new Thread(new MyThread()).start();
    }
}

实现Runable接口

class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        new Thread(new MyThread()).start();
        
        // lambda since jdk1.8
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
            }
        }).start();
    }
}

线程的状态

线程共有6种状态。

image.png

New(新创建)

当使用new新建一个Thread实例,并且此时未调用start()方法的时候,意味着该线程未开始执行,该线程处于New状态,在运行之前仍需一些工作需要完成。

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
            }
        });
    }
}

Runnable(运行状态)

当调用start()方法后,线程就处于Runnable状态,此时并不会立即执行,需要等待操作系统的调度。

线程转为Runnable状态

  1. 新线程启动。
public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println(i);
            }
        });

        thread.start();
    }
}

Blocked(阻塞)

当线程等待资源或者锁时会进入阻塞状态,需要等待资源释放。

Waiting(等待)

表明线程进入等待状态,表示当前线程需要等待其他线程的一些特定动作(通知或中断)。

Timed Waiting(超时等待)

在等待的基础上增加了超时限制,超时时间到达后会返回到运行状态。

Terminated(被终止)

随着run()方法的执行完毕,线程也随之终止,表明代码已经执行完毕。