一、线程与进程

  线程:一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。多线程是多任务的一种特别形式,但多线程使用了更小的资源开销。

  进程:一个进程包括有操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在。它必须是进程的一部分。一个进程一直运行直到所有的非守护线程都结束运行后才能结束。

  进程 线程
定义

进程是指处于运行中的程序,并且具有一定的独立功能。进程是系统进行资源分配和调度的一个单位。

当程序进入内存时,即为进程。

线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程。

线程可以拥有自己的堆栈,自己的程序计数器和局部变量,但不能拥有系统资源。

它与父进程的其他线程共享该进城的所有资源。

特点

1)独立性:进程是系统中独立存在的实体,它可以独立拥有资源,每一个进程都有自己独立的地址空间,

没有进程本身的运行,用户进程不可以直接访问其他进程的地址空间。

2)动态性:进程和程序的区别在于进程是动态的,进程中有时间的概念,进程具有自己的生命周期和各种

不同的状态。

3)并发性:多个进程可以在单个处理器上并发执行,互不影响。

1)线程可以完成一定任务,可以和其他线程共享父进程的共享变量和部分环境,相互协作来完成任务。

2)线程是独立运行的,其不知道进程中是否还有其他线程存在。

3)线程的执行是抢占式的,也就是说,当前执行的线程随时可能被挂起,一边运行另一个线程。

4)一个线程可以创建或撤销另一个线程,一个进程中的多个线程可以并发执行。

二、线程的生命周期

                                          

新建状态(New)

  使用new关键字和Thread类或七子类建立一个线程对象后,该线程对象就处于新建状态。此时仅由JVM为其分配内存,并初始化其成员变量的值。它保持这个状态知道程序start()这个线程。

就绪状态(runnable)

  当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,Java虚拟机会为其创建方法调用栈和程序计数器,等待JVM里线程调度器的调度。

运行状态(running)

  如果就绪状态的线程获取CPU资源,就可以执行run方法,此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

阻塞状态(blocked)

  线程因为某种原因放弃了CPU使用权,暂时停止运行。直到线程进入可运行状态,才有机会再次获得CPU timeslice 转到运行状态。

  如果一个线程执行了sleep(睡眠),suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

  1)等待阻塞:运行状态中的线程执行wait方法,使线程进入到等待阻塞状态;

  2)同步阻塞:线程在获取synchronized同步锁失败(因为同步锁被其他线程占用);

  3)其他阻塞:通过调用线程的sleep()或join()发出了IO请求时,线程就会进入到阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或IO请求处理完毕,线程重新转入就绪状态。

死亡状态(dead)

  一个运行状态的线程完成任务或其他终止条件发生时,该线程就切换到终止状态。

  正常结束:run()或call()方法执行完成,线程正常结束。

  异常结束:线程抛出一个未捕获的Exception或Error。

  调用stop:直接调用该线程的stop()方法结束该线程(该方法容易导致死锁,不推荐使用)。

 为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true;如果线程仍旧是new状态且不是可运行的,或线程死亡了,则返回false。

三、线程优先级

  每个线程都有一个优先级,方便操作系统确定线程的调度顺序。Java线程的优先级是一个整数,其取值范围是1(Thread.MIN_PRIORITY)-10(Thread.MAX_PRIORITY)。

  默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。具有较高优先级的线程在低优先级的线程之前分配处理器资源。但线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

四、创建线程的方式

1)继承Thread类:Thread类本质上是实现了Runnable接口的一个实例。启动线程的方法就是通过Thread类的start()方法。它是一个native方法,它将启动一个新线程,并执行其中的run()方法。

  A. 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为方法体。

  B. 创建Thread子类的实例,即创建了线程对象。

  C. 调用线程对象的start()方法来启动该线程。

 public class MyThread extends Thread {
public void run() {
    System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
myThread1.start();

2)实现Runnable接口

  A. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

  B. 创建Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

  C. 调用线程对象的start()方法来启动该线程。

 public class MyThread extends OtherClass implements Runnable {
  public void run() {
System.out.println("MyThread.run()");
}
}
//启动 MyThread,需要首先实例化一个 Thread,并传入自己的 MyThread 实例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
target.run(); //当传入一个 Runnable target 参数给 Thread 后,Thread 的 run()方法就会调用
public void run() {
if (target != null) {
target.run();
}
}

3)通过Callable和Future创建线程

  i. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

  ii. 创建Callable实现类的实例,使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值。

  iii. 使用FutureTask对象作为Thread对象的target创建并启动新线程。

  iv. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

 //创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 执行任务并获取 Future 对象
