线程和进程
进程: 操作系统会以进程为单位,分配系统资源(CPU时间片、内存等资源),进程是 资源分配的最小单位 (进程是代码在数据集合上的一次运行活动, 是系统进行资源分配和调度的基本单位)
线程:线程,有时被称为轻量级进程(Lightweight Process,LWP),是操作系统调度 (CPU调度)执行的最小单位
线程是进程中的一个实体,线程本身是不会独立存在的。进程是代码在数据集合上的一次运行活动, 是系统进行资源分配和调度的基本单位, 线程则是进程的一个执行路径, 一个进程中至少有一个线程,进程中的多个线程共享进程的资源。操作系统在分配资源时是把资源分配给进程的, 但是CPU 资源比较特殊, 它是被分配到线程的, 因为真正要占用CPU 运行的是线程, 所以也说线程是CPU 分配的基本单位。
进程与线程的区别
1.进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
2.进程拥有共享的资源,如内存空间等,供其内部的线程共享 进程间通信较为复杂
2.1 同一台计算机的进程通信称为 IPC(Inter-process communication)
2.2 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议, 例如 HTTP
3.线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一 个共享变量
4.线程更轻量,线程上下文切换成本一般上要比进程上下文切换低
进程间的通信
通过管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的 通信,有些管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。(缺点 :半双工通信,一条管道只能一个进程写,一个进程读。一个进程写完后,另一个进程才能读,反之同理。)
共享内存(shared memory):可以说这是最有用的进程间通信方式。它使得多个进 程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更 新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。共享内存解决了消息队列存在的内核态和用户态之间数据拷贝的问题
消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式 中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息; 对消息队列有读权限得进程则可以从消息队列中读取信息。
套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之 间的进程间通信,应用非常广泛。
信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互 斥手段。
信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方 式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果 上可以说是一致的
四种线程同步互斥的控制方法
1.临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访 问。(在一段时间内只允许一个线程访问的资源就称为临界资源)。
2.互斥量:为协调共同对一个共享资源的单独访问而设计的。
3.信号量:为控制一个具有有限数量用户资源而设计。
4.事件:用来通知线程有一些事件已发生,从而启动后继任务的开始
上下文切换(Context switch)
上下文切换是指CPU(中央处理单元)从一个进程或线程到另一个进程或线程的切换。
进程是程序的一个执行实例。在Linux中,线程是轻量级进程,可以并行运行,并与父进程(即创建线程的 进程)共享一个地址空间和其他资源。
上下文是CPU寄存器和程序计数器在任何时间点的内容。
寄存器是CPU内部的一小部分非常快的内存(相对于CPU外部较慢的RAM主内存),它通过提供对常用值 的快速访问来加快计算机程序的执行。
程序计数器是一种专门的寄存器,它指示CPU在其指令序列中的位置,并保存着正在执行的指令的地址 或下一条要执行的指令的地址,这取决于具体的系统。
![](https://i-blog.csdnimg.cn/blog_migrate/953a7e5702fcfe19b540b37b7d754a1a.png)
上下文切换可以更详细地描述为内核(即操作系统的核心)对CPU上的进程(包括线程)执行以下活动:
1. 暂停一个进程的处理,并将该进程的CPU状态(即上下文)存储在内存中的某个地方
2. 从内存中获取下一个进程的上下文,并在CPU的寄存器中恢复它
3. 返回到程序计数器指示的位置(即返回到进程被中断的代码行)以恢复进程。
Java中的线程
集成Thread
public class ThreadDemo {
public static void main(String[] args ){
MyThread thread = new MyThread("demo-test");
thread.start();
}
}
集成 Thread并重写了run() 方法。在main 函数里面创建了一个MyThread 的实例,然后调用该实例
的start 方法启动了线程。需要注意的是,当创建完thread 对象后该线程并没有被启动执行,直到调用了start 方法后才真正启动了线程。其实调用start 方法后线程并没有马上执行而是处于就绪状态, 这个就绪状态是指该
线程已经获取了除CPU 资源外的其他资源,等待获取CPU 资源后才会真正处于运行状态。一旦run 方法执
行完毕, 该线程就处于终止状态。
使用继承方式
优点:
在run() 方法内获取当前线程直接使用this 就可以了,无须使用Thread. currentThread() 方法;
缺点:
不好的地方是Java 不支持多继承,如果继承了Thread 类,那么就不能再继承其他类。另外任务与代码没有分离, 当多个线程执行一样的任务时需要多份任务代码,而Runable 则没有这个限制。
class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始。");
}
public MyThread(String name) {
super(name);
}
}
实现runnable
public class ThreadDemo {
public static void main(String[] args ){
new Thread(new MyThreadRunnable(),"demo-test").start();
}
}
/**
* MyThreadRunnable 可以继承其他类。但是上面介绍的两种方式都有一个缺点,就是任务没有返回值
*/
class MyThreadRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始。");
}
}
}
实现Callable/Future
public class ThreadDemo {
public static void main(String[] args )throws Exception{
// 创建异步任务
FutureTask<String> futureTask = new FutureTask<>(new CallableDemo());
// 启动线程
new Thread(futureTask).start();
// 获取异步任务结果
String result = futureTask.get();
System.out.println(result);
}
}
/**
* 实现接口 ,可以获取到结果的返回值信息
*/
class CallableDemo implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName() + ": " + LocalDateTime.now();
}
}
}
线程的几种状态流转
![](https://i-blog.csdnimg.cn/blog_migrate/937e7a44131a9c29a84c890f3f2ad91a.png)
这6中状态是Thread中定义的线程的状态,并非操作系统中的状态
Thread 类中定义的状态
public enum State {
/**
* Thread state for a thread which has not yet started.
* 新建状态 ,线程被创建出来了,但是还没有调用start()方法
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
* 运行状态 Java程序将操作系统中的 就绪 和运行状态 归并程了 运行状态
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
* 阻塞状态 标识线程被阻塞
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
* 等待状态 ,标识线程进入了等待的状态,进入了这个状态需要其他线程做一些操作(notify 或者中断等)
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul> 超时等待,有时间限制,超过了等待的时间 会自动运行
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
* 线程运行结束 ,终止状态
*/
TERMINATED;
}
需要注意的是,操作系统中的线程除去 new 和 terminated状态,一个线程真实存在的状态,只有:
ready:表示线程已经被创建,正在等待系统调度分配CPU使用权。
running:表示线程获得了CPU使用权,正在进行运算
waiting :表示线程等待(或者说挂起),让出CPU资源给其他线程使用
在加上新建状态(new)和死亡状态(TERMINATED),一共5种
Java线程执行为什么不能直接调用run()方法,而要调用start()方法
我的理解是 新建一个thread 会进入到新建状态,调用了start()方法,会启动一个线程并使这个线程进入就绪状态,当线程获取到时间片就可以运行了。start()会做一些线程启动的相关操作,而run()只是一个普通方法。直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。thread类中注释也提到,
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
Java线程属于内核级线程
内核级线程(Kernel Level Thread ,KLT):它们是依赖于内核的,即无论是用户进程中的线 程,还是系统进程中的线程,它们的创建、撤消、切换都由内核实现。
用户级线程(User Level Thread,ULT):操作系统内核不知道应用线程的存在。
Java线程的调度机制
线程调度是指系统为线程分配处理器使用权的过程,主要调度方式分两种,分别是协同式线程调 度和抢占式线程调度
协同式线程调度
线程执行时间由线程本身来控制,线程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。最大好处是实现简单,且切换操作对线程自己是可知的,没啥线程同步问题。坏 处是线程执行时间不可控制,如果一个线程有问题,可能一直阻塞在那里。
抢占式线程调度
每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Java中, Thread.yield()可以让出执行时间,但无法获取执行时间)。线程执行时间系统可控,也不会有一个线程导致整个进程阻塞。
Java线程调度就是抢占式调度
希望系统能给某些线程多分配一些时间,给一些线程少分配一些时间,可以通过设置线程优 先级来完成。Java语言一共10个级别的线程优先级(Thread.MIN_PRIORITY至
Thread.MAX_PRIORITY),在两线程同时处于ready状态时,优先级越高的线程越容易被系统 选择执行。但优先级并不是很靠谱,因为Java线程是通过映射到系统的原生线程上来实现的,所 以线程调度最终还是取决于操作系统
Thread.sleep()
Thread类中有一个静态的sleep方法,当一个执行中的线程调用了Thread的sleep方法后,当前线程从 Running 进入TIMED_WAITING状态。调用线程会暂时让出指定时间的执行权,也就是在这期间不参与CPU的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的。指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,然后参与CPU的调度,获取到CPU资源后就可以继续运行了。如果在睡眠期间其他线程调用了该线程的interrupt()方法中断了该线程,则该线程会在调用sleep方法的地方抛出InterruptedException异常而返回。并且会清除中断标志.睡眠结束后的线程未必会立刻得到执行 sleep当传入参数为0时,和yield相同.
Thread.yeild()
Thread类中有一个静态的yield方法,当一个线程调用yield方法时,实际就是在暗示
线程调度器当前线程请求让出自己的CPU使用,但是线程调度器可以无条件忽略这个暗示。我们知道操作系统是为每个线程分配一个时间片来占有CPU的,正常情况下当一个线程把分配给自己的时间片使用完后,线程调度器才会进行下一轮的线程调度,而当一个线程调用了Thread类的静态方法yield时,是在告诉线程调度器自己占有的时间片中还没有使用完的部分自己不想使用了,这暗示线程调度器现在就可以进行下一轮的线程调度。当一个线程调用yield方法时,当前线程会让出CPU使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个线程优先级最高的线程,当然也有可能会调度到刚刚让出CPU的那个线程来获取CPU执行权.
Thread.join()
等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之 后才能继续运行的场景
为什么要用join()方法
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
join方法的作用
即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
Thread.interrupt()
中断线程,例如,当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程A实际并没有被中断,它会继续往下执行。如果线程A因为调用了wait系列函数、join方法或者sleep方法而被阻塞挂起,这时候若线程B调用线程A的interrupt()方法,线程A会在调用这些方法的地方抛出InterruptedException 异常而返回。jdk中还有个stop()终止线程,已经被弃用。
boolean isInterrupted()方法:检测当前线程是否被中断,如果是返回true,否则返回false。
public boolean isInterrupted(){
// false 表示不清处中断标志
return isInterrupted(false);
}
b. boolean interrupted() 方法:检测当前线程是否被中断,如果是返回true,否则返回false。与islnterrupted不同的是,该方法如果发现当前线程被中断,则会清除中断标志,并且该方法是static 方法,可以通过Thread类直接调用。另外从下面的代码可以知道,在interrupted()内部是获取当前调用线程的中断标志而不是调用interrupted()方法的实例对象的中断标志。
public static boolean interrupted() {
// 清除标志位
return currentThread().isInterrupted(true);
}