一、线程安全

  • 线程安全是指在并发环境下,多个线程同时访问共享变量时,能够正确地处理共享变量,使程序功能按照预期的方式执行。

  • 简单来说,线程安全就是多个线程同时访问共享变量时,得到的结果和我们期望的一致。

二、并发与并行

1. 什么是并发

  • 并发(Concurrent)在操作系统中是指在同一时间段内有多个程序在同一个处理器上运行,这些程序都已经启动但尚未运行完毕。

  • 为了实现并发,操作系统采用了🎯分时操作系统🎯的方式。无论是Windows、Linux还是MacOS,都是多用户多任务的分时操作系统。这意味着多个用户可以同时进行多个任务。

  • 然而,在单个CPU的计算机中,✨实际上同一时间只能执行一项任务✨。为了实现看上去的“同时执行多个任务”,操作系统将CPU时间划分为基本相同的时间片,并通过操作系统管理,依次轮流分配这些时间片给不同的用户。

  • 如果一个任务在时间片结束之前还未完成,该任务就会被暂停,放弃CPU,并等待下一轮循环再继续执行。CPU则会被分配给另一个任务使用。

  • 由于计算机的处理速度非常快,只要时间片的间隔适当,一个用户任务在使用完分配给它的一个时间片后,获取下一个CPU时间片之间会有一小段“停顿”时间,但用户并不会察觉到,好像整个系统都由它“独占”一样。

  • 因此,在单个CPU的计算机中,我们看起来可以“同时执行多个任务”,实际上是通过CPU时间片技术来实现并发的。

2. 什么是并行

并行(Parallel)是指在具有多个CPU的系统中,🎶多个进程可以同时执行🎶,互不抢占CPU资源。当一个CPU执行一个进程时,另一个CPU可以同时执行另一个进程。这种方式实现了多个进程的同时进行,称为并行。

Erlang的创始人Joe Armstrong通过一张形象的图解释了并发与并行的区别。

在并发(Concurrent)中,多个进程可以在同一时间段内交替执行,但每个时刻只有一个进程在执行。而在并行中,多个进程可以在同一时刻同时执行,彼此之间互不干扰。

看图吧:
并行

🎏并发是指两个队伍交替使用一台咖啡机。🎏

  • 当一队使用咖啡机的时候,另一队等待,然后两队依次交替使用咖啡机。这种方式中,每个时刻只有一个队伍在使用咖啡机,但任务可以在不同的时间段内进行。

🎏并行是指两个队伍同时使用两台咖啡机。🎏

  • 每个队伍都可以独立地使用一台咖啡机,而不需要等待。这种方式中,两个队伍可以在同一时刻同时使用咖啡机,彼此之间互不干扰。

🐱‍🚀将上述比喻映射到计算机系统中,咖啡机可以看作是CPU,而两个队伍分别对应着两个进程。🐱‍🚀

在并发中,两个进程交替使用CPU资源,每个时刻只有一个进程在执行。而在并行中,两个进程可以在同一时刻同时使用各自的CPU资源,彼此之间互不抢占。

三、进程和线程

1. 进程和线程详细解释

在操作系统中,一个任务被表示为一个进程(Process)的形式。例如,打开一个浏览器将启动一个浏览器进程,打开一个记事本将启动一个记事本进程,打开两个记事本将启动两个记事本进程,打开一个Word将启动一个Word进程。

然而,在多个进程之间切换时,会发生上下文切换的过程,而这个过程会消耗一定的资源。因此,人们开始思考是否可以在一个进程中增加一些"子任务",从而减少上下文切换的成本。例如,在使用Word时,可以同时进行打字、拼写检查、字数统计等子任务,这些子任务共享同一个进程资源,而它们之间的切换不需要进行上下文切换。

在一个进程内部,要同时处理多个任务,就需要同时运行多个"子任务",我们将进程内部的这些子任务称为线程(Thread)。

随着时间的推移,人们进一步划分了进程和线程之间的职责。进程被视为资源分配的基本单元,而线程被视为执行的基本单元。同一个进程的多个线程之间共享资源。