Future f = pool.submit(c);
list.add(f);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从 Future 对象上获取任务的返回值,并输出到控制台
System.out.println("res:" + f.get().toString());
}

4)基于线程池的方式

  利用线程池不用new就可以创建线程,线程可复用,利用Executors创建线程池。

  ExecutorService threadPool = Executors.newFixedThreadPool(10);    // 创建线程池
while(true) {
threadPool.execute(new Runnable() { // 提交多个线程任务,并执行
public void run() {
  System.out.println(Thread.currentThread().getName() + " is running ..");
  try {
  Thread.sleep(3000);
  } catch (InterruptedException e) {
  e.printStackTrace();
  }
}
});
}

五、终止线程的方式

1、正常运行结束:程序运行结束,线程自动结束。

2、使用退出标志退出线程

  一般run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如:最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出,代码示例:

 public class ThreadSafe extends Thread {
public volatile boolean exit = false;
public void run() {
while (!exit){
//do something
     }
}
}

  定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false。在定义exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit值。

3、Interrupt方法结束线程

 使用interrupt()方法来中断线程有两种情况:

  1)线程处于阻塞状态:如使用了sleep,同步锁的wait、socket中的receiver、accept等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,会抛出InterruptException异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后break跳出循环状态,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用interrupt方法线程就会结束,实际上是错的,一定要先捕获InterruptedException异常之后通过break来跳出循环,才能正常结束run方法。

  2)线程未处于阻塞状态:使用isInterrupted()方法判断线程的中断标志来退出循环。当使用interrupt方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。

  public class ThreadSafe extends Thread {
public void run() {
    while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出
  try{
Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出
}catch(InterruptedException e){
e.printStackTrace();
      break;//捕获到异常之后,执行 break 跳出循环
}
}
}
}

4、Stop方法终止线程(线程不安全)

  程序中可以直接使用thread.stop()来强行终止线程,但是stop方法很危险,就像突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,不安全主要是:

    thread.stop()调用后,创建子线程的线程就会抛出ThreadDeatherror的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁被突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因此,并不推荐使用stop方法来终止线程。

六、线程同步的方式

  临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适应控制数据访问。

  互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以可以保证公共资源不会同时被多个线程访问。

  信号量:它允许多个线程统一时刻访问同一资源,但是需要限制同一时刻访问此资源的最大线程数目。信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中PV操作相似。

  事件(信号):通过通知操作的方式来保持对线程的同步,还可以方便的实现多线程的优先级比较的操作。

七、进程同步与互斥的区别

  互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排他性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

  同步:是指在互斥的基础上(大多数情况),通过其他机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

  同步体现的是一种协作性,护持体现的是一种排他性。

八、Java后台线程(守护线程)

 守护线程(Daemon):也称服务线程,是后台线程。为用户线程提供公共服务,在没有用户线程可服务是会自动离开。

  1、优先级较低,用于为系统中的其他对象和线程提供服务。

  2、设置:通过setDaemon(true)来设置线程为“守护线程”。

  3、垃圾回收线程就是守护线程,它始终在低级别的状态运行,用于监视和管理系统中的可回收资源。

  4、生命周期:守护线程不依赖于终端但依赖于系统,与系统“同生共死”。

守护线程和用户线程有什么区别?

  当我们在Java程序中创建一个线程,它就被称为用户线程。将一个用户线程设置为守护线程的方法就是在调用start()方法之前,调用对象得setDamon(true)方法。一个守护线程是在后台执行并且不会阻止JVM种植的线程,守护线程的作用是为其他线程的运行提供便利服务。当没有用户线程在运行的时候,JVM关闭程序并且退出。一个守护线程创建的子线程依然是守护线程。

  守护线程的一个典型例子就是垃圾回收器。

