Java面试题——并发编程

1、并发编程三要素?

(1) 原子性
     原子性指的是一个或多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。
(2) 可见性
     可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。
(3) 有序性
     有序性即程序的执行顺序按照代码的先后顺序来执行

2、实现可见性的方法有那些

synchronized 或者 Lock:保证同一个时刻只有一个线程获取锁执行代码,锁释放之前把最新的值刷新到主内存,实现可见性。

3、多线程的价值?

(1) 发挥多核 CPU 的优势

多线程,可以真正发挥出多核 CPU 的优势来,达到充分利用 CPU 的目的,采用多线程的方式去同时完成几件事情而不互相干扰。

(2) 防止阻塞

从程序运行效率的角度来看,单核 CPU 不但不会发挥出多线程的优势,反而会因为在单核 CPU 上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核 CPU 我们还是要应用多线程,就是为了防止阻塞。试想,如果单核 CPU 使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

(3) 便于建模

这是另外一个没有这么明显的优点了。假设有一个大的任务 A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务 A 分解成几个小任务,任务 B、任务 C、任务 D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

4、创建线程的有哪些方式?

(1)继承 Thread 类创建线程类;

(2)通过实现 Runnable 接口创建线程类;

(3)通过 Callable 和 Future 创建线程;(java8开始可以直接使用Lambda表达式创建Callable对象)

(4)通过线程池创建。

5、创建线程的三种方式的对比?

(1)采用实现 Runnable、Callable 接口的方式创建多线程。

优势是:线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

劣势是:编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread()方法。

(2)使用继承 Thread 类的方式创建多线程

优势是:编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread()方法,直接使用 this 即可获得当前线程。

劣势是: 线程类已经继承了 Thread 类,所以不能再继承其他父类。

(3)Runnable 和 Callable 的区别

1、Callable 规定(重写)的方法是 call(),Runnable 规定(重写)的方法是 run()。

2、Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值的。 

3、Call 方法可以抛出异常,run 方法不可以。

4、运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过 Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

6、线程的状态流转图。

线程的生命周期及五种基本状态:

7、Java 线程具有五中基本状态。

(1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t= new MyThread();

(2)就绪状态(Runnable):当调用线程对象的 start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待 CPU 调度执行,并不是说执行了t.start()此线程立即就会执行;

(3)运行状态(Running):当 CPU 开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

(4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。

根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1)等待阻塞:运行状态中的线程执行 wait()方法,使本线程进入到等待阻塞状态;
2)同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3)其他阻塞:通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。

(5)死亡状态(Dead):线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。

举个通俗点的例子来解释上面的五种状态:比如去超市买东西,拿着购物篮准备去选东西就是新建状态(new),结账要排队,排队就是就绪状态(Runnable),别人结账完了轮到你了,就是运行状态(Running),结账时发现手机没电了,没网了各种原因无法立即支付,就是阻塞(Blocked),你在那磨成太久了别人等不及了把你赶走,就是挂起,或者说你让别人先结账,等几分钟有网了或者充上电了再回来,就是睡眠,等你买东西整个过程结束了就是死亡状态了。

8、什么是线程池?有哪几种创建方式?

线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。由于创建和销毁线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候就可以考虑使用线程池来提升系统的性能。

java 提供了一个 java.util.concurrent.Executor 接口的实现用于创建线程池。

9、线程池的四种创建方式:

