一些关于线程的事
线程
定义
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
###线程的特点
在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。
后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程,线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。
在多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。
1) 轻型实体
线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。
线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。
2)独立调度和分配的基本单位
在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
3)可并发执行
在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
4)共享进程资源。
在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
线程的同步
同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。
进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。
同步需要解决的问题
在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,目前实现线程同步的方法有很多,临界区对象就是其中一种。
java单线程
1234567891011121314151617 > package com.haoeasy.thread;> /**> * Created by HUANGKUN on 2018/4/1.> */> public class SingleThread {> /*单线程*/> public static void pintfor(){> for(int i = 0;i<10;i++){> String threadName = Thread.currentThread().getName();> System.out.println(threadName);> }> }> public static void main(String []args){> SingleThread.pintfor();> }> }>
>
out : 10个main.main函数的默认线程是main。
java创建线程
单线程程序简单明了,但有时无法满足特定的需求。如一个文字处理的程序,我在打印文章的同时也要能对文字进行编辑,如果是单线程的程序则要等打印机打印完成之后你才能对文字进行编辑,但打印的过程一般比较漫长,这是我们无法容忍的。如果采用多线程,打印的时候可以单独开一个线程去打印,主线程可以继续进行文字编辑。在程序需要同时执行多个任务时,可以采用多线程。
在程序需要同时执行多个任务时,可以采用多线程。Java给多线程编程提供了内置的支持,提供了两种创建线程方法:1.通过实现Runable接口;2.通过继承Thread类。
Thread是JDK实现的对线程支持的类,Thread类本身实现了Runnable接口,所以Runnable是显示创建线程必须实现的接口; Runnable只有一个run方法,所以不管通过哪种方式创建线程,都必须实现run方法。我们可以看一个例子。
####实现runnable
12345678910111213141516171819202122232425262728293031 > package com.haoeasy.thread;>> /**> * Created by HUANGKUN on 2018/4/1.> */> /**> * 通过实现runnable方法> * */> public class ThreadA implements Runnable{> private Thread thread;> private String threadName;> public ThreadA(String threadName){> thread = new Thread(this,threadName);> this.threadName = threadName;> }> /**> * 实现run方法> * */>> public void run() {> int i = 0;> for(;i<10;i++){> System.out.println(threadName+":"+i);> }> }> public void start(){> thread.start();> }> }>>
实现thread
1234567891011121314151617 > public class ThreadB extends Thread{> private String threadName;> public ThreadB(String threadName){> super();> this.threadName = threadName;> }> //实现run方法>> public void run(){> int i = 0;> for(;i<10;i++){> System.out.println(threadName+":"+i);> }> }>> }>
>
1234567891011121314 > package com.haoeasy.thread;>> /**> * Created by HUANGKUN on 2018/4/1.> */> public class MultiThread {> public static void main(String []args){> ThreadA threadA = new ThreadA("ThreadA");> ThreadB threadB = new ThreadB("ThreadB");> threadA.start();> threadB.start();> }> }>
>
out:
123456789101112131415161718192021 > ThreadA:0> ThreadB:0> ThreadA:1> ThreadB:1> ThreadA:2> ThreadA:3> ThreadA:4> ThreadA:5> ThreadB:2> ThreadA:6> ThreadB:3> ThreadA:7> ThreadB:4> ThreadA:8> ThreadA:9> ThreadB:5> ThreadB:6> ThreadB:7> ThreadB:8> ThreadB:9>
>
说明:上面的例子中例举了两种实现线程的方式。大部分情况下选择实现Runnable接口的方式会优于继承Thread的方式,因为:
- 从 Thread 类继承会强加类层次;
- 有些类不能继承Thread类,如要作为线程运行的类已经是某一个类的子类了,但Java只支持单继承,所以不能再继承Thread类了。