0%

文章字数:857,阅读全文大约需要3分钟

一、如何实现

  • 栈封闭,变量处于方法内部,则此变量处于线程私有区域,是安全的
  • 无状态,例如基本的加减乘除,无论多少线程调用都不会有问题
  • 类不可变,成员变量位final的基本类型的类,此类只能初始化时赋值,其他时候无法改变。或者成员变量无法获取,只能做判断。(Akka框架)
  • volatile,一个线程写,多个读的情况可以实现线程安全。例如线程安全的map,写加锁,读不加。
  • 加锁或CAS
  • 安全的发布,所谓的发布即将内部的成员变量的引用return,外部可以使用此引用进行对象操作。安全的发布就是返回类的副本,让外部无法直接操作
  • ThreadLocal,线程数据隔离

二、Servlet线程不安全

  • servlet很少有线程安全的需求
  • 接受请求,返回都是一个线程负责
  • 请求到达生成,请求返回销毁

三、线程不安全问题

  • 死锁,两个及以上线程执行中资源竞争阻塞,如果没其他因素推动,将一直持续下去的情况。(多个资源占用,一个资源不会发生)
    怀疑死锁
    jps -m查看当前机子上运行的java及进程号
    jstack id检测死锁

  • 动态顺序死锁,根据参数,锁定的顺序不确定,可能导致的死锁。

  1. 首先要将不确定的顺序变成固定的,可以使用hashCode将两个锁的排序指定锁顺序。 System.identityHashCode(xx)可以直接调用最底层的hashCode,避免调用对象重写的HashCode
    如果hash值一致,则竞争获取一个static的锁,获胜者即可将所有资源获取。

  2. 使用显式锁tryLock去分别获取锁,获取不到则释放重新尝试
    释放后需要休眠随机时间,避免死锁。

  • 线程饥饿,低优先级的线程总是拿不到执行时间
  • 性能,线程切换需要时间,如果切换的消耗不能抵消节约的时间,就不划算。
  1. 延迟时间和吞吐量(相应时间和处理任务能力),往往是矛盾的。一般来说,吞吐量更受重视。
  2. 优先保证程序正确,确实达不到要求时再考虑提供速度
  3. 一切以测试为基准
  4. 锁的范围,粒度尽量小。范围指定是代码的区域,粒度指的是如果有多个锁保护的对象,对象直接相互独立,就可以使用多个锁。
  5. 锁分段,替换独占锁。

四、单例模式线程安全

  1. 懒汉模式-双重检查可能会有势力内存地址以及引用,但是对象内部值还没分配的情况,可以增加volita修饰变量。
  2. 懒汉模式-延迟占位模式,使用私有内部类类持有变量,获取时用私有类.变量获取,虚拟机保证在获取时才会实例化内部类生成变量。

文章字数:578,阅读全文大约需要2分钟

降低线程创建和销毁的消耗,提高速度,节约资源,线程可管理。

参数含义

  • int corePoolSize核心线程数
  • int maximumPoolSize最大线程数
  • long keepAliveTime空闲线程存活时间
  • workQueue任务队列
  • threadFactory创建线程的工厂,主要赋予名字
  • RejectedExcutionHandler线程池饱和策略
  1. AbortPolicy抛出异常,默认
  2. CallerRunsPolicy调用者线程执行
  3. DiscardOldestPolicy丢弃最老的任务
  4. DiscardPolicy直接丢弃

实现自己的饱和策略,实现RejectedExcutionHandler就行,保存到数据库之类的。

提交任务

  • execute(Runnable command)不需要返回
  • Future<T> submit(Callable<T> task)有返回

最佳线程数

  • CPU密集型,Runtime.getRuntime().availableProcessors() + 1最大核心数+1
  • IO密集型,尽量大,可以是CPU数 * 2

预定义线程池(全部实现ExecutorService接口)

  • FixedThreadPool
  • SingleThreadExecutor
  • CachedThreadPool
  • WorkStealingPool基于fork/join
  • ScheduledThreadPoolExecutor定时执行的任务,可以指定抛出异常,不影响线程执行。
  • newSingleThreadScheduledExecutor包含一个线程,只需要单个线程执行周期任务,保证任务执行顺序
  1. schedule执行一次,可以延时执行
  2. scheduleAtFixedRate提交固定时间执行任务
  3. scheduleWhithFixedDelay提交固定延时间隔执行的任务
  • scheduleAtFixedRate任务超时,当前一个任务超出下个任务的超时时间,下个任务会在上一个执行后马上执行。

自定义线程池

  • new ThreadPoolExecutor(...)

CompletionService

  • 用于接收线程池返回结果(先结束的先拿)
