Java并发编程之多线程

简介: 我们首先,先要了解什么是进程,什么是线程。

什么是多线程?


我们首先,先要了解什么是进程,什么是线程

首先,我们看看进程。我们如果允许一个程序,它卡死了,我们通常会去任务管理器里面将进程结束。

网络异常,图片无法展示
|

所以,这里所看见的,就是进程。

那么,何为线程呢?

首先,看看来自知乎的解释:

网络异常,图片无法展示
|

线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

啥意思呢?通俗的说。QQChrome 浏览器是两个进程Chrome进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件,同时你开多个窗口浏览网页也没问题。

想了解得更详细点,可以看看知乎这一篇——>进程和线程

然后,腾讯云还有一篇,链接中文,不好分享,我直接截图。

网络异常,图片无法展示
|

多线程优势

多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。

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

多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

优势很多,我就直接去网上搬运来了,能达到学会的目的就可以,不在意是不是自己描述出来。

一、多线程优势

采用多线程技术的应用程序可以更好地利用系统资源。主要优势在于充分利用了CPU的空闲时间片,用尽可能少的时间来对用户的要求做出响应,使得进程的整体运行效率得到较大提高,同时增强了应用程序的灵活性。由于同一进程的所有线程是共享同一内存,所以不需要特殊的数据传送机制,不需要建立共享存储区或共享文件,从而使得不同任务之间的协调操作与运行、数据的交互、资源的分配等问题更加易于解决。

线程同步,在多线程应用中,考虑不同线程之间的数据同步和防止死锁。当两个或多个线程之间同时等待对方释放资源的时候就会形成线程之间的死锁。为了防止死锁的发生,需要通过同步来实现线程安全。在Visual Basic中提供了三种方法来完成线程的同步。在Java中可用synchronized关键字。

二、代码域同步

使用Monitor类可以同步静态/实例化的方法的全部代码或者部分代码段。

三、手工同步

可以使用不同的同步类创建自己的同步机制。这种同步方式要求你自己手动的为不同的域和方法同步,这种同步方式也可以用于进程间的同步和解除由于对共享资源的等待而造成的死锁。

四、上下文同步

使用SynchronizationAttributeContextBoundObject对象创建简单的,自动同步。这种同步方式仅用于实例化的方法和域的同步。所有在同一个上下文域的对象共享同一个锁。

总结多线程的好处,使用线程可以把占据时间长的程序中的任务放到后台去处理;用户界面更加吸引人,这样比如用户点击了一个按钮去触发某件事件的处理,可以弹出一个进度条来显示处理的进度;程序的运行效率可能会提高;在一些等待的任务实现上如用户输入,文件读取和网络收发数据等,线程就比较有用了。

以上很多部分,如果看不懂,可能是许多东西还未涉及到。

实现多线程

我们实现Java的多线程呢,有4中方法。

1.继承Thread类创建线程

2.实现Runnable接口创建线程

3.实现Callable接口通过FutureTask包装器来创建Thread线程

4.使用ExecutorServiceCallableFuture实现有返回结果的线程(线程池方式)

多线程如何运行?

菜鸟教程其他的不行,图还是好用,我们看看这张图。

网络异常,图片无法展示
|

如果正常运行的话,路径就是这样的:

新建线程——>就绪状态——>运行状态——>死亡状态

网络异常,图片无法展示
|

Thread类创建线程

使用Thread类创建线程,我们首先需要继承它,并且重写run方法。

满足这两个条件就可以。

网络异常,图片无法展示
|

我这里,为了体现多线程的并发,我使用了Time下的LocalTime类,来体现时间的变化。

sleep()方法可以设置延迟,也就是说,运行一次后,我这里需要1000毫秒在运行下一次,我加个循环运行一下。

