您的位置:首页技术文章
文章详情页

Java 实现并发的几种方式小结

【字号: 日期:2022-08-12 13:39:41浏览:4作者:猪猪
Java实现并发的几种方法

Java程序默认以单线程方式运行。

synchronized

Java 用过synchronized 关键字来保证一次只有一个线程在执行代码块。

public synchronized void code() { // TODO}Volatile

Volatile 关键字保证任何线程在读取Volatile修饰的变量的时候,读取的都是这个变量的最新数据。

Threads 和 Runnable

public class MyRunnable implements Runnable { @Override public void run() { // TODO }}

import java.util.ArrayList;import java.util.List;public class Main { public static void main(String[] args) {Runnable task = new MyRunnable();Thread worker = new Thread(task);worker.setName(’Myrunnable’);worker.start();}

创建thread会有很多overhead,性能低且不易管理

Thread pools

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Main { private static final int NUMOFTHREDS = 5; public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(NUMOFTHREDS);for (int i = 0; i < 50; i++) { Runnable worker = new MyRunnable(i); executor.execute(worker);}// executor不接受新的threadsexecutor.shutdown();// 等待所有threads结束executor.awaitTermination();System.out.println('Finished all threads'); }}Futures 和 Callables

因为Runnable对象无法向调用者返回结果,我们可以用Callable类来返回结果。

package de.vogella.concurrency.callables;import java.util.concurrent.Callable;public class MyCallable implements Callable<Long> { @Override public Long call() throws Exception { // TODO int sum = 1;return sum; }}

import java.util.ArrayList;import java.util.List;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class CallableFutures { private static final int NUMOFTHREDS = 5; public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(NUMOFTHREDS);List<Future<Long>> list = new ArrayList<Future<Long>>();for (int i = 0; i < 10; i++) { Callable<Long> worker = new MyCallable(); Future<Long> submit = executor.submit(worker); list.add(submit);}long sum = 0;for (Future<Long> future : list) { try {sum += future.get(); } catch (InterruptedException e) {e.printStackTrace(); } catch (ExecutionException e) {e.printStackTrace(); }}System.out.println(sum);executor.shutdown(); }}CompletableFuture

CompletableFuture 在Future的基础上增加了异步调用的功能。callback()函数Thread执行结束的时候会自动调用。

CompletableFuture既支持阻塞,也支持非阻塞的callback()

import java.util.concurrent.CompletableFuture;import java.util.concurrent.ExecutionException;public class CompletableFutureSimpleSnippet { public static void main(String[] args) { CompletableFuture<Integer> data = createCompletableFuture().thenApply((Integer count) -> { int transformedValue = count * 10; return transformedValue;}); try { int count = futureCount.get(); } catch (InterruptedException | ExecutionException ex) { } } private static CompletableFuture<Integer> createCompletableFuture() {CompletableFuture<Integer> futureCount = CompletableFuture.supplyAsync(() -> { return 1;});return futureCount; }}

补充:Java如何处理高并发的情况

为了更好的理解并发和同步,需要先明白两个重要的概念:同步和异步

所谓同步,可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到返回的值或消息后才往下执行其它的命令。 同步就是一件事,一件事情一件事的做。

异步,执行完函数或方法后,不必阻塞性地等待返回值或消息,只需要向系统委托一个异步过程,那么当系统接收到返回值或消息时,系统会自动触发委托的异步过程,从而完成一个完整的流程。异步就是,做一件事情,不影响做其他事情。

同步关键字synchronized,假如这个同步的监视对象是类的话,那么如果当一个对象 访问类里面的同步方法的话,那么其它的对象如果想要继续访问类里面的这个同步方法的话,就会进入阻塞,只有等前一个对象 执行完该同步方法后当前对象才能够继续执行该方法。这就是同步。相反,如果方法前没有同步关键字修饰的话,那么不同的对象可以在同一时间访问同一个方法,这就是异步。

脏数据:就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据(Dirty Data),依据脏数据所做的操作可能是不正确的。

1、什么是并发问题

多个进程或线程同时(在同一段时间内)访问同一资源会产生并发问题。

比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户减去 50元,A先提交,B后提交。 最后实际账户余额为1000-50=950元,但本该为 1000+100-50=1050。这就是典型的并发问题。如何解决?

处理并发和同同步问题主要是通过锁机制。

2、如何处理并发和同步

一种是java中的同步锁,典型的就是同步关键字synchronized。

另外一种比较典型的就是悲观锁和乐观锁。

在java中有两种方式实现原子性操作(即同步操作):

1)使用同步关键字synchronized

2)使用lock锁机制其中也包括相应的读写锁

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自 外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。

乐观锁,大多是基于数据版本 Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来 实现。 读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提 交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据 版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

乐观锁机制是在我们的系统中实现,来自外部系统的用户 余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在 系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如 将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途 径,而不是将数据库表直接对外公开)。

【谨防在此,面试官会问到死锁的相关问题!!!关于死锁的问题,在其余某篇博客都有说明】

3、常见并发同步案例分析案例一、订票系统案例

某航班只有一张机票,假定有1w个人打开你的网站来订票,问你如何解决并发问题(可扩展到任何高并发网站要考虑的并发读写问题)

假定我们采用了同步机制或者数据库物理锁机制,如何保证1w个人还能同时看到有票,显然会牺牲性能,在高并发网站中是不可取的。

采用乐观锁即可解决此问题。乐观锁意思是不锁定表的情况下,利用业务的控制来解决并发问题,这样即保证数据的并发可读性又保证保存数据的排他性,保证性能的同时解决了并发带来的脏数据问题。

如何实现乐观锁:

前提:在现有表当中增加一个冗余字段,version版本号, long类型

原理:

1)只有当前版本号>=数据库表版本号,才能提交

2)提交成功后,版本号version ++

案例二、股票交易系统、银行系统,大数据量你是如何考虑的

首先,股票交易系统的行情表,每几秒钟就有一个行情记录产生,一天下来就有(假定行情3秒一个) 股票数量×20×60*6 条记录,一月下来这个表记录数量多大? 一张表的记录数超过100w后 查询性能就很差了,如何保证系统性能?

再比如,中国移动有上亿的用户量,表如何设计?把所有用于存在于一个表?

所以,大数量的系统,必须考虑表拆分-(表名字不一样,但是结构完全一样),通用的几种方式:(视情况而定)

1)按业务分,比如 手机号的表,我们可以考虑 130开头的作为一个表,131开头的另外一张表 以此类推