以我们熟悉的Java语言为例,Java程序运行在JVM上,每个JVM实际上是一个进程。所有的资源分配都基于JVM进程。在JVM进程中,可以创建多个线程,这些线程共享JVM资源,并且可以并发执行。

2. 线程的特点

2.1 轻型实体:

线程是操作系统调度的基本单位,相对于进程而言,线程的创建、切换和销毁的开销较小,所占用的资源也较少。

2.2 独立调度和分派的基本单位:

线程是独立调度和分派的基本单位,线程之间可以并发执行,每个线程都有自己的程序计数器、栈和局部变量等,因此可以独立执行不同的任务。

2.3 可并发执行:

在多核或多处理器系统中,多个线程可以同时运行在不同的处理器上,从而实现并发执行,提高系统的吞吐量和响应性能。

2.4 共享进程资源:

线程是进程的一部分,同一个进程中的线程之间共享进程的资源,包括内存空间、文件描述符等。这样可以方便地进行线程之间的数据共享和通信,提高程序的效率和灵活性。

总的来说,线程是轻量级的、独立调度和分派的基本单位,可以并发执行,并且可以共享进程资源,因此在多任务和多线程编程中被广泛应用。

四、共享变量

共享变量是指多个线程可以同时访问和修改的变量。在Java中,共享变量通常是保存在堆内存或方法区中的变量。由于堆和方法区是多个线程共享的数据区域,因此多个线程可以同时对同一个共享变量进行操作。

在多线程编程中,共享变量的访问需要考虑线程安全性。当多个线程同时对一个共享变量进行读写操作时,可能会出现竞态条件(Race Condition)导致数据不一致或错误的结果。为了保证共享变量的正确性,需要使用同步机制,如使用synchronized关键字或Lock接口来保证多个线程对共享变量的安全访问。

五、类变量(静态变量)

1、类变量(静态变量):类变量属于整个类,被该类的所有对象所共享。它在类加载时被初始化,在程序运行期间保持不变。

🐱‍💻类变量存放在方法区(Method Area)中。🐱‍💻

下面是一个简单的示例代码:

public class MyClass {
    public static int count; // 类变量

    public static void main(String[] args) {
        MyClass.count = 10; // 访问类变量

        MyClass obj1 = new MyClass();
        MyClass obj2 = new MyClass();

        System.out.println(obj1.count); // 输出:10
        System.out.println(obj2.count); // 输出:10

        obj1.count = 20; // 修改类变量的值

        System.out.println(obj1.count); // 输出:20
        System.out.println(obj2.count); // 输出:20
    }
}

六、成员变量(实例变量)

成员变量(实例变量):成员变量属于对象,每个对象都有一份成员变量的拷贝。它在对象创建时被分配内存,在对象销毁时释放内存。

🐱‍🐉成员变量存放在堆内存(Heap)中。🐱‍🐉

下面是一个简单的示例代码:

public class MyClass {
    public int count; // 成员变量

    public static void main(String[] args) {
        MyClass obj1 = new MyClass();
        MyClass obj2 = new MyClass();

        obj1.count = 10; // 修改成员变量的值

        System.out.println(obj1.count); // 输出:10
        System.out.println(obj2.count); // 输出:0(默认值)

        obj2.count = 20; // 修改成员变量的值

        System.out.println(obj1.count); // 输出:10
        System.out.println(obj2.count); // 输出:20
    }
}

七、局部变量

局部变量:局部变量只在方法体或代码块中可见,每次方法调用时才会分配内存,方法执行完毕后会被销毁。

🐱‍👓局部变量存放在栈内存(Stack)中。🐱‍👓

下面是一个简单的示例代码:

public class MyClass {
    public void myMethod() {
        int num = 10; // 局部变量

        System.out.println(num);
    }

    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.myMethod(); // 输出:10
    }
}

🐱‍👤注意事项:🐱‍👤

需要注意的是,方法区(Method Area)是线程共享的,而堆内存(Heap)和栈内存(Stack)是线程私有的。因此,类变量在多线程环境下可能会存在并发访问的问题,需要考虑线程安全性。而成员变量和局部变量在每个线程中都有自己的副本,不存在并发访问的问题,不需要额外的线程安全措施。

Logo

更多推荐