try {
    for (int i = 0; i < 3; i++) {
        Thread.sleep(1000);
        System.out.println(this.getName()+"多线程输出"+LocalTime.now());
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}

在创建一个入口类,运行起来。

package Thread;
public class ThreadTest {
    public static void main(String[] args) {
        ThreadDemo t = new ThreadDemo();
        t.start();
        ThreadDemo t1 = new ThreadDemo();
        t1.start();
        ThreadDemo t2 = new ThreadDemo();
        t2.start();
    }
}

这里创建了三个线程,使用start()方法可以运行起来,调用run方法。

注意!!!不是.run(),是.start()

网络异常,图片无法展示
|

运行下了,结果如图。

Thread-2多线程输出18:29:22.345
Thread-1多线程输出18:29:22.345
Thread-0多线程输出18:29:22.345
    //sleep(1000),延迟1s后继续运行
Thread-1多线程输出18:29:23.356
Thread-2多线程输出18:29:23.356
Thread-0多线程输出18:29:23.356
    //sleep(1000),延迟1s后继续运行
Thread-1多线程输出18:29:24.357
Thread-2多线程输出18:29:24.357
Thread-0多线程输出18:29:24.357

看看后面的时间,TT1T2都是同时运行的,比如第一次,都是18:29:22.345这个时间。

至于为啥三个对象顺序不一样,这就相当于挤公交,鬼知道谁先挤进去呢?嘿嘿。

Thread 方法

下表列出了Thread类的一些重要方法:

序号 方法描述
1 public void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2 public void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3 public final void setName(String name) 改变线程名称,使之与参数 name 相同。
4 public final void setPriority(int priority) 更改线程的优先级。
5 public final void setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
6 public final void join(long millisec) 等待该线程终止的时间最长为 millis 毫秒。
7 public void interrupt() 中断线程。
8 public final boolean isAlive() 测试线程是否处于活动状态。

上述方法是被 Thread 对象调用的,下面表格的方法是 Thread 类的静态方法。

序号 方法描述
1 public static void yield() 暂停当前正在执行的线程对象,并执行其他线程。
2 public static void sleep(long millisec) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
3 public static boolean holdsLock(Object x) 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
4 public static Thread currentThread() 返回对当前正在执行的线程对象的引用。
5 public static void dumpStack() 将当前线程的堆栈跟踪打印至标准错误流。

方法用法都一样,请自行斟酌。

Runnable接口创建线程

因为和Thread创建线程类似,我就直接放代码了。

package Runnable;
import java.time.LocalTime;
public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        //设置延迟
        try {
            for (int i = 0; i < 3; i++) {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+"多线程输出"+ LocalTime.now());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这里也是两个条件。

implements Runnable@Override run。继承接口,重写run方法。

Thread.currentThread().getName()这个是返回当前调用的主线程的名字。

运行类就有点不一样了。

package Runnable;
public class RunnableTest {
    public static void main(String[] args) {
        RunnableDemo r = new RunnableDemo();
        Thread t = new Thread(r);
        t.start();
        RunnableDemo r1 = new RunnableDemo();
        Thread t1 = new Thread(r1);
        t1.start();
        RunnableDemo r2 = new RunnableDemo();
        Thread t2 = new Thread(r2);
        t2.start();
    }
}
//创建线程对象
RunnableDemo r = new RunnableDemo();
//将线程对象放在Thread类对象重
Thread t = new Thread(r);
//调用start方法
t.start();

运行结果也一样。

网络异常,图片无法展示
|

为何要用Runnable

那有人就说了,为啥这个东西多了一步,还麻烦,我怎么不直接用Thread呢?

我们首先要明白,Java语言不可以多继承

两者实现方式带来最明显的区别就是,由于Java不允许多继承,因此实现了Runnable接口可以再继承其他类,但是Thread明显不可以。

Runnable可以实现多个相同的程序代码的线程去共享同一个资源,而Thread并不是不可以,而是相比于Runnable来说,不太适合。

线程的调度

线程的调度的操作,有如下常用方法。

方法 作用
int getPriority() 返回线程的优先级
void setPrority(int newPrority) 更改线程的优先级
boolean isAlive() 判断线程是否处于活动状态
void join() 使进程中其他线程等待该线程终止后再运行
void yield() 暂停当前正在执行的线程对象并允许其他线程

线程优先级

RunnableDemo r = new RunnableDemo();
Thread t = new Thread(r);
//设置优先级
t.setPriority(Thread.MAX_PRIORITY);
t.start();

这样就可以设置线程对象优先级,优先级有三个常量。

MIN_PRIORITY //值为1 最低
NORM_PRIORITY //值为5 普通级别
MAX_PRIORITY //值为10 最高

线程强制

package Runnable;
public class RunnableTest {
    public static void main(String[] args) {
        RunnableDemo r = new RunnableDemo();
        Thread t = new Thread(r);
        //线程强制运行
        //join强制
        try {
            t.start();
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        RunnableDemo r1 = new RunnableDemo();
        Thread t1 = new Thread(r1);
        t1.start();
        RunnableDemo r2 = new RunnableDemo();
        Thread t2 = new Thread(r2);
        t2.start();
    }
}

join还有两个重载方法,可以去自己了解。

线程礼让

网络异常,图片无法展示
|

yield是静态方法,直接用类调用就可以。

注意!!!

上面的设置优先级,是不能完完全全一个不漏的把控住的,只是优先级越高,先运行的机率越高。

yield的礼让也是如此,不是一定,是提高概率,不是绝对礼让。

而我们的join是绝对的

线程的同步

首先,我们需要了解,为什么同步。

为什么需要同步

线程的安全问题

  • 多个线程执行的不确定性硬气执行结果的不稳定性
  • 多个线程对账本的共享, 会造成操作的不完整性, 会破坏数据.
  • 多个线程访问共享的数据时可能存在安全性问题

比如:

卖票过程中出现了重票和错票的情况 (以下多窗口售票demo存在多线程安全问题)。

当票数为1的时候,三个线程中有线程被阻塞没有执行票数-1的操作,这是其它线程就会通过if语句的判断,这样一来就会造成多卖了一张票,出现错票的情况。

极端情况为,当票数为1时,三个线程同时判断通过,进入阻塞,然后多执行两侧卖票操作。

所以,线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。

那么,线程同步的原因,就解释清楚了,我得准备一些代码来写关于同步锁的文章。

所以这篇文章先到这吧!

相关文章
|
16小时前
|
数据采集 安全 Java
Java并发编程学习12-任务取消(上)
【5月更文挑战第6天】本篇介绍了取消策略、线程中断、中断策略 和 响应中断的内容
13 4
Java并发编程学习12-任务取消(上)
|
1天前
|
安全 算法 Java
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第13天】 在Java开发中,并发编程是一个复杂且重要的领域。它不仅关系到程序的线程安全性,也直接影响到系统的性能表现。本文将探讨Java并发编程的核心概念,包括线程同步机制、锁优化技术以及如何平衡线程安全和性能。通过分析具体案例,我们将提供实用的编程技巧和最佳实践,帮助开发者在确保线程安全的同时,提升应用性能。
8 1
|
1天前
|
Java 编译器 开发者
Java并发编程中的锁优化策略
【5月更文挑战第13天】在Java并发编程中,锁是一种重要的同步机制,用于保证多线程环境下数据的一致性。然而,不当的使用锁可能会导致性能下降,甚至产生死锁等问题。本文将介绍Java中锁的优化策略,包括锁粗化、锁消除、锁降级等,帮助开发者提高程序的性能。
|
1天前
|
数据采集
多线程在编程中的重要性有什么?并以LabVIEW为例进行说明
多线程在编程中的重要性有什么?并以LabVIEW为例进行说明
11 4
|
1天前
|
Java 调度
Java一分钟之线程池:ExecutorService与Future
【5月更文挑战第12天】Java并发编程中,`ExecutorService`和`Future`是关键组件,简化多线程并提供异步执行能力。`ExecutorService`是线程池接口,用于提交任务到线程池,如`ThreadPoolExecutor`和`ScheduledThreadPoolExecutor`。通过`submit()`提交任务并返回`Future`对象,可检查任务状态、获取结果或取消任务。注意处理`ExecutionException`和避免无限等待。实战示例展示了如何异步执行任务并获取结果。理解这些概念对提升并发性能至关重要。
16 5
|
1天前
|
安全 Java 调度
深入理解Java并发编程:线程安全与性能优化
【5月更文挑战第12天】 在现代软件开发中,多线程编程是提升应用程序性能和响应能力的关键手段之一。特别是在Java语言中,由于其内置的跨平台线程支持,开发者可以轻松地创建和管理线程。然而,随之而来的并发问题也不容小觑。本文将探讨Java并发编程的核心概念,包括线程安全策略、锁机制以及性能优化技巧。通过实例分析与性能比较,我们旨在为读者提供一套既确保线程安全又兼顾性能的编程指导。
|
2天前
|
Java
Java一分钟:线程协作:wait(), notify(), notifyAll()
【5月更文挑战第11天】本文介绍了Java多线程编程中的`wait()`, `notify()`, `notifyAll()`方法,它们用于线程间通信和同步。这些方法在`synchronized`代码块中使用,控制线程执行和资源访问。文章讨论了常见问题,如死锁、未捕获异常、同步使用错误及通知错误,并提供了生产者-消费者模型的示例代码,强调理解并正确使用这些方法对实现线程协作的重要性。
11 3
|
16天前
|
Java 数据库 Android开发
【专栏】Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
|
4天前
|
Java 数据库
【Java多线程】对线程池的理解并模拟实现线程池
【Java多线程】对线程池的理解并模拟实现线程池
13 1
|
4天前
|
设计模式 消息中间件 安全
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
10 0