九、如何在两个线程之间共享数据?

  Java里面进行多线程痛心的主要方式就是共享内存的方式,共享内存主要的关注点有两个:可见性和有序性。Java内存模型(JMM)解决了可见性和有序性的问题,而锁解决了原子性的问题,理想情况下我们希望做到“同步”和“互斥”。有以下常规实现方法:

  1、将数据抽象成一个类,并将数据的操作作为这个类的方法,这么设计可以很容易做到同步,只要在方法上加“synchronized”

 public class MyData {
private int j=0;
public synchronized void add(){
j++;
System.out.println("线程"+Thread.currentThread().getName()+"j 为:"+j);
}
public synchronized void dec(){
j--;
System.out.println("线程"+Thread.currentThread().getName()+"j 为:"+j);
}
public int getData(){
return j;
}
}
public class AddRunnable implements Runnable{
MyData data;
public AddRunnable(MyData data){
this.data= data;
}
public void run() {
data.add();
}
}
public class DecRunnable implements Runnable {
MyData data;
public DecRunnable(MyData data){
this.data = data;
}
public void run() {
data.dec();
}
}
public static void main(String[] args) {
MyData data = new MyData();
Runnable add = new AddRunnable(data);
Runnable dec = new DecRunnable(data);
for(int i=0;i<2;i++){
new Thread(add).start();
new Thread(dec).start();
}
}

  2、将Runnable对象作为一个类的内部类,共享数据作为这个类的成员变量,每个线程对共享数据的操作方法也封装在外部类,以便实现对数据的各个操作的同步和互斥,作为内部类的各个Runnable对象调用外部类的这些方法。

 public class MyData {
private int j=0;
public synchronized void add(){
j++;
System.out.println("线程"+Thread.currentThread().getName()+"j 为:"+j);
}
public synchronized void dec(){
j--;
System.out.println("线程"+Thread.currentThread().getName()+"j 为:"+j);
}
public int getData(){
return j;
}
}
public class TestThread {
public static void main(String[] args) {
final MyData data = new MyData();
for(int i=0;i<2;i++){
new Thread(new Runnable(){
public void run() {
data.add();
}
}).start();
new Thread(new Runnable(){
public void run() {
data.dec();
}
}).start();
}
}
}

十、多线程中的常见问题?

1、Java创建线程之后,直接调用run()方法和start()方法的区别?

  start()方法来启动线程,并在新线程中运行run()方法,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start方法来启动一个线程,这时此线程是处于就绪状态,并没有运行,然后通过此Thread类调用run()方法来完成其运行操作,这里方法run称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行run函数中的代码。run() 方法运行结束,此线程终止,然后CPU再调度其他线程。

  只有调用了start方法,才会表现出多线程的特性,不同线程的run方法里面的代码交替执行。如果直接调用run()方法的话,会把run()方法当作普通方法来调用,会在当前线程中执行run()方法,而不会启动新线程来运行run()方法。程序还是要顺序执行,必须等待一个线程的run方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run方法里面的代码。

2、Java中 Runnable 接口和 Callable 接口的区别?

  Runnable接口中的run()方法的返回值为void(即不能有返回值),它做的事情只是纯粹地去执行run()方法中的代码而已。Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

  Callable的call方法可抛出异常,而Runnable的run方法不能抛出异常。

  Callable+Future/FutureTask可以获取多线程运行的结果,可以在等待时间太长没获取需要的数据的情况下取消该线程的任务。

3、Sleep()方法和Wait()方法的区别?

  sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器(监视对象同步),sleep方法不会放弃这个对象的监视器,且可以在任何地方使用; wait方法会放弃这个对象的监视器,并且wait只能在同步控制方法或者同步控制块中使用。

  sleep方法属于Thread类,是静态方法;sleep方法导致了程序暂停执行指定的时间,让出CPU给其他线程,但是它的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。在调用sleep方法过程中,线程不会释放对象锁

  wait方法属于Object类,和notify()或notifyAll()方法配套使用,来实现线程间通信。当调用方法时,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对对象调用notify方法后本线程才进入对象锁定池,准备获取对象锁进入运行状态。

  特别注意:sleep 和 wait 必须捕获异常(Thread.sleep()和Object.wait()都会抛出InterruptedException),notify和notifyAll不需要捕获异常。

4、线程让步(yield)

  yield会使当前线程让出CPU执行时间片,与其他线程一起重新竞争CPU时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到CPU时间片,但这又不是绝对的,有的操作系统对线程优先级并不敏感。

5、join 等待其他线程终止

  join方法,等待其他线程终止,在当前线程中调用一个线程的join方法,则当前线程转为阻塞状态,当另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待CPU。

  为什么要用join方法?

    很多情况下,主线程生成并启动子线程,需要用到子线程返回结果,也就是主线程需要在子线程结束后再结束,这时就要用到join方法。

System.out.println(Thread.currentThread().getName() + "线程运行开始!");
Thread6 thread1 = new Thread6();
thread1.setName("线程 B");
thread1.join();
System.out.println("这时 thread1 执行完毕之后才能执行主线程");

