java多线程系列(1)入门

简介: 多线程这块一直是面试的重点,也是开发当中的重点,于是下决定把这一块的内容吃透它。整个系列的基础文章最少就在60篇以上,因为这块的知识看一两篇确实感觉没什么作用,需要有一个体系的才好。这也是第一篇文章。点到为止。

一、认识线程


1、概念


什么是线程呢?

线程是进程划分成的更小的运行单位。就好比电脑QQ是一个进程,里面还有各种子模块,比如QQ空间,个性皮肤等子功能。

这里出现了另外一个名词进程。

进程是系统运行程序的基本单位。就好比是一个个应用程序QQ、微信等等。

看概念确实是一脸懵逼,举个例子就明白了。我们打开电脑的任务管理器,会发现上面就有进程,点击这个进程我们就能看到一个个应用程序,这就是进程。

v2-293b03eb9a58f159b142a1ff0b54d8f0_1440w.jpg

那什么是线程呢?不知道我们注意到了没有,每一个进程最左边都有一个小箭头>,我们打开来看看:

v2-06a50e9be3a7926f0c4b7c2d3b2cdf27_1440w.jpg

这些小的模块就好比是一个个线程。有了这个印象我们重新来认识一下线程和进程就容易多了。


线程:

线程是一个比进程小的执行单位,也被称为轻量级进程。一个进程可以产生多个线程。多个线程共享同一块内存和系统资源,CPU在这多个线程间来回切换去执行。


进程:

进程是系统运行程序的基本单位,也就是一个程序。系统运行一个程序即是一个进程从创建,运行到消亡的过程。

有了对线程的基本认识接下来我们就可以去理解一下,这一系列文章常用到的一些概念了。


2、并行与并发


其实他们俩区分很容易区分。

并发是多个任务交替使用CPU,同一时刻还是只有一个任务在跑,并行是多个任务同时跑。举个例子就明白了。

桌子上有三个馒头,每一时刻,小明只能咬一个馒头。

桌子上有三个馒头,每一时刻,小明、小红、小华三个人同时咬三个馒头。

明白了吧。


3、同步和异步


这两个名词我们在学习的时候经常会遇到,举个例子去理解,

对于同步:我们去餐厅吃饭,只有一个客户的时候商家比较容易处理,但是当有两个人三个人的时候,这时候就需要排队了,也就是拥塞了,这就是同步。换个例子来说,就是我们请求服务器的时候,必须要等到服务器的反馈我们才能够去做其他的事。

对于异步:就好比微信聊天,我们只管把信息发送给对方,不管对方有没有回复我们,我们都可以去做其他的事,也就是说执行完函数之后,不必等到反馈就可以去做其他的事。


4、死锁


和操作系统里面的死锁意思一样,也就是说多个人同时竞争一个资源,

例子一:好比多个男孩追求同一个女孩,这时候女孩就不知道该嫁给谁了。

例子二:我们去图书馆借书,发现这本书被借走了,我们只能等到那个人把书还到图书馆才可以看。


5、原子变量与原子操作


所谓原子操作,就是“不可中断的一个或一系列操作”。就好比你高考考试的时候,就算天塌下来也要把卷子做完。再急的事也不能抢夺他的优先权。

原子变量其实是一个抽象的意思,因为本质上并没有严格意义上的原子变量,但是在这里,我们可以这样理解,原子变量提供原子操作,就好比变量a,多个线程对其操作改变时,每一次只能有一个线程拿到他的执行权进行操作。

在这里我们基本上列举了一些基本的概念,但其实还有很多,我们在遇到的时候再去分析和理解会比较容易。我们说了这么久的线程,下面我们来认识一下java中的线程。


二、基础案例


我们以一个生活中的案例来解释说明,比如我们敲代码的同时还想听音乐。

java中创建一个线程有两种方式,继承Thread类和实现runnable接口。我们两种都实现一下。


1、继承Thread类


在这里我们定义两个线程

public class ThreadTest01 extends  Thread{
    @Override
    public void run() {
        for (int i=0;i<10;i++)
            System.out.println("听音乐");
    }
}

还有一个线程可以敲代码

public class ThreadTest02 extends  Thread{
    @Override
    public void run() {
        for (int i=0;i<10;i++)
            System.out.println("敲代码");
    }
}

最后我们就可以一边敲代码一边听音乐了

public class ThreadTest {
    public static void main(String[] args) {
        ThreadTest01 thread1=new ThreadTest01();
        ThreadTest02 thread2=new ThreadTest02();
        thread1.start();
        thread2.start();
    }
}

