一、线程与进程

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

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

  进程 线程
定义

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

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

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

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

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

特点

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. Java学习笔记之面向对象、static关键字

    一周Java学习总结 今天就总结理清一下关于面向对象和面向过程的程序设计的一些不同特点,以及讲下static关键字. 面向对象 现在接触的Java是面向对象的,现在的程序开发几乎都是以面向对象为基础的 ...

  2. 谁说程序员不浪漫?Python导出微信聊天记录生成爱的词云图

    明天又双叒叕是一年一度的七夕恋爱节了! 又是一波绝好的机会!恩爱秀起来! 购物车清空!礼物送起来!朋友圈晒起来!   等等! 什么?! 你还没准备好七夕礼物么? 但其实你不知道要送啥? 原来又双叒叕要 ...

  3. SSM框架之Spring(5)JdbcTemplate及spring事务控制

    Spring(5)JdbcTemplate及spring事务控制 ##1.JdbcTmeplate 它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装.spring ...

  4. 利用Azure虚拟机安装Dynamics 365 Customer Engagement之七:安装前端服务器及部署管理器

    我是微软Dynamics 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面 ...

  5. 通过 RxSwift 优雅使用 NotificationCenter

    原文 纯粹的官方代码使用NotificationCenter真的很难用,但是有了RxSwift,就变得方便了很多. 修改 Podfile,通过pod引入RxSwift pod 'RxSwift' po ...

  6. angularjs $http请求网络数据并展示

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. LRU的实现(使用list)

    首先是LRU的定义,LRU表示最近最少使用,如果数据最近被访问过,那么将来被访问的几率也更高. 所以逻辑应该是每次都要将新被访问的页放到列表头部,如果超过了list长度限制,就将列表尾部的元素踢出去. ...

  8. Python3+Requests+Excel完整接口自动化框架

    框架整体使用Python3+Requests+Excel:包含对实时token的获取 框架结构图 1.------base -------runmethond.py runmethond:对不同的请求 ...

  9. centos7上搭建开源系统jforum

    centos7上搭建好tomcat,mysql; 将 jforum-2.6.2.war放到tomcat目录的webapps下: 启动tomcat,./startup.sh ,查看webapp下jfor ...

  10. 题解:SPOJ1026 Favorite Dice

    原题链接 题目大意 给你一个n个面的骰子,每个面朝上的几率相等,问每个面都被甩到的期望次数 题解 典型的赠券收集问题. 我们考虑当你手上已有\(i\)种不同的数,从集合中任选一个数得到新数的概率,为\ ...