欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-12 11:30:59
...

多线程的理论

  • 多线程:指的是这个程序(一个进程)运行时产生了不止一个线程
  • 并行与并发:
    • 并行:多个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
.
.
.

 

转载于:https://my.oschina.net/githubhty/blog/874082