1
2
3
4
5
6
7
8
// 包装线程池
CompletionService<Integer> cService = new ExcutorCompletionService<>(pool);
// 提交若干任务
cService.submit(new WorkTask(..));
cService.submit(new WorkTask(..));
// 按照执行速度获取执行结果
cService.take().get();
cService.take().get();
  • 内部包装了task会将执行成功的结果放到内部维护的队列中。

文章字数:872,阅读全文大约需要3分钟

整理自聊聊高并发(二)结合实例说说线程封闭和背后的设计思想

高并发问题简述

  1. 定义: 来自《java并发变成实战》的定义。线程安全指的是多个线程访问某个类时,这个类始终能表现正确行为。
1
2
3
4
我的理解是线程安全问题就是多个线程同时操作一个数据,
导致每个线程在读取并操作期间数据可能会被其它线程篡改,
即不能表现正确行为。
而栈空间(方法栈)的数据是线程私有的,所以只有类有这个问题。
  1. 思路: 线程安全问题,就是对象状态问题。如果对象无状态(不变性,操作前后一样。),或者避免多个线程共享,那就是线程安全的。只有对象不可避免要被多个线程操作时才会有多线程问题。所以业务代码尽量做到无状态。
1
2
3
4
思考流程
1. 能否做成无状态的不变对象
2. 能否线程封闭,不让其他线程操作
3. 实在需要多线程操作,那么采用什么同步技术

线程封闭技术

  1. 栈封闭: 多使用局部变量,局部变量属于方法,方法属于栈,栈属于本线程。
  2. ThreadLocal: 限制其他线程访问的全局变量。本质是一个Map,每个线程只能操作自己作为key下的值。
  3. 程序控制线程封闭: 思想,把处理对象状态的代码放到一个线程中。例子:NettyEventLoop被设计成一个线程池(工作线程+任务队列)。请求被插入任务队列,工作线程再一个个取。这样保证了请求有序处理。
1
2
3
思路:
1. 把用户状态相关代码放到一个队列中,由一个线程处理。
2. 考虑是否隔离用户之间状态,及一个用户一个任务队列还是多个用户使用一个。

一般设计

1
2
3
4
1. 有界任务队列和不限个数的工作线程,任务队列有界保证不会内存被一直加入的请求撑爆。不限个数的工作线程保证任务队列的请求都能被相应,而不至于以为工作线程的速度慢而产生积压。
2. 线程请求尽量快,尽量不超过100ms
3. 单线程处理时间由于任务大而耗时可以拆解成小任务多次执行
4. 小任务还是慢则可以使用线程,异步处理,然后使用线程定时查看状态或者回调函数的形式。

文章字数:592,阅读全文大约需要2分钟

为什么需要多线程

  1. 现在的计算机大多数都是多核cpu,单线程运行程序就只能利用其中一个。
  2. 所以根据cpu核心数选取正确的线程数能有效利用资源,提升性能。

程序分类

  1. cpu密集型:就是线程大多数时间都是用于cup的数据处理。
  2. I/O密集型:相对的,这个是程序大多操作都是在执行I/O操作

线程数量选取

  1. cpu密集型:
    理论上线程数量=cpu核心数就行了,一个cpu核心执行一个线程,避免切换线程带来的消耗。
    但是实际上会设置cpu逻辑核心数+1。
    《Java并发编程》

    计算(CPU)密集型的线程恰好在某时因为发生一个页错误或者因其他原因而暂停,刚好有一个“额外”的线程,可以确保在这种情况下CPU周期不会中断工作。

  2. I/O密集型:
    这种类型的程序执行时有很大部分时间是在I/O的消耗上,所以会导致cpu空闲时间较多。此时根据cpu核数计算线程数量就不合算了。

最佳线程数 = CPU核心数 * (1/CPU利用率) = CPU核心数 * (1 + (I/O耗时/CPU耗时))

cpu使用率获取

APM(Application Performance Manager)工具可以帮我们找到准确的数据

  1. SkyWalking
  2. CAT
  3. zipkin

文章字数:146,阅读全文大约需要1分钟

  1. 先进先出算法FIFO
    最先存入缓存的数据将最先被淘汰
  2. 最不经常使用算法LFU
    淘汰使用次数最少的数据,一般实现是对每个数据进行计数,每使用一次就进行计算一次,淘汰计数次数最少的3.
  3. 最近最少使用算法LRU
    最近不使用的数据最先被淘汰,一般实现是通过链表,将最新访问、新插入的元素移到链表头部,淘汰链表最后一个元素

文章字数:228,阅读全文大约需要1分钟

