java-多线程与锁
概述:
多线程的使用,锁,线程池,更多的锁。
1:多线程的使用。
1.1:使用多线程需要保证:原子性,可见性,有序性。
1.2:启动多线程的方法:(不做 多余赘述,写两种常见的方式)
继承Thread类,重写run方法。
实现Runnable接口,重写run方法。
1.3:这里引出一个方法 join()。(一个执行先后的方法)
假设开启了两个线程T1,T2,如果同时 start 之后T1,T2的执行顺序比较随机,如果在T2的 run 方法中写上T1.join(),那么就是说T2在T1执行完之后执行。
2:高并发保证线程安全:锁。
2.1:分类:synchronized(内置锁),lock。
2.2:区别:lock需要unlock方法手动解锁,synchronized在执行完锁的部分或者异常就是释放锁。
2.3:对于synchronized的使用。(解决的是高并发情况多线程公用一个变量引起的线程安全问题)
2.3.1:同步方法。
public class Test { /** * 同步方法 * 说明:锁的对象:这个类,即Test类,也叫this锁。 */ public synchronized void syncTest() { // TODO Auto-generated method stub } }
2.3.2:同步代码块。
public class Test { private Object object = new Object(); /** * 同步代码块 * 说明:锁的对象: * 这个类,即Test类, * 也可以的一对象。 * 以下两种皆可 */ public void syncTest() { //锁的类 synchronized (this) { } //锁的对象 synchronized (object) { } } }
2.3.3:静态同步代码。
public class Test { /** * 同步代码块 * 说明:锁的对象: * 这个类(即Test类)的字节码文件, */ public static void syncTest() { synchronized (Test.class) { } } }
2.4:延伸的概念。
2.4.1:修饰词 threadLocal(解决的是多线程之间公用的变量隔离出来使用,不影响其他线程)
概述:将共享变量私有化。具体使用网上有很多的帖子。
2.4.2:修饰词 volatile (修改变量,快速写入主内存)
解释说明:首先了解共享内存模型(jmm),即一个变量在内存中存于主内存,还有一块虚拟的私有内存(虚拟的,不存在的)。
内存结构是我们常说的 jvm。
普通变量:修改的是自己私有内存的变量,然后再同步到主内存中。
被volatile 修饰的变量:1:会快速的被写入主内存,然后其他线程使用时候直接从主内存中拿取。我的理解是不是volatile 修饰的变量
一直操作的是主内存中的呢?(没有看过源码进行深入的研究,只是一个疑问,不过不太影响对这个的理解)
2:volatile 可以禁止指令重排序(指令重排是cpu为了提高程序的执行性能的一种方法)。
对比synchronized:
1:优点,volatile 不会造成线程阻塞,性能方面要由于锁。volatile 可以禁止指令重排序。
2:缺点,volatile 是可以保证可见性,有序性,但是不能保证原子性,所以不能取代synchronized。
总结:volatile 是轻量级的synchronized,如果有对变量进行操作等场景使用synchronized。
使用场景:
单例模式是个典型的应用。
说明:变量需要volition修饰,变量的构造赋值部分是用synchronized修饰的,目的是T1进入了synchronized部分之后,
如果对象还未完全构建成功,那么T2进入,发现判断的对象不为null,这样会存在问题。
2.4.3:wait(),notify(),notifyAll()应用。(wait让线程进入等待状态,notify唤醒当前线程,notifyAll唤醒所以线程)
假设场景 :假如对一个共享变量,T1进行set值,T2进行get值。我们要保证:T2 get值时候,变量里面要有值的,T1 set值时候变量是无指的。
T1与T2中使用wait(),notify()组合。
对比 sleep() :sleep等待多长时间继续执行,不会释放锁。wait会释放锁。
3:线程池。
3.1:为何使用线程池:
频繁的以1的描述方式开启线程,关闭线程,很消耗性能,所以引入线程池。
3.2:线程池种类:
newCachedThreadPool:缓存线程池
newFixedThreadPool:定长线程池
3.3:spring项目中的应用。(自定义一个线程池)
@Configuration @EnableAsync //开启异步调用 public class Test { /** * 详细的参数解释可参见,里面有相应的线程池参数配置规则 * https://www.cnblogs.com/waytobestcoder/p/5323130.html * @return */ @Bean("pool1") public Executor threadPool1() { ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); //线程核心数目 threadPoolTaskExecutor.setCorePoolSize(10); //核心线程,超时是否关闭,一般缓存线程池默认60S,其他线程池设置的是0S,我们默认这个也是60S threadPoolTaskExecutor.setAllowCoreThreadTimeOut(true); //最大线程数 threadPoolTaskExecutor.setMaxPoolSize(10); //配置队列大小 threadPoolTaskExecutor.setQueueCapacity(50); //配置线程池前缀,相当于@Bean("pool1")已经指定了使用的线程池名称 // threadPoolTaskExecutor.setThreadNamePrefix("pool1"); //配置拒绝策略 threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); //数据初始化 threadPoolTaskExecutor.initialize(); return threadPoolTaskExecutor; } }
@Async("pool1") public void test(){ }
如果调用了test(),就是使用了线程池pool1。
4:更多的锁
4.1:乐观锁,悲观锁,重入锁,自旋锁,分布式锁,CAS无锁
CAS概述:三个值,初始值,期望值,新值。如果初始值=期望值,那么初始值=新值,否则代表有其他线程已经改了,再重新取值对比。