(1)newCachedThreadPoo l创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
(2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
(3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
(4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

10、线程池的优点?

(1)重用存在的线程,减少对象创建销毁的开;

(2)可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞;

(3)提供定时执行、定期执行、单线程、并发数控制等功能。

11、常用的并发工具类有哪些?

(1)CountDownLatch;CountDownLatch是一个同步计数器,初始化的时候传入需要计数的线程等待数,可以是需要等待执行完成的线程数。作用:用来协调多个线程之间的同步,或者说起到线程之间的通信作用。

(2)CyclicBarrier ;CyclicBarrier主要用于线程组内部之间的线程的相互等待问题,初始化的时候传入需要等待的线程数。作用:让一组线程达到某个屏障被阻塞,一直到组内最后一个线程达到屏障时,屏障开放,所有被阻塞的线程才会继续运行。

(3)Semaphore;Semaphore又名信号量,是操作系统中的一个概念,在Java并发编程中,信号量控制的是线程并发的数量。作用:Semaphore管理一系列许可证。

(4)Exchanger。Exchange类似于一个交换器,可以在对中对元素进行配对和交换的线程的同步点,用于两个线程间的数据交换。

12、synchronized 的作用?

在 Java 中,synchronized 关键字是用来控制线程同步的,就是在多线程的环境下,控制 synchronized 代码段不被多个线程同时执行。synchronized 既可以加在一段代码上,也可以OBDF加在方法上。

13、volatile 关键字的作用。

对于可见性,Java 提供了 volatile 关键字来保证可见性。当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详细的可以参见 java.util.concurrent.atomic 包下的类,比如 AtomicInteger。

14、什么是 CAS。

CAS 是 compare and swap 的缩写,即我们所说的比较交换。

cas 是一种基于锁的操作,而且是乐观锁。在 java 中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加 version 来获取数据,性能较悲观锁有很大的提高。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和 A 的值是一样的,那么就将内存里面的值更新成 B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里面的值被b 线程修改了,那么 a 线程需要自旋,到下次循环才有可能机会执行。

java.util.concurrent.atomic 包下的类大多是使用 CAS 操作来实现的(AtomicInteger,AtomicBoolean,AtomicLong)。

15、CAS 的问题。

(1)CAS 容易造成 ABA 问题

一个线程 a 将数值改成了 b,接着又改成了 a,此时 CAS 认为是没有变化,其实是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次version 加 1。在 java5 中,已经提供了 AtomicStampedReference 来解决问题。

(2)不能保证代码块的原子性

CAS 机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证 3 个变量共同进行原子性的更新,就不得不使用 synchronized 了。

(3)CAS 造成 CPU 利用率增加

之前说过了 CAS 里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用。

16、什么是 Future?

在并发编程中,我们经常用到非阻塞的模型,在之前的多线程的三种实现中,不管是继承 thread 类还是实现 runnable 接口,都无法保证获取到之前的执行结果。通过实现 Callback 接口,并用 Future 可以来接收多线程的执行结果。

Future 表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback 以便在任务执行成功或失败后作出相应的操作。

17、什么是 AQS。

AQS 是 AbustactQueuedSynchronizer 的简称,它是一个 Java 提高的底层同步工具类,用一个 int 类型的变量表示同步状态,并提供了一系列的 CAS 操作来管理这个同步状态。

AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask 等等皆是基于AQS 的。

18、AQS 支持两种同步方式:

(1)独占式

(2)共享式

这样方便使用者实现不同类型的同步组件,独占式如 ReentrantLock,共享式如Semaphore,CountDownLatch,组 合 式 的 如 ReentrantReadWriteLock。总之,AQS 为使用提供了底层支撑,如何组装实现,使用者可以自由发挥。

19、什么是乐观锁和悲观锁

(1)乐观锁:

就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。

(2)悲观锁:

还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像 synchronized,不管三七二十一,直接上了锁就操作资源了。

20、线程 B 怎么知道线程 A 修改了变量。

(1)volatile 修饰变量;

(2)synchronized 修饰修改变量的方法;

(3)wait/notify;

(4)while 轮询。

21、synchronized、volatile、CAS 比较。

(1)synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞;

(2)volatile 提供多线程共享变量可见性和禁止指令重排序优化;

(3)CAS 是基于冲突检测的乐观锁(非阻塞)。

22、sleep 方法和 wait 方法有什么区别?

这个问题常问,sleep 方法和 wait 方法都可以用来放弃 CPU 一定的时间,不同点在于如果线程持有某个对象的监视器,sleep 方法不会放弃这个对象的监视器,wait 方法会放弃这个对象的监视器。

23、为什么 wait()方法和 notify()/notifyAll()方法要在同步块中被调用?

这是 JDK 强制的,wait()方法和 notify()/notifyAll()方法在调用前都必须先获得对象的锁

24、多线程同步有哪几种方法?

Synchronized 关键字,Lock 锁实现,分布式锁等。

25、线程的调度策略。

线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:

(1)线程体中调用了 yield 方法让出了对 cpu 的占用权利;

(2)线程体中调用了 sleep 方法使线程进入睡眠状态;

(3)线程由于 IO 操作受到阻塞;

(4)另外一个更高优先级线程出现;

(5)在支持时间片的系统中,该线程的时间片用完。

26、怎么唤醒一个阻塞的线程?

如果线程是因为调用了 wait()、sleep()或者 join()方法而导致的阻塞,可以中断线程,并且通过抛出 InterruptedException 来唤醒它;如果线程遇到了 IO 阻塞,无能为力,因为 IO 是操作系统实现的,Java 代码并没有办法直接接触到操作系统。

27、Java 中用到的线程调度算法是什么?

抢占式。一个线程用完 CPU 之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

28、什么是自旋。

很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然 synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在 synchronized 的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。

29、Java Concurrency API 中的 Lock 接口(Lock interface)是什么?对比同步它有什么优势?

Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。

它的优势有:

(1)可以使锁更公平;

(2)可以使线程在等待锁的时候响应中断;

(3)可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间;

(4)可以在不同的范围,以不同的顺序获取和释放锁。

30、单例模式的线程安全性。

单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法:

(1)饿汉式单例模式的写法:线程安全

(2)懒汉式单例模式的写法:非线程安全

(3)双检锁单例模式的写法:线程安全

31、Java 线程数过多会造成什么异常?

(1)线程的生命周期开销非常高

(2)消耗过多的 CPU 资源  如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置。大量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量的线程在竞争 CPU资源时还将产生其他性能的开销。

(3)降低稳定性   JVM 在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并且承受着多个因素制约,包括 JVM 的启动参数、Thread 构造函数中请求栈的大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出OutOfMemoryError 异常。

32、如何提升系统的并发能力?

互联网分布式架构设计,提高系统并发能力的方式,方法论上主要有两种:垂直扩展(Scale Up)与水平扩展(Scale Out)。

垂直扩展:提升单机处理能力。垂直扩展的方式又有两种:

(1)增强单机硬件性能,例如:增加CPU核数如32核,升级更好的网卡如万兆,升级更好的硬盘如SSD,扩充硬盘容量如2T,扩充系统内存如128G;

(2)提升单机架构性能,例如:使用Cache来减少IO次数,使用异步来增加单服务吞吐量,使用无锁数据结构来减少响应时间;

在互联网业务发展非常迅猛的早期,如果预算不是问题,强烈建议使用“增强单机硬件性能”的方式提升系统并发能力,因为这个阶段,公司的战略往往是发展业务抢时间,而“增强单机硬件性能”往往是最快的方法。

不管是提升单机硬件性能,还是提升单机架构性能,都有一个致命的不足:单机性能总是有极限的。所以互联网分布式架构设计高并发终极解决方案还是水平扩展。

水平扩展:只要增加服务器数量,就能线性扩充系统性能。水平扩展对系统架构设计是有要求的,如何在架构各层进行可水平扩展的设计,以及互联网公司架构各层常见的水平扩展实践,是本文重点讨论的内容。

33、mysql数据库层的优化。

优化方向:数据表数据类型优化,索引优化,sql语句优化,存储引擎的优化,数据表结构设计的优化,数据库服务器架构的优化。

数据表数据类型优化:字段使用什么样的数据类型更合适,性能更快,tinyint、smallint、bigint,考虑空间和范围的问题;char、varchar,存储字符串长度是否固定;enum,特定固定的分类可以使用enum存储,效率更快;IP地址的存储,ip2long(),使用整型存储IP地址。

索引的优化:建立合适的索引,索引在什么场景下效率最高,索引的创建原则:不是越多越好,在合适的字段上创建合适的索引,复合索引的前缀原则,like查询%的问题,全表扫描优化,or条件索引使用情况,字符串类型索引失效的问题。

sql语句的优化:优化查询过程中的数据访问,优化长难句、特定类型的查询语句。使用limit,返回列不用*,变复杂为简单,切分查询,分解关联查询,优化count(),优化关联查询,优化子查询,优化group by和distinct,优化limit和union

存储引擎的优化:尽量使用innoDB存储引擎。

数据表结构设计的优化:分区操作,通过特定的策略对数据表进行物理拆分,对用户透明,partition by;分库分表,水平拆分,垂直拆分。

数据库架构的优化:主从复制,读写分离,双主热备,binlog日志,中继日志,主从库binlog的交换,事件传输;负载均衡,通过LVS的三种基本模式实现负载均衡,mycat数据库中间件实现负载均衡。

34、缓存。

缓存一词搞技术的都接触过,很多地方用到缓存。网站架构和网站开发中的缓存也是非常重要。这里先讲述最基本的两种缓存。高级和分布式的缓存在后面讲述。

架构方面的缓存,对Apache比较熟悉的人都能知道Apache提供了自己的缓存模块,也可以使用外加的Squid模块进行缓存,这两种方式均可以有效的提高Apache的访问响应能力。

网站程序开发方面的缓存,Linux上提供的Memory Cache是常用的缓存接口,可以在web开发中使用,比如用Java开发的时候就可以调用MemoryCache对一些数据进行缓存和通讯共享,一些大型社区使用了这样的架构。另外,在使用web语言开发的时候,各种语言基本都有自己的缓存模块和方法,PHP有Pear的Cache模块,Java就更多了,.net不是很熟悉,相信也肯定有。

35、负载均衡。

负载均衡将是大型网站解决高负荷访问和大量并发请求采用的终极解决办法。

负载均衡技术发展了多年,有很多专业的服务提供商和产品可以选择,我个人接触过一些解决方法,其中有两个架构可以给大家做参考。

1)硬件四层交换

第四层交换使用第三层和第四层信息包的报头信息,根据应用区间识别业务流,将整个区间段的业务流分配到合适的应用服务器进行处理。第四层交换功能就象是虚IP,指向物理服务器。它传输的业务服从的协议多种多样,有HTTP、FTP、NFS、Telnet或其他协议。这些业务在物理服务器基础上,需要复杂的载量平衡算法。在IP世界,业务类型由终端TCP或UDP端口地址来决定,在第四层交换中的应用区间则由源端和终端IP地址、 TCP和UDP端口共同决定。

在硬件四层交换产品领域,有一些知名的产品可以选择,比如Alteon、F5等,这些产品很昂贵,但是物有所值,能够提供非常优秀的性能和很灵活的管理能力。Yahoo中国当初接近2000台服务器使用了三四台Alteon就搞定了。

2)软件四层交换

大家知道了硬件四层交换机的原理后,基于OSI模型来实现的软件四层交换也就应运而生,这样的解决方案实现的原理一致,不过性能稍差。但是满足一定量的压力还是游刃有余的,有人说软件实现方式其实更灵活,处理能力完全看你配置的熟悉能力。

软件四层交换我们可以使用Linux上常用的LVS来解决,LVS就是Linux Virtual Server,他提供了基于心跳线heartbeat的实时灾难应对解决方案,提高系统的鲁棒性,同时可供了灵活的虚拟VIP配置和管理功能,可以同时满足多种应用需求,这对于分布式的系统来说必不可少。

一个典型的使用负载均衡的策略就是,在软件或者硬件四层交换的基础上搭建squid集群,这种思路在很多大型网站包括搜索引擎上被采用,这样的架构低成本、高性能还有很强的扩张性,随时往架构里面增减节点都非常容易。

36、集群与分布式。

集群是每台服务器都具有相同的功能,处理请求时调用那台服务器都可以,主要起分流作用。

分布式是将不同的业务放到不同的服务器中,处理一个请求可能需要用到多台服务器,这样就可以提高一个请求的处理速度,而且集群和分布式也可以同时使用。

集群有两个方式:一种是在静态资源集群。另一种是应用程序集群。静态资源集群比较简单。应用程序集群在处理过程中最核心的问题就是Session 同步问题。

Session 同步有两种处理方式:一种是在Session 发生变化后自动同步到其他服务器,另一种就是用个程序统一管理Session。所有集群的服务器都使用同一个Session,Tomcat 默认使用就是第一种方式,通过简单的配置就可以实现,第二种方式可以使用专门的服务器安装Mencached等高效的缓存程序统一来管理session,然后再应用程序中通过重写Request并覆盖getSession 方法来获取制定服务器中的Session。

对于集群来说还有一个核心的问题就是负载均衡,也就是接收到一个请求后具体分配到那个服务器去处理的问题,这个问题可以通过软件处理也可以使用专门的硬件(如:F5)解决。

图片

37、反向代理。

反向代理指的是客户端直接访问的服务器并不真正提供服务,它从别的服务器获取资源然后将结果返回给用户。

1、反向代理服务器和代理服务器的区别

代理服务器的作用是代我门获取想要的资源然后将结果返回给我们,所要获取的资源是我门主动告诉代理服务器的,比如,我门想访问Facebook,但是直接访问不了,这时就可以让代理服务器访问,然后将结果返回给我们。

反向代理服务器是我门正常访问一台服务器的时候,服务器自己去调用了别的服务器资源并将结果返回给我们,我门自己并不知道。

代理服务器是我们主动使用的,是为我们服务的,他不需要有自己的域名;反向代理服务器是服务器自己试用的,我门并不知道,它有自己的域名,我门访问它和访问正常的网址没有任何区别。

反向代理服务器主要有三个作用:

1. 可以作为前端服务器跟实际处理请求的服务器集成;

2. 可以做负载均衡

3. 转发请求,比如说可以将不同类型的资源请求转发到不同的服务器去处理。

38、分布式数据库。

分布式数据库是将不同的表放到不同的数据库中然后放到不同的服务器。这样需要调用多个表,则可以让多台服务器同时处理,从而提高处理速度。

分布式数据库是解决单个请求本身就非常复杂的问题,它可以将单个请求分配到多个服务器,使用分布式后的每个节点还可以同时使用读写分离,从来组成多个节群。

图片

使用分布式数据库有很多的问题需要解决,如分布式事务处理、多表查询等。

分布式的另外一种使用思路是将不同的业务数据表保存到不同的节点,让不同的业务调用不同的数据库,这样的做法其实就是和集群一样起分流作用,不过这种做法就不用同步数据了。

39、海量数据的解决方案。

1. 使用缓存;
2. 页面静态化技术;
3. 数据库优化;
4. 分离数据库中活跃的数据;
5. 批量读取和延迟修改;
6. 读写分离;
7. 使用NoSQL和Hadoop等技术;
8. 分布式部署数据库;
9. 应用服务和数据服务分离;
10. 使用搜索引擎搜索数据库中的数据;
11. 进行业务的拆分。

40、高并发情况下的解决方案。

1. 应用程序和静态资源文件进行分离;
2. 页面缓存;
3. 集群与分布式;
4. 反向代理;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值