6、一个类是否可以同时继承Thread和实现Runnable接口?

  可以。比如下面的程序可以通过编译。因为Test类从Thread类中继承了run()方法,这个run()方法可以被当作对Runnable接口的实现。

public class Test extends Thread implements Runnable{
public static void main(String[] args){
Thread t = new Thread(new Test());
t.start();
}
}

Java之线程与进程的更多相关文章

  1. Java多线程--线程及相关的Java API

    Java多线程--线程及相关的Java API 线程与进程 进程是线程的容器,程序是指令.数据的组织形式,进程是程序的实体. 一个进程中可以容纳若干个线程,线程是轻量级的进程,是程序执行的最小单位.我 ...

  2. Java并发编程:进程和线程之由来

    Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...

  3. 线程和进程详解(以java为例具体说明)

    详细参见http://ifeve.com/java-concurrency-thread-directory/ 一.线程概述 线程是程序运行的基本执行单元.当操作系统(不包括单线程的操作系统,如微软早 ...

  4. Java多线程基础:进程和线程之由来

    转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...

  5. java并发编程:进程和线程

    java并发编程涉及到很多内容,当然也包括多线程,再次补充点相关概念 原文地址:http://www.cnblogs.com/dolphin0520/p/3910667.html 一.操作系统中为什么 ...

  6. Java并发编程:进程和线程之由来__进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能

    转载自海子:http://www.cnblogs.com/dolphin0520/p/3910667.html Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨 ...

  7. Java并发编程:线程和进程的创建(转)

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

  8. Java并发基础:进程和线程之由来

    转载自:http://www.cnblogs.com/dolphin0520/p/3910667.html 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程. ...

  9. java线程与进程

    Java线程与进程 进程与线程的关系 进程里面至少有一个线程,进程间的切换会有较大的开销 线程必须依附在进程上,同一进程共享代码和数据空间 多线程的优势 多线程可以达到高效并充分利用cpu 线程使用的 ...

随机推荐

  1. go语言的常量

    Go 语言常量 常量是一个简单值的标识符,在程序运行时,不会被修改的量. 常量中的数据类型只可以是布尔型.数字型(整数型.浮点型和复数)和字符串型. 常量的定义格式: const identifier ...

  2. Linux系统:Centos7下搭建ClickHouse列式存储数据库

    本文源码:GitHub·点这里 || GitEE·点这里 一.ClickHouse简介 1.基础简介 Yandex开源的数据分析的数据库,名字叫做ClickHouse,适合流式或批次入库的时序数据.C ...

  3. 程序员修神之路--redis做分布式锁可能不那么简单

    菜菜哥,复联四上映了,要不要一起去看看? 又想骗我电影票,对不对? 呵呵,想去看了叫我呀 看来你工作不饱和呀 哪有,这两天我刚基于redis写了一个分布式锁,很简单 不管你基于什么做分布式锁,你觉得很 ...

  4. C#中 EF 性能优化

    https://www.cnblogs.com/chenwolong/p/7531955.html EF使用AsNoTracking(),无跟踪查询技术(查询出来的数据不可以修改,如果你做了修改,你会 ...

  5. echarts 柱状图+折线+文字倾斜及省略

    效果图: 代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...

  6. 激活windows操作系统的命令行指令

    1.开始菜单中搜索命令提示符,右键管理员运行 2.为了避免激活的失败,首先卸载已经过期的密钥 输入以下命令:slmgr.vbs /upk 回车确认之后会弹出提示“已成功卸载了产品密钥” 3.下一步是将 ...

  7. Redis专题——Redis管理工具

    一.安全性 1.运行环境 Redis以简洁为美,其安全性没有太多操作,要求在生产系统中外界不能直接连接Redis进行操作,而必须经过程序中转后,由程序进行操作. 即,redis要求运行在可信的环境中. ...

  8. sshd 启动后 出现:Could not load host key: /etc/ssh/ssh_host_rsa_key

    今天在启动sshd时,出现了如下问题,导致客户端不能成功连接服务端,不能建立ssh连接: 有两个提示 Could not load host key: /etc/ssh/ssh_host_rsa_ke ...

  9. 201871010126 王亚涛 《面向对象程序设计 (Java)》第十六周学习总结

    内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/nwnu-daizh/p/12 ...

  10. configparser读取配置文件时的相对路径问题

    学习接口测试时,当我把配置文件xx.config和读取配置文件的模块read_config.py放在项目下的同一个包config里时,只需传入文件名xx.config即可实现对配置文件的读取. 但是当 ...