文档章节

java多线程的理论、应用场景、实现方法及实际案例

帅的不像男的
 帅的不像男的
发布于 2017/04/06 15:02
字数 1954
阅读 1845
收藏 2

多线程的理论

  • 多线程:指的是这个程序(一个进程)运行时产生了不止一个线程
  • 并行与并发:
    • 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
    • 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
  • 线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码:
    void transferMoney(User from, User to, float amount){
      to.setMoney(to.getBalance() + amount);
      from.setMoney(from.getBalance() - amount);
    }
  • 同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。详细介绍可参考:https://my.oschina.net/u/3046428/blog/802087

多线程的应用场景

在现实应用中,很多时候需要让多个线程按照一定的次序来访问共享资源。例如,经典的生产者和消费者问题。

① 这类问题描述了这样一种情况,假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。如果仓库中没有产品,则生产者可以将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,知道仓库中再次放入产品为止。

② 显然,这是一个同步问题,生产者和消费者共享同一资源,并且,生产者和消费者之间彼此依赖,互为条件向前推进。

该如何变成程序来解决这个问题呢?

传统的思路是利用循环检测的方式来实现,通过重复检查某一个特定条件是否成立来决定线程的推进顺序。

比如,一旦生产者生产结束,它就继续利用循环检测来判断仓库中的产品是否被消费者消费,而消费者也是在消费结束后就会立即使用循环检测的方式来判断仓库中是否又放进产品。显然,这些操作是很耗CPU资源的,不值得提倡。

有没有更好的方法来解决这类问题呢?

Java提供了3个重要的方法巧妙解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。

① wait():可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。

② notify():可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行状态。

③ notifyAll():可以使所有正在等待队列中同一共享资源的线程从等待队列状态退出,进入可运行状态,此时,优先级最高的那个线程最先执行。

多线程实现方法

实现多线程有两种方式:一种继承Thread,另外一种是实现Runnable.

一般都实现Runnable,主要是为了避免单继承带来的弊端,另外实现Runnable,不用sychronized就可以共享资源。

取线程的名字,继承Thread直接就getName,实现Runnable,用Thread.currentThread().getName().因为

操作 线程的主要方法都在Thread里面。

线程同步的问题也是我们最关心的问题:

我个人认为实现线程同步:1、使用sychronized关键字,获取同步监视器的锁定。

2、显式加锁的方法Lock

3、使用wait让出线程,同时释放同步监视器的锁定,等另一个线程执行到一定条件,使用notify唤醒该线程

多线程的实际案例

实际案例以生产者和消费者为例子:

一个工厂要生产的同时检查仓库的库存,实时检查库存如果库存等于仓库最大容量时,停止生产,努力消费,实时检查消费时的库存,如果库存等于0时则停止销售努力生产。同时,可以思考一下,淘宝或者京东等等的商品购买时的库存是如何实时更新的,我之前的公司我做的时候是用的是单线程,用户下单则减少库存,我做的是服务器启动时开启单线程不停止,一直实时检查是否有未付款的订单的付款时间超过30分钟的就库存增加,并失效订单,这样确实可以实现,但是用多线程应该也能实现,并且线程不用一直启动。

回到生产消费的例子,假设最大库存为10,这样我们就能这样写;

生产者的线程类(用于生产产品并检查库存是否超过最大容量):

//生产者线程  
public class Productor extends Thread {
	private Warehouse warehouse = null;

	public Productor(Warehouse warehouse) {
		super();
		this.warehouse = warehouse;
	}

	@Override
	public void run() {
		warehouse.pushProduct();
	}
}

消费者的线程类(用于取出产品并检查库存是否为0)

//消费者线程
public class Consumer extends Thread {
	private Warehouse warehouse = null;

	public Consumer(Warehouse warehouse) {
		super();
		this.warehouse = warehouse;
	}

	@Override
	public void run() {
		warehouse.popProduct();
	}
}

仓库类(存放产品、取出产品、自身库存):

//仓库类  
public class Warehouse {
	private LinkedList<Product> warehouse = new LinkedList<Product>();

	// 放10个产品
	public synchronized void pushProduct() {
		for (int i = 0;; i++) {
			Product product = new Product(i);
			push(product);
		}
	}

	// 消费10个产品
	public synchronized void popProduct() {
		for (int i = 0;; i++) {
			pop();
		}
	}