我们运行一边会发现,敲代码和听音乐是交叉输出的。这就体现了多线程的含义,因为要是平时输出,肯定就是谁在前先输出谁,比如说先输出十个听音乐,在输出十个敲代码。不会交叉输出。


当然我们也可以使用一个线程类去演示,在这里,首先我们创建了两个类ThreadTest01和ThreadTest02,并且都继承了Thread,然后再测试类中,我们只需要调用相应的start方法即可。


使用一个线程类和使用多个线程类的区别你可以这样理解,一个是多个不同的线程分别完成自己的任务,一个是多个相同的线程共同完成一个任务。


2、实现Runnable接口


我们同样拿上面的例子进行说明

public class MyRunnable01 implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<10;i++)
            System.out.println("听音乐");
    }
}

然后还可以敲代码

public class MyRunnable02 implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<10;i++)
            System.out.println("敲代码");
    }
}

最后我们可以测试一下了

public class ThreadTest {
    public static void main(String[] args) {
        MyRunnable01 runnable01=new MyRunnable01();
        MyRunnable02 runnable02=new MyRunnable02();
        Thread thread1=new Thread(runnable01);
        Thread thread2=new Thread(runnable02);
        thread1.start();
        thread2.start();
    }
}

每次运行的时候,在控制台你都会看到不一样的效果。不过多运行几次依然能够发现交叉运行的效果。


其实呢还有一种方式也可以实现线程,那就是实现Callable接口,不过很少用到,这种方式在以后的文章中再进行详细的介绍。毕竟这是第一篇文章。只是认识了解一下线程。

通过Runnable接口的方式,我们依然发现,创建了两个MyRunnable,然后直接赋给Thread即可,调用的时候同样是使用start方法来启动。这就是简单的使用一下线程。

不知道我们注意到没有,java其实为我们已经提供了Thread,我们可以直接进行实例化。有时候我们会经常使用匿名内部类的方式来创建一个线程,比如说下面这种

new Thread("th1") {
     @Override
     public void run() {
          System.out.println( "匿名内部类");
     }
}.start();
注意:上面的两种创建线程的方式中,明明都是重写的run方法,为什么要去调用start启动线程呢?而且这两种方式有什么区别呢?在这里先留一个悬念,下一篇文章将会介绍道。


三、分析多线程


1、使用线程有什么好处呢?


好处你已经能够看到了,就是我们可以同时做好几件事,在玩游戏的时候可以听歌,还可以看电影等等,多方便。


2、使用线程有什么坏处嘛?


坏处其实也很多,比如说对于单核 CPU,CPU 在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换,上下文切换是一个复杂的过程,比如要记录程序计数器、CPU寄存器的状态等信息,耗时又耗空间。

为了解决这个问题,才有了现在的多核CPU。我们经常会听到手机或者是电脑是八核的,就是减少上下文切换带来的时间空间损耗,提高程序运行的效率。


3、多线程带来一个问题


从上面的例子其实我们发现只是各干各的事,相互之间互不干扰。还有一种情况是一个资源被多个线程所用到了,这就带来了线程安全问题。我们使用例子来演示一下这个问题,

public class ThreadTest {
    private int value=0;
    public int getValue() {
        return value++;
    }
    public static void main(String[] args) throws InterruptedException {
        final ThreadTest test = new ThreadTest();
        //我们的本意可能是th1执行后value变为1
        new Thread("th1") {
            @Override
            public void run() {
                System.out.println( test.getValue()+" "+super.getName());
            }
        }.start();
        //然后th2执行后value变为2
        new Thread("th2") {
            @Override
            public void run() {
                System.out.println(test.getValue()+" "+super.getName());
            }
        }.start();
    }
}

在上面,我们直接创建了两个线程,在第一个线程执行完之后value,在第二个线程执行完之后value变为2。但是结果与我们想的往往不一样,我测试了N多次,结果总是0 th2和1 th1。或者是反过来,再或者是00、11等等。这就是线程不安全的例子。如何去确保线程安全呢?方式其实有很多种,我们一点一点深入之后再去解决。


下一篇文章,我们将对线程的生命周期,常用API、以及源码(构造函数等)进行一个讲解,正式开始我们的多线程之旅。感谢支持。

相关文章
|
13天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
4天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
4天前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
|
4天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
27 1
|
12天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
12天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
11天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
14天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
17天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
7月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。