基于线程、并发的基本概念(详解)

发布时间 - 2026-01-11 01:32:15    点击率:

什么是线程?

提到“线程”总免不了要和“进程”做比较,而我认为在Java并发编程中混淆的不是“线程”和“进程”的区别,而是“任务(Task)”。进程是表示资源分配的基本单位。而线程则是进程中执行运算的最小单位,即执行处理机调度的基本单位。关于“线程”和“进程”的区别耳熟能详,说来说去就一句话:通常来讲一个程序有一个进程,而一个进程可以有多个线程。

但是“任务”是很容易忽略的一个概念。我们在实际编码中通常会看到这么一个包叫做xxx.xxx.task,包下是XxxTask等等以Task后缀名结尾的类。而XxxTask类通常都是实现Runnable接口或者Thread类。严格来说,“任务”和并发编程没多大关系,就算是单线程结构化顺序编程中,我们也可以定义一个Task类,在类中执行我们想要完成的一系列操作。“任务”我认为是我们人为定义的一个概念,既抽象又具体,抽象在它指由软件完成的一个活动,它可以是一个线程,也可以是多个线程共同达到某一目的的操作,具体在于它是我们认为指定实实在在的操作,例如:定时获取天气任务(定时任务),下线任务……关键就在于不要认为一个任务对应的就是一个线程,也许它是多个线程,甚至在这个任务中是一个线程池,这个线程池处理这个我们定义的操作。

我产生“线程”和“任务”的疑惑就是在《Thinking in Java》这本书的“并发”章节中它将线程直接定义为一个任务,在开篇标题就取名为“定义任务”,并且提到定义任务只需实现Runnable接口.而这个任务则是通过调用start来创建一改新的线程来执行.说来说去有点绕,其实也不必纠结于在书中时而提到线程,时而提到人任务.我认为就记住:任务是我们在编程时所赋这段代码的实际意义,而线程就关注它是否安全,是否需要安全,这就是后面要提到的线程安全问题.在像我一样产生疑惑时,不用在意它两者间的关系和提法。

什么是并发?

提到了并发,那又不得不和并行作比较。并发是指在一段时间内同时做多个事情,比如在1点-2点洗碗、洗衣服等。而并行是指在同一时刻做多个事情,比如1点我左手画圆右手画方。两个很重要的区别就是“一段时间”和“同一时刻”.在操作系统中就是:

1) 并发就是在单核处理中同时处理多个任务。(这里的同时指的是逻辑上的同时)

2) 并行就是在多核处理器中同时处理多个任务。(这里的同时指的就是物理上的同时)

初学编程基本上都是单线程结构化编程,或者说是根本就接触不到线程这个概念,反正程序照着自己实现的逻辑,程序一步一步按照我们的逻辑去实现并且得到希望输出的结果。但随着编程能力的提高,以及应用场景的复杂多变,我们不得不要面临多线程并发编程。而初学多线程并发编程时,常常出现一些预料之外的结果,这就是涉及到“线程安全”问题。

什么线程安全?

这是在多线程并发编程中需要引起足够重视的问题,如果你的线程不足够“安全”,程序就可能出现难以预料,以及难以复现的结果。《Java并发编程实战》提到对线程安全不好做一个定义,我的简单理解就是:线程安全就是指程序按照你的代码逻辑执行,并始终输出预定的结果。书中的给的定义:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。具体有关线程安全的问题,例如原子性、可见性等等不在这里做详细阐述,适当的时候会进行详细介绍,简单说一点,想要这个线程安全,得在访问的时候给它上个锁,不让其他线程访问,当然这种说法不严谨,不过可以暂时这么理解。

以上是从基本概念理论出发来大致了解需要知道的一些概念,下面就针对JDK中有关线程的API来对多线程并发编程做一个了解。

java.lang.Object
  -public void notify()//唤醒这个对象监视器正在等待获取锁的一个线程
  -public void notifyAll()//唤醒这个对象监视器上正在等待获取锁的所有线程
  -public void wait()//导致当前线程等待另一个线程调用notify()或notifyAll()
  -public void wait(long timeout)// 导致当前线程等待另一个线程调用notify()或notifyAll(),或者达到timeout时间
  -public void wait(long timeout, int nanos)//与上个方法相同,只是将时间控制到了纳秒nanos

我们先用一个经典的例子——生产者消费者问题来说明上面的API是如何使用的。生产者消费者问题指的的是,生产者生产产品到仓库里,消费者从仓库中拿,仓库满时生产者不能继续生产,仓库为空时消费者不能继续消费。转化成程序语言也就是生产者是一个线程

