大白话Java多线程,小白都能看的懂的哦

简介: 大白话Java多线程,小白都能看的懂的哦

什么是线程

  • 说到线程我们应该先了解下什么是进程,下面这个图片大家应该都比较熟悉吧。

在这里插入图片描述
我们看到的这些单独运行的程序就是一个独立的进程,进程之间是相互独立存在的。我们上面图中的360浏览器、百度云盘等等都是独立的进程。

  • 那么什么是线程呢?
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

上面这个定义是引入百度百科。看起来文绉绉的,说的都是啥玩意,一点也不好理解。那我们就说点人话吧。最大工厂富士康大家都知道吧,里面是不是有很多个生产车间,一个车间里面又有很多条流水线。我们可把一个进程当成一个车间,那么线程就是车间里面的一条条流水线。一个车间的工作过程是一个进程,一个流水线的工作过程是一个线程。进程是操作系统资源分配的最小单位(制造科8号车间负责生产100w个手机壳),线程是cpu调度的最小单位(8号车间里面的每一条生产线负责生手机壳这个具体的任务)。

为什么要使用多线程

  • 更快的响应时间

这个比较好理解,上面的例子比如一个车间一条流水线1天两班倒(24小时工作)可以生产1w个手机壳。如果要生产100w个手机壳如果一个车间就只有一条生产线那是不是需要100天。100天等你生产出来这个手机壳都过时了。那如果一个车间有50条生产线并行生产,那是不是生产100w手机壳2天就完工了。

  • 更多的处理器核心

线程是大多数操作系统调度的基本单元,一个程序作为一个进程来运行,程序运行过程中能够创建多个线程,而一个线程在一个时刻只能运行在一个处理器核心上。

  • 更好的编程模型
    java为多线程编程提供了良好。考究并且一致的编程模型,使开发人员能够更加专注于问题解决,即为所遇到的问题建立适合的模型,而不是绞尽脑汁地考虑如何将其多线程化。一旦开发人员建立好了模型,稍作修改总是能够方便地映射到Java提供的多线程编程模型上。

多线程创建的方式

  • 第一种,通过继承Thread类创建线程类
package com.workit.demo.thread;

public class ExtendsThread extends Thread {
    public static void main(String[] args) {
        for(int i = 0;i<10;i++){
            //创建并启动线程
            new ExtendsThread().start();
        }
    }
    @Override
    public void run() {
        System.out.println(this.getName());
    }
}
  • 第二种,通过实现Runnable接口创建线程类
package com.workit.demo.thread;