	// 向仓库放产品
	private void push(Product product) {
		// 当仓库中存放了10个产品就等待并通知消费者来消费
		if (warehouse.size() == 10) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}// 等待并释放当前资源的锁
		}
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		warehouse.addFirst(product);
		System.out.println("放入仓库:" + product.toString());
		notify();// 通知消费者来消费
	}

	// 向仓库中取产品
	private void pop() {
		// 当仓库中产品为0时就等待并通知生产者来生产
		if (warehouse.size() == 0) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}// 等待并释放当前资源的锁
		}
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		Product product = warehouse.removeFirst();
		System.out.println("取出仓库:" + product.toString());
		notify();// 通知生产者来生产
	}
}

main方法:

	public static void main(String[] args) {
		Warehouse warehouse=new Warehouse();
		Productor productor=new Productor(warehouse);
		Consumer consumer=new Consumer(warehouse);
		productor.start();
		consumer.start();
	}

打印结果:

放入仓库:产品:1
放入仓库:产品:2
放入仓库:产品:3
放入仓库:产品:4
放入仓库:产品:5
放入仓库:产品:6
放入仓库:产品:7
放入仓库:产品:8
放入仓库:产品:9
放入仓库:产品:10
取出仓库:产品:10
取出仓库:产品:9
取出仓库:产品:8
取出仓库:产品:7
取出仓库:产品:6
取出仓库:产品:5
取出仓库:产品:4
取出仓库:产品:3
取出仓库:产品:2
取出仓库:产品:1
放入仓库:产品:11
放入仓库:产品:12
.
.
.

 

© 著作权归作者所有

帅的不像男的
粉丝 12
博文 91
码字总数 49823
作品 0
深圳
程序员
私信 提问
Java并发编程面试必备的知识点!

相信不用我说,大家也都知道掌握并发编程对于一个 Java 程序员的重要性。但相对于其他 Java 基础知识点来说,并发编程更加抽象,涉及到的知识点很多很零散,实际使用也更加麻烦。以至于很多人...

架构师技术联盟
03/05
0
0
学Android开发,入门语言java知识点

Android是一种以Linux为基础的开源码操作系统,主要使用于便携设备,而linux是用c语言和少量汇编语言写成的,如果你想研究Android,就去学java语言吧。 Android开发入门教程 -Java语言,最差...

抉择很难
2015/12/11
221
0
【阿里面试系列】Java线程的应用及挑战

文章简介 上一篇文章【「阿里面试系列」搞懂并发编程,轻松应对80%的面试场景】我们了解了进程和线程的发展历史、线程的生命周期、线程的优势和使用场景,这一篇,我们从Java层面更进一步了解...

Java架构资源分享
2018/12/07
113
0
Java开发学习之三版本简介 java编程

  Java编程语言,在更迭迅速的互联网领域多年屹立不倒,足以得见Java这门语言旺盛的生命力,因此,会有很多想要进入互联网领域的朋友,想要学Java来转行开发。但是,所谓“隔行如隔山”,j...

老男孩Linux培训
2018/06/05
5
0
Java大佬带你详细了解,线程的应用及挑战

文章简介 上一篇文章我们了解了进程和线程的发展历史、线程的生命周期、线程的优势和使用场景,这一篇,我们从Java层面更进一步了解线程的使用 内容导航 并发编程的挑战 线程在Java中的使用 ...

Java-飞鱼
06/25
66
0

没有更多内容

加载失败,请刷新页面

加载更多

c++ 虚基类

c++ 虚基类 p556

天王盖地虎626
26分钟前
39
0
Java中的面向对象

一、面向对象 面向对象和面向过程的区别 过程就是函数,就是写方法,就是方法的一种实现。 对象就是将函数,属性的一种封装。用人们思考习惯的方式思考问题。 如何自定义类 修饰符 类名{ //成...

zhiruochujian
35分钟前
3
0
k8s删除Terminating状态的命名空间

背景: 我们都知道在k8s中namespace有两种常见的状态,即Active和Terminating状态,其中后者一般会比较少见,只有当对应的命名空间下还存在运行的资源,但是该命名空间被删除时才会出现所谓的...

Andy-xu
38分钟前
30
0
seata源码阅读笔记

seata源码阅读笔记 本文没有seata的使用方法,怎么使用seata可以参考官方示例,详细的很。 本文基于v0.8.0版本,本文没贴代码。 seata中的三个重要部分: TC:事务协调器,维护全局事务和分支...

东都大狼狗
51分钟前
21
0
Rust:最小化窗口后 CPU占用率高 (winit,glutin,imgui-rust)

最近试着用 imgui-rust 绘制界面,发现窗口最小化后CPU占用会增大。 查询的资料如下: https://github.com/rust-windowing/winit/issues/783 https://github.com/ocornut/imgui/issues/1151 ...

reter
55分钟前
31
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部