文章目录
- JUC 并发编程
- 1、什么是高并发
- 2、Java 实现多线程的第三种方式
- 3、sleep 和 wait 方法的区别
- 4、synchronized 锁定的是谁?
- 5、ConcurrentModificationException
- 6、JUC 工具类
- 7、读写锁
- 8、线程池
JUC 并发编程
JUC 是指 Java 并发编程工具包
java.util.concurrent JDK 中的一个包,包下全部都是 Java 并发编程相关的类/接口
为什么公司如此看重并发编程的能力,并发编程的目的就是充分利用计算机的资源,把计算机的性能发挥到极致。
1、什么是高并发
并行和并发
并发是指多线程操作同一个资源,但不是同时操作,而是交替操作,单核 CPU。
并行才是真正的多个线程同时执行,多核 CPU,每个线程使用一个 CPU 的资源来运行。
我们所说的并发其实是一种程序的设计标准,在单核 CPU 的情况下,开发出来的代码应该更加充分利用资源,有效提升程序的效率。
编写出来的代码具备处理多个任务在同一时间段内执行的能力。
高并发是互联网分布式系统架构设计中必须要考虑的因素之一,它是指通过设计保证系统在互联网海量用户请求的情况下,能够确保系统正常运行。
互联网架构中,如何实现高并发设计?
垂直扩展和水平扩展
垂直扩展
提升单机的处理能力
1、增强单机的硬件性能,CPU 核数、提升内存、硬盘扩容、网卡升级。。。
2、提升软件架构性能,使用缓存来提高查询效率,使用异步请求来提升服务吞吐量,使用 NoSQL 提升数据访问能力,使用并发框架。。。
垂直扩展有上限,一定会达到某个瓶颈无法再优化。
水平扩展
水平扩展理论上没有上限,所以互联网分布式架构设计最终的解决方案还是水平扩展。
集群和分布式的区别?
集群是指每台服务器所完成的工作一样,通过增加服务器的数量来提高并发能力。
分布式是指将系统拆分成不同的模块,交给不同的服务器来完成。
饭店,一个厨师,多雇几个水平一样的厨师来应对客流量,这就是集群。
不增加厨师的数量,而是把做菜分步骤完成,洗菜,切菜,炒菜,然后给厨师雇两个助手,分别完成洗菜和切菜,厨师只负责炒菜。
2、Java 实现多线程的第三种方式
实现 Callable 接口,与 Runnable 的区别在于 Callable 方法有返回值。
package com.htl.test2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) {
//任务
MyCallable myCallable = new MyCallable();
//线程
FutureTask<String> futureTask = new FutureTask<>(myCallable);
new Thread(futureTask).start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("callable");
return "success";
}
}
Callable 和 Runnable 的区别
- Callable 可以在任务结束后提供一个返回值,Runnable 没有这个功能。
- Callable 中的 call 方法可以抛出异常,Runnable 的 run 不能抛异常。
3、sleep 和 wait 方法的区别
让线程暂停执行任务
来自不同的类
sleep 是 Thread 类中的方法
wait 是 Object 类中的方法
sleep 是直接让当前线程暂停执行,进入阻塞状态。暂停不是看用哪个线程对象调用,而是在哪调用;在哪调就是让当前线程休眠。
让主线程休眠
package com.htl.test;
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("A----------------");
}
});
thread.start();
try {
//让主线程休眠
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println("=====================B");
}
}
}
让子线程休眠
package com.htl.test;
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(()->{
try {
//让子线程休眠
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println("A----------------");
}
});
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println("=====================B");
}
}
}
wait 也是让线程休眠,但是不直接作用于线程对象,而是作用于线程对象访问的资源,Thread —> data,调用 data.wait,此时 Thread 就会休眠。
wait 方法有一个前提,当前线程对象必须拥有 data 对象,所以 wait 方法只能在同步方法或者同步代码块中调用,否则抛出异常。
package com.htl.test;
import java.util.concurrent.TimeUnit;
public class Test2 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.test(i);
}
}).start();
}
}
class Data {
public synchronized void test(int i){
if(i == 5){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i + "--------------------------");
}
}
wait 让线程对象休眠,没有阻塞时间,如果不加处理,线程会永远阻塞下去。
如何解除阻塞?
1、指定 wait 时间,wait(long millis)
if(i == 5){
try {
this.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2、通过 notify 方法唤醒线程 (notify方法跟wait方法一样,必须在同步方法中调用)
package com.htl.test;
import java.util.concurrent.TimeUnit;
public class Test2 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.test(i);
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(8);
} catch (InterruptedException e) {
e.printStackTrace();
}
data.test2();
}).start();
}
}
class Data {
//在i==5的时候让线程休眠
public synchronized void test(int i){
if(i == 5){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i + "--------------------------");
}
//唤醒线程
public synchronized void test2(){
this.notify();
}
}
是否释放锁
wait 会释放锁,sleep 不会释放锁
4、synchronized 锁定的是谁?
一、如果 synchronized 修饰非静态方法,则锁定的就是方法调用者。
package com.htl.test2;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
data.func1();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
data.func2();
},"B").start();
}
}
class Data{
public synchronized void func1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1...");
}
public synchronized void func2(){
System.out.println("2...");
}
}
synchronized 修饰的是 func1,func2,所以谁调用 func1、func2 就锁谁。
对代码进行修改:
package com.htl.test2;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
Data data1 = new Data();
Data data2 = new Data();
new Thread(()->{
data1.func1();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
data2.func2();
},"B").start();
}
}
class Data{
public synchronized void func1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1...");
}
public synchronized void func2(){
System.out.println("2...");
}
}
不会排队,继续修改:
package com.htl.test2;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
data.func1();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
data.func3();
},"B").start();
}
}
class Data{
public synchronized void func1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1...");
}
public synchronized void func2(){
System.out.println("2...");
}
public void func3(){
System.out.println("3...");
}
}
不会排队,因为 func3 的调用不需要锁定资源,无需等待 func3 的执行完毕即可执行。
如果方法添加了 synchronized ,不是说不能调,只是调用的是需要锁定当前对象,如果没有添加 synchronized,不需要考虑任何锁定的问题,直接调。
二、如果 synchronized 修饰静态方法,则锁定的就是类,无论多少个对象调用,都会同步。
package com.htl.test2;
import java.util.concurrent.TimeUnit;
public class Test2 {
public static void main(String[] args) {
Data2 data2 = new Data2();
Data2 data1 = new Data2();
new Thread(()->{
data1.func1();
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
data2.func2();
}).start();
}
}
class Data2 {
public synchronized static void func1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1...");
}
public synchronized static void func2(){
System.out.println("2...");
}
}
三、如果 synchronized 静态方法和 synchronized 非静态方法同时存在,静态方法锁类,实例方法锁对象。
package com.htl.test3;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
Data3 data = new Data3();
Data3 data2 = new Data3();
new Thread(()->{
data.func1();
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
data2.func2();
}).start();
}
}
class Data3{
public synchronized static void func1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1...");
}
public synchronized void func2(){
System.out.println("2...");
}
}
四、如果 synchronized 修饰的是代码块,则锁定的是传入的对象。
能否实现同步,就看锁定的对象是否位同一个对象
package com.htl.test4;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
Data4 data = new Data4();
for (int i = 0; i < 5; i++) {
Integer num = Integer.valueOf(1);
new Thread(()->{
data.func(num);
}).start();
}
}
}
class Data4{
public void func(Integer num){
synchronized (num){
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
}
此时会同步,因为 num = 1 在内存中只有一份,5 个线程争夺同一个资源。
package com.htl.test4;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
Data4 data = new Data4();
for (int i = 0; i < 5; i++) {
Integer num = Integer.valueOf(i);
new Thread(()->{
data.func(num);
}).start();
}
}
}
class Data4{
public void func(Integer num){
synchronized (num){
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
}
不会同步,因为 5 个线程有 5 个不同的 num,不会产生争夺,而是各用各的。
public class Test {
public static void main(String[] args) {
Account account = new Account();
new Thread(()->{
account.count();
},"A").start();
new Thread(()->{
account.count();
},"B").start();
}
}
class Account{
private Integer num = 0;
private Integer id = 0;
public void count(){
synchronized (id){
num++;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "是第" + num + "位访客");
}
}
}
5、ConcurrentModificationException
并发修改异常,集合 List Set Map
package com.htl.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("a");
System.out.println(list);
}).start();
}
}
}
当多个线程同时对集合对象进行读写操作的时候,就会抛出 ConcurrentModificationException 异常。
ArrayList 是线程不安全的
如何解决?使用线程安全的类来完成数据的装载。
1、直接将 ArrayList 替换为 Vector
package com.htl.test;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("a");
System.out.println(list);
}).start();
}
}
}
2、Collections.synchronizedList
package com.htl.test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("a");
System.out.println(list);
}).start();
}
}
}
3、JUC CopyOnWriteArrayList
package com.htl.test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("a");
System.out.println(list);
}).start();
}
}
}
CopyOnWrite 写时复制,当我们往一个容器中添加元素的时候,不是直接操作这个容器,而是将原来的容器先复制一份,往复制出来的新容器中添加元素,添加完毕,再将原容器的引用指向新容器,以此来解决并发修改异常,实际上就是实现了读写分离。
6、JUC 工具类
CountDownLatch
减法计数器
Java Memory Model Java 内存模型 JMM
工作内存,当一个线程对数据进行操作的时候,不会直接操作主内存,而是会将
主内存中的数据进行复制,复制到工作内存中,线程操作的是工作内存中的数据,操作
完成之后,再将工作内存中的数据同步到主内存中。
package com.htl.test;
import java.util.concurrent.CountDownLatch;
public class Test2 {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(100);
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("+++++++++++++++++++++++++Thread");
countDownLatch.countDown();
}
}).start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println("main-------------------------");
}
}
}
countDown():计数器减一
await():计数器停止,唤醒其他线程
CyclicBarrier
加法计数器
package com.htl.test;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(30, ()->{
System.out.println("放行");
});
for (int i = 0; i < 90; i++) {
final int temp = i;
new Thread(()->{
//lambda表达式中只能访问final修饰的变量
System.out.println("-->" + temp);
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore
计数信号量,实现限流操作,限制可以访问某些资源的线程数量。
Semaphore 只有 3 个操作
- 初始化
- 获得许可
- 释放
package com.htl.test;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreTest {
public static void main(String[] args) {
//初始化
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 15; i++) {
new Thread(()->{
try {
//获得许可
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "进店购物");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "出店");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
7、读写锁
ReadWriteLock 接口,实现类 ReentrantReadWriteLock,可以多线程同时读,但是同一时间只能有一个线程进行写入。
读锁是一个共享锁,写锁是一个独占锁。
package com.htl.test;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static void main(String[] args) {
Cache2 cache = new Cache2();
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
cache.write(temp,String.valueOf(temp));
}).start();
}
for (int i = 0; i < 5; i++) {
final int temp = i;
new Thread(()->{
cache.read(temp);
}).start();
}
}
}
class Cache2{
private Map<Integer,String> map = new HashMap<>();
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 写操作
* @param key
* @param value
*/
public void write(Integer key,String value){
readWriteLock.writeLock().lock();
System.out.println(key + "开始写入");
map.put(key, value);
System.out.println(key + "写入完毕");
readWriteLock.writeLock().unlock();
}
/**
* 读操作
* @param key
*/
public void read(Integer key){
readWriteLock.readLock().lock();
System.out.println(key + "开始读取");
map.get(key);
System.out.println(key + "读取完毕");
readWriteLock.readLock().unlock();
}
}
8、线程池
存放线程对象的缓冲池,为了节约资源。
预先创建一定数量的线程对象,存入缓冲池中,需要用的时候直接从缓冲池中取出,用完不销毁,重新放回到缓冲池中,供下一次请求使用。
优势:
- 提高线程的利用率
- 提高响应速度
- 便于统一管理线程对象
- 可控制最大并发数
7 大核心参数:
1、corePoolSize 核心池的大小,目前上班的柜台数
2、maximumPoolSize 线程池最大线程数,所有的柜台数(包括上班和不上班的)
3、keepAliveTime:空闲线程的存活时间
4、unit:keepAliveTime 时间单位
5、workQueue:阻塞队列,等候区
6、threadFactory:线程工厂,生成线程对象
7、handler:拒绝任务策略
package com.htl.test;
import java.util.concurrent.*;
public class Test2 {
public static void main(String[] args) {
//线程池对象
ExecutorService executorService = new ThreadPoolExecutor(
2,
3,
1L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
for (int i = 0; i < 6; i++) {
executorService.execute(()->{
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "===>办理业务");
});
}
executorService.shutdown();
}
}
Executors 提供了三种快速创建线程池的方式
newSingleThreadExecutor():线程池中只有一个线程对象
newFixedThreadPool(5):线程池中有 5 个线程对象
newCachedThreadPool():线程池中线程对象随机分配,由电脑配置决定
package com.htl.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test3 {
public static void main(String[] args) {
//单例线程池,只有一个线程对象
// ExecutorService executorService = Executors.newSingleThreadExecutor();
// ExecutorService executorService = Executors.newFixedThreadPool(5);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10000; i++) {
final int temp = i;
executorService.execute(()->{
System.out.println(Thread.currentThread().getName() + ":" + temp);
});
}
executorService.shutdown();
}
}
handler 提供了四种拒绝策略
AbortPolicy 直接抛出异常
DiscardPolicy 放弃任务,不抛出异常
DiscardOldestPolicy 尝试与阻塞队列最前面的任务去争夺,不抛出异常
CallerRunsPolicy 谁调用谁处理