public class RunnableThread implements Runnable {
    public static void main(String[] args) {
        for(int i = 0;i<10;i++){
            //创建并启动线程
            new Thread(new RunnableThread()).start();
        }
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

网上还有其他跟多版本的实现比如说,通过CallableFuture接口创建,但是这些都无非是对以上的封装,个人认为并不能当成实现方式。oracle官网也有说到创建线程的实现方式只有两种。
在这里插入图片描述
感兴趣的同 学可以去看下创建线程的两种方式

怎么保证线程安全

比如车间生产手机壳是不是需要物料,领物料是不是要到仓库去领取,如果每个流水线都派一个人去领。大家都蜂拥而上乱哄哄的去抢物料。流水线A把本属于流水线B的物料给领了,流水线C又把属于D的给干没了。最后有些流水线就没了物料。那为了解决这个问题应该怎么办呢?仓库设置了一个规定,一次只能有一个人来领取物料,大家排队领取。这样子秩序是好多了,但是突然有一天一个大胖子见有人领完了,他直接插对到对头领取(传说中的非公平锁)。排在后面的人敢怒不敢言宝宝心里那个苦啊。
在这里插入图片描述
后面仓库又指定了个个规矩,必须按照先来后到的顺序,如有插队就罚款100元。这样大家都按照先来后到的顺序,领物料的时候先看看有没有人排队没人排队就不用排队了直接去领物料,有人排队就自觉的排到队尾去。(传说的公平锁)
哎说了一大堆废话还是回归正题吧。线程安全在三个方面体现

  • 原子性:
提供了互斥访问,同一时刻只能有一个线程对它进行操作。

流水线工人到仓库领物料只同一时刻只能有一个人领取。

  • 可见性:
一个线程对主内存的修改可以及时的被其他线程观察到。

前一个流水线工人领完了10w的物料仓库总共还剩多少物料是对后面领取物料的工人可见的。

有序性:

程序的执行顺序按照代码顺序执行,在单线程环境下,程序的执行都是有序的,但是在多线程环境下,JMM 为了性能优化,编译器和处理器会对指令进行重排,程序的执行会变成无序。
  • 比如到仓库领物料的时候,要分三个步骤,第一步先确认信息核实你是哪个车间哪个产线的,第二步填写物料领取单子,第三步到物料处领取物料。如果仓库为了提高工作效率这几个步骤有时候可以互换。可以是2->1->3的顺序。也可以3->2->1的顺序。

下面通过代码来演示下吧怎么正确的获取物料。100条流水线每条流水线领取1000个物料最终物料应该剩余0.

  • synchronized关键字
package com.workit.demo.thread;

import jdk.nashorn.internal.runtime.options.LoggingOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;

import java.util.concurrent.CountDownLatch;


public class RunnableThread implements Runnable {
    private final static Logger LOG = LoggerFactory.getLogger(RunnableThread.class);
   static CountDownLatch countDownLatch = new CountDownLatch(100);
    public static void main(String[] args) throws InterruptedException {
        RunnableThread runnableThread = new RunnableThread();
        for(int i = 0;i<100;i++){
            //创建并启动线程
            Thread thread = new Thread(runnableThread);
            // 流水线
            thread.setName("流水线:"+i);
            thread.start();
        }
        countDownLatch.await();
        System.out.println("仓库剩余物料:"+runnableThread.count);

    }

    // 物料总数 volatile   
    private  int  count =100000;

    @Override
    public  void run() {
       int  remainCount =get();
        countDownLatch.countDown();
       LOG.info(Thread.currentThread().getName()+":领取了物料,仓库还剩于物料:"+remainCount);
    }

    public  synchronized    int  get(){
        for(int i =0;i<1000;i++){
            count--;
        }
        return count;
    }
}

synchronized为什么可以保证线程安全呢?
Synchronized 使用 Monitor(监视锁)保证资源在多线程环境下阻塞互斥访问,是JVM层面的锁。对了它还是非公平锁(就是上面插对的那个胖子)。这里就不详细介绍了后续后转门写一个文章介绍下它。

  • 使用lock锁保证线程安全。

Lock 也是 java.util.concurrent 包下的一个接口,定义了一系列的锁操作方法。Lock 接口主要有 ReentrantLock。它可以是公平锁或者非公平锁。通过构造 函数设置。

package com.workit.demo.thread;

import jdk.nashorn.internal.runtime.options.LoggingOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class RunnableThread implements Runnable {
    private final static Logger LOG = LoggerFactory.getLogger(RunnableThread.class);
    static CountDownLatch countDownLatch = new CountDownLatch(100);
    static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        RunnableThread runnableThread = new RunnableThread();
        for (int i = 0; i < 100; i++) {
            //创建并启动线程
            Thread thread = new Thread(runnableThread);
            // 流水线
            thread.setName("流水线:" + i);
            thread.start();
        }
        countDownLatch.await();
        System.out.println("仓库剩余物料:" + runnableThread.count);

    }

    // 物料总数volatile 
    private int count = 100000;

    @Override
    public void run() {
        int remainCount = getByLock();
        countDownLatch.countDown();
        LOG.info(Thread.currentThread().getName() + ":领取了物料,仓库还剩于物料:" + remainCount);
    }


    public int getByLock() {
        lock.lock();
        try {
            for (int i = 0; i < 1000; i++) {
                count--;
            }
            return count;
        } finally {
            lock.unlock();
        }
    }
}

总结