sql的索引有B+树和Hash结构两种,聚集索引非聚集索引都是采用B+树索引。

聚集索引

定义:数据行的物理顺序与该索引列值的逻辑顺序相同,一个表只能有一个聚集索引。即代表物理行位置的索引。

不创建索引,系统会自动创建隐含列作为表的聚集索引。

SQL Server默认主键就是聚集索引,也可以指定非聚集索引
创建完表后再指定聚集索引
create clustered index clustered_index on table_name(colum_name)

MySql主键就是聚集索引,直接设置主键就行了
alter table table_name add primary key(colum_name)

非聚集索引

定义:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。

非聚集索引也可以作为主键,视数据库。


文章字数:605,阅读全文大约需要2分钟

正常流程

  1. 前端请求数据
  2. 先从缓存找,找到直接返回结果
  3. 取不到从数据库中取
  4. 数据库取到更新到缓存再返回结果
  5. 数据库也没有,返回空

缓存穿透

描述
用户不断发送内存和数据库中都没有的数据。导致数据库压力过大

解决

  1. 基础规则校验,如id > 0等屏蔽必然不存在的数据请求
  2. 布隆过滤器,排除必然不存在的数据
  3. 缓存和数据库都没有的数据,查出来之后可以将值设为空保存在内存中,设置有效期。

缓存击穿

描述
并发量大的时候缓存失效,导致请求全部压到了数据库上。

解决

  1. 热点数据永不过期
  2. 峰值前预热
  3. 读数据库加锁,只让一个线程读取数据库。获取锁失败的线程自旋,等待若干时间后再从内存中获取

缓存雪崩

描述
缓存中的数据大批量过期,导致数据查询量过大。缓存击穿是单个数据过期,缓存雪崩是多个数据过期

解决

  1. 缓存数据过期时间随机设置,防止同一时间设置的缓存又同时失效
  2. 分布式缓存可以将热点数据均匀分布在不同缓存数据库中
  3. 热点数据永不过期
  4. 后端进行服务降级和限流

文章字数:331,阅读全文大约需要1分钟

在jpa的基础上增加自己的实现 原文

核心代码

  1. 创建一个中间层Dao,继承jpa的两个接口,并用注解注释

    1
    2
    @NoRepositoryBean
    public interface BaseDao<T,ID extends Serializable> extends JpaRepository<T,ID>,JpaSpecificationExecutor<T>
  2. 创建上面的dao+impl的实现类,拓展功能。

  3. 其它dao直接继承与BeseDao

@NoRepositoryBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package org.springframework.data.repository;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to exclude repository interfaces from being picked up and thus in consequence getting an instance being
* created.
* <p/>
* This will typically be used when providing an extended base interface for all repositories in combination with a
* custom repository base class to implement methods declared in that intermediate interface. In this case you typically
* derive your concrete repository interfaces from the intermediate one but don't want to create a Spring bean for the
* intermediate interface.
*
* @author Oliver Gierke
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface NoRepositoryBean {

}

翻译

1
2
当为所有存储库提供扩展的基础接口,并结合自定义存储库基类以实现在该中间接口中声明的方法时,通常会使用此方法。 
在这种情况下,您通常会从中间接口派生出具体的存储库接口,但又不想为中间接口创建Spring bean。

配置开启功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

package com.data.jpa.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
/**
* 通过注解配置factory-class
* @author xiaowen
* @date 2016年5月30日
* @ version 1.0
*/
@Configuration
@EnableJpaRepositories(basePackages = "com.data.jpa**.dao",
repositoryFactoryBeanClass = CustomRepositoryFactoryBean.class)
@EnableSpringDataWebSupport
public class JpaDataConfig {

}

这个注解是为基类仓库拓展准备的


文章字数:92,阅读全文大约需要1分钟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

public class MyClassLoader extends ClassLoader {
//指定路径
private String path ;


public MyClassLoader(String classPath){
path=classPath;
}

/**
* 重写findClass方法
* @param name 是我们这个类的全路径
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class log = null;
// 获取该class文件字节码数组
byte[] classData = getData();

if (classData != null) {
// 将class的字节码数组转换成Class类的实例
log = defineClass(name, classData, 0, classData.length);
}
return log;
}

/**
* 将class文件转化为字节码数组
* @return
*/
private byte[] getData() {

File file = new File(path);
if (file.exists()){
FileInputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(file);
out = new ByteArrayOutputStream();

byte[] buffer = new byte[1024];
int size = 0;
while ((size = in.read(buffer)) != -1) {
out.write(buffer, 0, size);
}

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
} catch (IOException e) {

e.printStackTrace();
}
}
return out.toByteArray();
}else{
return null;
}
}
}