线程是操作系统能够进行运算的最小单元,是独立调度和分派的基本单元,是进程中的实际运作的单元。一个进程可以有并发执行多个线程,每条线程并发执行不同的任务;同一个进程中的多个线程可以共享该进程中的全部系统资源,例如:虚拟地址空间,文件描述符和信号处理等等。每个线程都有自己的调用栈,寄存器环境,本地存储。
线程实际上只有一点必不可少的,能够保证独立运行的资源。线程的实体包括程序、数据、TCB(Thread Control Block 线程控制块)。
TCB包括:线程状态、线程不运行时被保存的现场资源、一组执行堆栈、存放每个线程的局部变量主存区、访问同一个进程中的主存和其他资源。
线程与进程之间的区别如下:
1、地址空间和其他资源:进程之间相互独立间,统一进程中的各个线程间资源共享,进程内的线程对其他的进程不可见。
2、通信:进程间通信IPC,线程间可以直接读写进程数据段来进行通信--需要进程同步和互斥来协助,以保证数据的一致性。
3、调度和切换:由于线程比进程更小,基本上不拥有系统资源,因此在调度的过程中付出的开销要小得多,线程上下文切换比进程上下文切换要快得多。
4、进程是分配资源的基本单位,线程是独立运行和独立调度的基本单位。
线程的并发安全性:
多线程:多线程中有两个概念就是并行和串行,并行就是同一时刻执行的任务,在时间上是重叠的。串行就是对于单个线程执行多个任务来说,多个任务必须是按照一定的顺序执行的,只有上一个任务完成之后才能执行下一个任务,在时间上是不重叠的。
线程安全:
在多线程并发执行的过程中访问同一数据时,程序是否能继续按照预期的效果去执行,保证数据的一致性,安全性,可见性。
public class ThreadDemo1 {
private int count ;
private void add(){
count ++;
}
private void runThread(){
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
add();
System.out.println(Thread.currentThread().getName()+":" +count);
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
add();
System.out.println(Thread.currentThread().getName()+":" +count);
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
add();
System.out.println(Thread.currentThread().getName()+":" +count);
}
}
}.start();
}
public static void main(String[] args) {
ThreadDemo1 threadDemo1 = new ThreadDemo1();
threadDemo1.runThread();
}
}
期望值为:30000,真正的执行结果:
Thread-2:29995
Thread-2:29996
Thread-2:29997
Thread-2:29998
Thread-2:29999
Process finished with exit code 0
线程的并发就需要保证共享数据的原子性、一致性、可见性。
原子性:对共享的内存中的数据操作时要么全部执行,要么不执行,并且在执行的过程中不能被其他的外界因素打断。
可见性:多线程操作共享内存的时候,执行的结果能够及时的同步到共享内存,确保这个结果对其他线程是可见的。
有序性:程序的执行顺序按照代码的顺序执行,在单线程的环境下,程序的执行都是有序的,但是在多线程的环境下,JMM为了性能的优化,编译器和处理器会对指令进行重排序,程序的执行会变得无序。
各个线程在执行任务的时候是如何操作数据以及存储数据?
由上图可以看出来,线程在对共享内存数据进行操作的时候,需要将操作的变量拷贝到线程工作内存中(读操作),在进行完成一系列的业务逻辑操作之后,需要将更改的变量重写存储到主内存中。然而在线程AB在再将变量存入到主内存的时候,就会出现数据安全性的问题。