  • 出现线程安全问题的原因?
在多线程并发环境下,多个线程共同访问同一共享内存资源时,其中一个线程对资源进行写操作的中途(写入已经开始,但还没结束),其他线程对这个写了一半的资源进了读操作,或者对这个写了一半的资源进了写操作,导致此资源出现数据错误。
  • 如何避免线程安全问题?
保证共享资源在同一时间只能由一个线程进行操作(原子性,有序性)。
将线程操作的结果及时刷新,保证其他线程可以立即获取到修改后的最新数据(可见性)。

结束

  • 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
  • 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
  • 感谢您的阅读,十分欢迎并感谢您的关注。

参考
https://zhuanlan.zhihu.com/p/73899015
《java并发编程艺术》

目录
相关文章
|
7天前
|
Java 开发者
奇迹时刻!探索 Java 多线程的奇幻之旅:Thread 类和 Runnable 接口的惊人对决
【8月更文挑战第13天】Java的多线程特性能显著提升程序性能与响应性。本文通过示例代码详细解析了两种核心实现方式:Thread类与Runnable接口。Thread类适用于简单场景,直接定义线程行为;Runnable接口则更适合复杂的项目结构,尤其在需要继承其他类时,能保持代码的清晰与模块化。理解两者差异有助于开发者在实际应用中做出合理选择,构建高效稳定的多线程程序。
28 7
|
6天前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
6天前
|
存储 监控 安全
一天十道Java面试题----第三天(对线程安全的理解------>线程池中阻塞队列的作用)
这篇文章是Java面试第三天的笔记,讨论了线程安全、Thread与Runnable的区别、守护线程、ThreadLocal原理及内存泄漏问题、并发并行串行的概念、并发三大特性、线程池的使用原因和解释、线程池处理流程,以及线程池中阻塞队列的作用和设计考虑。
|
1天前
|
Java
java开启线程的四种方法
这篇文章介绍了Java中开启线程的四种方法,包括继承Thread类、实现Runnable接口、实现Callable接口和创建线程池,每种方法都提供了代码实现和测试结果。
java开启线程的四种方法
|
3天前
|
存储 缓存 安全
深度剖析Java HashMap:源码分析、线程安全与最佳实践
深度剖析Java HashMap:源码分析、线程安全与最佳实践
|
6天前
|
安全 Java
Java模拟生产者-消费者问题。生产者不断的往仓库中存放产品,消费者从仓库中消费产品。其中生产者和消费者都可以有若干个。在这里,生产者是一个线程,消费者是一个线程。仓库容量有限,只有库满时生产者不能存
该博客文章通过Java代码示例演示了生产者-消费者问题,其中生产者在仓库未满时生产产品,消费者在仓库有产品时消费产品,通过同步机制确保多线程环境下的线程安全和有效通信。
|
5天前
|
缓存 前端开发 JavaScript
一篇文章助你搞懂java中的线程概念!纯干货,快收藏!
【8月更文挑战第11天】一篇文章助你搞懂java中的线程概念!纯干货,快收藏!
13 0
一篇文章助你搞懂java中的线程概念!纯干货,快收藏!
|
7天前
|
缓存 监控 Java
Java性能优化:从单线程执行到线程池管理的进阶实践
在Java开发中,随着应用规模的不断扩大和用户量的持续增长,性能优化成为了一个不可忽视的重要课题。特别是在处理大量并发请求或执行耗时任务时,单线程执行模式往往难以满足需求,这时线程池的概念便应运而生。本文将从应用场景举例出发,探讨Java线程池的使用,并通过具体案例和核心代码展示其在实际问题解决中的强大作用。
22 1
|
8天前
|
缓存 Java 数据处理
Java中的并发编程:解锁多线程的力量
在Java的世界里,并发编程是提升应用性能和响应能力的关键。本文将深入探讨Java的多线程机制,从基础概念到高级特性,逐步揭示如何有效利用并发来处理复杂任务。我们将一起探索线程的创建、同步、通信以及Java并发库中的工具类,带你领略并发编程的魅力。
|
3天前
|
算法 安全 Java
深入解析Java多线程:源码级别的分析与实践
深入解析Java多线程:源码级别的分析与实践