1,消费者是线程

2,仓库是一个队列,线程1往队尾中新增,线程2从队首中移除,队列满时线程1不能再新增,队列空时线程2不能再移除。

package com.producerconsumer;

import java.util.Queue;



/**

 * 生产者

 * Created by yulinfeng on 2017/5/11.

 */

public class Producer implements Runnable{

  private final Queue<String> queue;

  private final int maxSize;

  public Producer(Queue<String> queue, int maxSize) {

    this.queue = queue;

    this.maxSize = maxSize;

  }
  public void run() {
    produce();
  }

  /**

   * 生产

   */

  private void produce() {

    try {
      while (true) {
        synchronized (queue) {
          if (queue.size() == maxSize) {
            System.out.println("生产者:仓库满了,等待消费者消费");
            queue.wait();
          }
          System.out.println("生产者:" + queue.add("product"));
          queue.notifyAll();
        }
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

 

package com.producerconsumer;

import java.util.Queue;

/**

 * 消费者

 * Created by yulinfeng on 2017/5/11.

 */

public class Consumer implements Runnable {

  private final Queue<String> queue;
  public Consumer(Queue<String> queue) {
    this.queue = queue;
  }

  public void run() {
    consume();
  }

 

  /**

   * 消费

   */

  private void consume() {
    synchronized (queue) {
      try {
        while (true) {
          if (queue.isEmpty()) {
            System.out.println("消费者:仓库空了,等待生产者生产");
            queue.wait();
          }
          System.out.println("消费者:" + queue.remove());
          queue.notifyAll();
        }
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

}

 

package com.producerconsumer;

import java.util.LinkedList;
import java.util.Queue;

/**

 * Created by yulinfeng on 2017/5/11.

 */

public class Main {

  public static void main(String[] args) {

    Queue<String> queue = new LinkedList<String>();
    int maxSize = 100;
    Thread producer = new Thread(new Producer(queue, maxSize));
    Thread consumer = new Thread(new Consumer(queue));
    producer.start();
    consumer.start();

  }

}

 

这个生产者消费者问题的实现,我采用线程不安全的LinkedList,使用内置锁synchronized来保证线程安全,在这里我们不讨论synchronized,主要谈notify()、notifyAll()和wait()。

在这里例子中,作为生产者,当队列满时调用了队列的wait()方法,表示等待,并且此时释放了锁。作为消费者此时获取到锁并且移除队首元素时调用了notifyAll()方法,此时生产者由wait等待状态转换为唤醒状态,但注意!此时仅仅是线程被唤醒了,有了争夺CPU资源的资格,并不代表下一步就一定是生产者生产,还有可能消费者继续争夺了CPU资源。一定记住是被唤醒了,有资格争夺CPU资源。notifyAll()表示的是唤醒所有等待的线程,所有等待的线程被唤醒过后,都有了争夺CPU资源的权利,至于是谁会获得这个锁,那不一定。而如果是使用notify(),那就代表唤醒所有等待线程中的一个,只是一个被唤醒具有了争夺CPU的权力,其他没被唤醒的线程继续等待。如果等待线程就只有一个那么notify()和notifyAll()就没区别,不止一个那区别就大了,一个是只唤醒其中一个,一个是唤醒所有。唤醒不是代表这个线程就一定获得CPU资源一定获得锁,而是有了争夺的权利。

java.lang.Thread
  -public void join()
  -public void sleep()
  -public static void yield()
  -……

针对Thread线程类,我们只说常见的几个不容易理解的方法,其余方法不在这里做详细阐述。

关于sleep()方法,可能很容易拿它和Object的wait方法作比较。两个方法很重要的一点就是sleep不会释放锁,而wait会释放锁。在上面的生产者消费者的生产或消费过程中添加一行Thread.sleep(5000),你将会发现执行到此处时,这个跟程序都会暂停执行5秒,不会有任何其他线程执行,因为它不会释放锁。

关于join()方法,JDK7的解释是等待线程结束(Waits for this thread to die)似乎还是不好理解,我们在main函数中启动两个线程,在启动完这两个线程后main函数再执行其他操作,但如果不加以限制,有可能main函数率先执行完需要的操作,但如果在main函数中加入join方法,则表示阻塞等待这两个线程执行结束后再执行main函数后的操作,例如:

package com.join;


public class Main {

  public static void main(String[] args) throws Exception{
    Thread t1 = new Thread(new Task(0));
    Thread t2 = new Thread(new Task(0));
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.print("main结束");
  }
}

上面个例子如果没有join方法,那么“main”结束这条输出语句可能就会先于t1、t2,加上在启动线程的调用方使用了线程的join方法,则调用方则会阻塞线程执行结束过后再执行剩余的方法。

关于Thread.yield()方法,本来这个线程处于执行状态,其他线程也想争夺这个资源,突然,这个线程不想执行了想和大家一起来重新夺取CPU资源。所以Thread.yield也称让步。从下一章开始就正式开始了解java.util.concurrent。

以上这篇基于线程、并发的基本概念(详解)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。


# 线程的基本概念  # Java多线程和并发基础面试题(问答形式)  # Java线程的基本概念  # 浅谈Java线程并发知识点  # JAVA多线程与并发学习总结分析  # 详谈java线程与线程、进程与进程间通信  # 多个  # 是一个  # 多线程  # 的是  # 在这里  # 移除  # 这就是  # 则是  # 是指  # 它是  # 很容易  # 说来说去  # 这两个  # 我认为  # 用了  # 给大家  # 很重要  # 做一个  # 多核  # 书中 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel表单请求验证类怎么用_Laravel Form Request分离验证逻辑教程  七夕网站制作视频,七夕大促活动怎么报名?  深圳网站制作的公司有哪些,dido官方网站?  如何在宝塔面板创建新站点?  Swift开发中switch语句值绑定模式  Laravel怎么实现微信登录_Laravel Socialite第三方登录集成  在Oracle关闭情况下如何修改spfile的参数  如何快速选择适合个人网站的云服务器配置?  Laravel怎么实现前端Toast弹窗提示_Laravel Session闪存数据Flash传递给前端【方法】  Android利用动画实现背景逐渐变暗  Laravel如何实现API速率限制?(Rate Limiting教程)  深圳网站制作设计招聘,关于服装设计的流行趋势,哪里的资料比较全面?  Laravel如何使用Service Provider注册服务_Laravel服务提供者配置与加载  夸克浏览器网页跳转延迟怎么办 夸克浏览器跳转优化  详解CentOS6.5 安装 MySQL5.1.71的方法  高端云建站费用究竟需要多少预算?  Chrome浏览器标签页分组怎么用_谷歌浏览器整理标签页技巧【效率】  焦点电影公司作品,电影焦点结局是什么?  Swift中switch语句区间和元组模式匹配  香港服务器网站推广:SEO优化与外贸独立站搭建策略  网站制作壁纸教程视频,电脑壁纸网站?  韩国网站服务器搭建指南:VPS选购、域名解析与DNS配置推荐  儿童网站界面设计图片,中国少年儿童教育网站-怎么去注册?  制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?  香港服务器租用每月最低只需15元?  iOS验证手机号的正则表达式  Laravel怎么配置自定义表前缀_Laravel数据库迁移与Eloquent表名映射【步骤】  Laravel的契約(Contracts)是什么_深入理解Laravel Contracts与依赖倒置  微信小程序 HTTPS报错整理常见问题及解决方案  html5audio标签播放结束怎么触发事件_onended回调方法【教程】  nginx修改上传文件大小限制的方法  如何登录建站主机?访问步骤全解析  EditPlus中的正则表达式 实战(1)  极客网站有哪些,DoNews、36氪、爱范儿、虎嗅、雷锋网、极客公园这些互联网媒体网站有什么差异?  Win11搜索不到蓝牙耳机怎么办 Win11蓝牙驱动更新修复【详解】  微信小程序 wx.uploadFile无法上传解决办法  Laravel Session怎么存储_Laravel Session驱动配置详解  百度输入法全感官ai怎么关 百度输入法全感官皮肤关闭  高防服务器租用首荐平台,企业级优惠套餐快速部署  JavaScript如何实现音频处理_Web Audio API如何工作?  Android自定义listview布局实现上拉加载下拉刷新功能  Laravel用户密码怎么加密_Laravel Hash门面使用教程  Android滚轮选择时间控件使用详解  Laravel如何实现邮箱地址验证功能_Laravel邮件验证流程与配置  Laravel如何与Vue.js集成_Laravel + Vue前后端分离项目搭建指南  Laravel怎么配置不同环境的数据库_Laravel本地测试与生产环境动态切换【方法】  C语言设计一个闪闪的圣诞树  重庆市网站制作公司,重庆招聘网站哪个好?  香港服务器部署网站为何提示未备案?  Laravel Telescope怎么调试_使用Laravel Telescope进行应用监控与调试