2)利用表拆分机制做分表

3)如果是交易系统,我们可以考虑按时间轴拆分,当日数据一个表,历史数据弄到其它表。这里历史数据的报表和查询不会影响当日交易。

此外,我们还得考虑缓存

这里的缓存独立于应用,依然是内存的读取,假如我们能减少数据库频繁的访问,那对系统肯定大大有利的。比如一个电子商务系统的商品搜索,如果某个关键字的商品经常被搜,那就可以考虑这部分商品列表存放到缓存(内存中去),这样不用每次访问数据库,性能大大增加。

4、常见的提高高并发下访问的效率的手段首先要了解高并发的的瓶颈在哪里?

1、可能是服务器网络带宽不够

2.可能web线程连接数不够

3.可能数据库连接查询上不去。

根据不同的情况,解决思路也不同。

1、像第一种情况可以增加网络带宽,DNS域名解析分发多台服务器。

2、负载均衡,前置代理服务器nginx、apache等等

3、数据库查询优化,读写分离,分表等等

最后复制一些在高并发下面需要常常需要处理的内容

1、尽量使用缓存,包括用户缓存,信息缓存等,多花点内存来做缓存,可以大量减少与数据库的交互,提高性能。

2、用jprofiler等工具找出性能瓶颈,减少额外的开销。

3、优化数据库查询语句,减少直接使用hibernate等工具的直接生成语句(仅耗时较长的查询做优化)。

4、优化数据库结构,多做索引,提高查询效率。

5、统计的功能尽量做缓存,或按每天一统计或定时统计相关报表,避免需要时进行统计的功能。

6、能使用静态页面的地方尽量使用,减少容器的解析(尽量将动态内容生成静态html来显示)。

7、解决以上问题后,使用服务器集群来解决单台的瓶颈问题。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持好吧啦网。

标签: Java
相关文章: