你需要了解的多线程知识(JAVA ) 复习
Volatile 关键字
Volatile 是java虚拟机提供的轻量级同步机制(保证可见性,不保证原子性,禁止指令重排)
可见性之前需要了解
JVM(java虚拟机)
JMM(java内存模型) javamemory model 不真实存在描述的一种规则规范 定义了程序中各个变量(包括实例字段静态字段和构成数组对象的元素)的访问方式
JMM的同步的规定
1 线程解锁前必须把共享变量的值刷新回主内存
2 线程加锁前必须读取主内存的最新值到自己工作内存
3 加解锁是同一把锁
可见性:举个栗子 比如你new了一个student对象 要设置里面的age(默认为15) 的值 new的对象是在主内存中(我们暂时理解主内存是一个大的空间是一种共享区域内存所有线程可以访问),此时有两个线程需要更改student 中age的值 线程一设置的时候会从主内存copy一份student 并且更改age的值为18 更改完成后写到主内存中(另一个线程无法读这个线程里面的值)此时主内存的值被改变此时jmm通知另个线程age已经被更改为18 拎一个线程里面的age也变为了18
不保证原子性: 比如1000线程对一个变量进行++操作 如果是原子性的话会输出1000但是实际值一般不会是1000;(原因是 在一个线程取到值加完后要写的主内存的瞬间被挂起其他线程写入 此时还没有被通知 相当于同样的一个值被覆盖了....自行理解)如何解决 最简单的加上syn关键字 或者用Atomicinteger、AtomicLong .....等一些带原子类型的包装类详情百度
禁止指令重排: 重排的意思是例如x=3 y=4 x++ y++ 这四句可能翻译后的字节码为1324 (4个语句顺序) 也可能为1234 指令重排后的就为1324 假如多线程的话会导致数据不一致
(底层如何实现屏障 通过插入内存屏障禁止在内存屏障天后的指令执行重排优化 写之后加入store 读之后加入load)
单例模式多线程问题(懒汉式线程不安全)
懒汉式多线程获得的不是同一个对象导致不是单例了
单例模式多线程下无效
可以用DCL(双端检锁)但是不一定线程安全,原因是有指令重排的存在加volatile可以禁止指令重排(加入双端检锁也可能发生不安全 因为在你new完的时候 可能切换线程此时实例还是为null 另一个也new 了一个 这样不就两个实例了吗)还有一个重排问题就是可能导致实例为null 需要在instance 前加volatile
安全的代码
public class VolatileMain { private static volatile VolatileMain instance=null;//不加这个volatile可能导致重排对象为null private VolatileMain() { System.out.println(Thread.currentThread().getName()+"构造方法"); } //DCL 双端检测-可以用双端检测加锁前后都进行判断 锁机制 会好很多 public static VolatileMain getInstance() //多个线程就不是单例模式想要单例需要方法加上synchronize关键字 { if (instance==null) { synchronized (VolatileMain.class) { if (instance==null) { instance=new VolatileMain(); } } } return instance; }
CAS知道吗如何实现?(cpu并发原语)
CAS 是判断内存的某个位置是否为预期值如果是这个值就改为新址这个过程是原子性的cas并发原语体现在java语言中就是sun.unsafe 类中的各个方法调用unsafe方法的cas方法这是一种完全依赖于硬件的功能通过它实现了原子操作 cas是系统原语 由若干条指令组层 实现某一功能
CAS比较并交换含义:比如一个线程 需要更改主物理内存的值比如主物理内存age=5; 线程1需要copy一份并修改为指定的值假如是200;此时修改完后需要写进主物理内存,写的时候需要比较原来的期望值是5 ,现在主物理内存如果是5的话就更新不是的话就不更新AutomicInteger.compareAndSet(期望值,要更改的值)
Unsafe类一些代码
比如调用自动增加的方法 getAndIncrement(){ return unsafe.getAndAddInt(本对象,地址,1) }
都会来比较内存地址的值和原来的数值如果一样就更新 有一个dowhile会一直比较
Unsafe类+cas思想(自旋锁--死循环)
CAS缺点:循环时间长 给CPU带来很大开销 引出ABA问题 只能保证共享一个变量
ABA问题
其实这个问题我想到了 提前取出内存中某时刻的数据并在当下时刻比较并替换 这个时间差会导致数据的变化。 假如 一个线程把age初始值为1 两个线程old值为一时才更改 线程一先运行然后立即转到线程2 线程2把age值改成2 然后又马上改成1 此时切换到线程一 线程一认为age 值没有修改 并且把age修改为3
原子更新引用 的概念
(原子引用)AtomicReference<User> <> 里面是泛型 User 就一个get和set 两个 成员变量不贴了
public class AtomicRederenceMy { public static void main(String[] args) { User u=new User(1,"lsi"); User u1=new User(2,"hah "); AtomicReference<User> atomicReference=new AtomicReference<>(); atomicReference.set(u); System.out.println(atomicReference.compareAndSet(u,u1)); System.out.println(atomicReference.compareAndSet(u,u1)); } }
解决办法:
如何规避ABA问题?
AtomicStampedReference 带时间戳的原子引用
public static void main(String[] args) { Integer myinte=new Integer(100); AtomicStampedReference<Integer> atomicStampedReference=new AtomicStampedReference<Integer>(myinte,0); int stamp=atomicStampedReference.getStamp(); new Thread(new Runnable() { @Override public void run() { atomicStampedReference.compareAndSet(myinte,new Integer(10),0,stamp+1); atomicStampedReference.compareAndSet(10,new Integer(100),atomicStampedReference.getStamp()-1,stamp+1); } }).start(); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { } boolean b=atomicStampedReference.compareAndSet(myinte,new Integer(10),0,stamp+1); System.out.println(atomicStampedReference.getReference()+"---"+b); } }).start(); }
集合类并发解决
开启多个线程对list进行修改添加操作容易造成 java.util.ConcurrentModificationException
添加的时候集合类都容易报这个异常
解决方案一:使用Vector
解决方案二:使用Collections.synchronizedlist()返回一个加锁的集合类(底层sy关键字) 不光有list map也有
解决方案三: 使用List<String> list=new CopyOnWriteArrayList<>() 里面也有set什么的
CopyOnWriteArrayList 如何实现
意思是写时复制 向一个容器添加元素的时候不直接向当前容器Object[]添加 而是对当前容器进行复制 让后往新的容器添加元素添加完后需要把原来容器引用指向新的容器这样的好处是可以对copyOnwrite容器进行并发的读而不需要加锁因为当前容器不会添加任何元素利用了读写分离的思想
Hashset 线程不安全解决办法
Set<String> set=new CopyOnWriteArraySet<>() 底层还是CopyOnWriteArraylist实现
HashSet的底层怎么实现?Hashmap 为什么map是键值对两个参数但是set是一个参数?
底层是hashmap hashmap.add 方法调用的是set的add方法只加入key value是一个常量值Object对象。
解决办法1 ConcurrentHashMap
解决办法2 Collections.synchronizedMap
java 传参还是传引用问题
这个网上看到好多答案有的说直接传参 有的说对象传引用 普通数值类型传参 我还是比较接受第二种的 一个代码
public class ChuanCan { private int k=0; public ChuanCan(int k) { this.k = k; } public int getK() { return k; } public static void method3(ChuanCan i) { i.k=100; } public static void method1(int i) { i++; } public static void method2(String i) { i= i+"胡"; } public static void main(String[] args) { int i=5;//folat同理 String s="hahh"; // method2(s);//调用方法还是hahh 因为string储存在常量中无法修改只能再开辟一份空间 method1(i); System.out.println(s);//输出5 相当于作用域问题输出的是main中的5 方法是他的拷贝 ChuanCan cc= new ChuanCan(80); method3(cc);//对象传的是引用 System.out.println(cc.k); } }
对于new 出来的Stirng 还是传参不是引用 但是对stringbuffer和bulider就是引用了
Java的锁
公平锁:很公平你一次我一次
非公平锁:可能导致鸡饿,但是吞吐量大快一点吧
可重入锁(递归锁):同一线程外层获得锁内层递归仍然能获得锁(例如一个同步方法可以访问另一个同步方法)。而且好像是获得锁之后内层再尝试获得锁还是原来的锁 详情百度
代码
可重入锁小代码
public class Suo { public static synchronized void sendE() { System.out.println(Thread.currentThread().getId()); } public static synchronized void sendQ() { System.out.println(Thread.currentThread().getId()); } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { sendE(); sendQ(); } }).start(); } }
自旋锁
尝试获取锁的线程不会立即阻塞而是采用循环的方式去尝试获取锁减少线程切换消耗,缺点是消耗CPU
手写自旋锁小例子
public class SpinLockDemo { AtomicReference<Thread> atomicReference=new AtomicReference<>();//引用类型初始值WieNULL public void mylock() { Thread thread=Thread.currentThread(); System.out.println(Thread.currentThread().getName()+"\t com in "); while (!atomicReference.compareAndSet(null,thread))//进去为true 因此取反 { } } public void myunlock() { Thread thread=Thread.currentThread();//这个实例是当前Thread 的引用。 atomicReference.compareAndSet(thread,null); System.out.println(Thread.currentThread().getName()+" invoked "); } public static void main(String[] args) { SpinLockDemo spinLockDemo=new SpinLockDemo(); new Thread(new Runnable() { @Override public void run() { spinLockDemo.mylock(); try { Thread.sleep(5000); System.out.println("5秒OK"); } catch (InterruptedException e) { e.printStackTrace(); } spinLockDemo.myunlock(); } }).start(); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } spinLockDemo.mylock(); spinLockDemo.myunlock(); } }).start(); } }
上面的小例子引出 Final值能否改变的问题
Final 修饰的值不能变 其实是地址不变 for循环里面也可以循环赋值例如final StringBuffer sbu = new StringBuffer(“abc”); sub 可以再添加值
独占锁(写锁)/共享锁(读锁)
可以使用ReenTrantReadWriteLock 里面有writelLock 或者ReadLock
小例子
import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 多个线程可以读一个资源 * 但是无法共享写一个资源 * 总结 读读可以共存 * 读写不可以共存 * 写写不能共存 * * 写(原子性) * * 看输出写的时候不能输出正在读 要先写完在读 */ public class ReadWriteLOCK { public static void main(String[] args) { Mycache mycache=new Mycache(); for (int i = 0; i <5 ; i++) { final int tempint=i; new Thread(()->{ try { mycache.put(tempint+"","tempi"+tempint); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } for (int i = 0; i <5 ; i++) { final int tempint=i; System.out.println(tempint+"---"); new Thread(()->{ try { mycache.get(tempint+""); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } } } class Mycache{ private volatile Map<String,Object> map=new HashMap<>(); private ReentrantReadWriteLock lock =new ReentrantReadWriteLock(); public void put(String key,Object value) throws InterruptedException { lock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName()+"正在写入"+value); map.put(key,value); Thread.sleep(300); System.out.println(Thread.currentThread().getName()+"写入完成"); } catch (Exception e) { e.printStackTrace(); } finally { lock.writeLock().unlock(); } } public void get(String key) throws InterruptedException { lock.readLock().lock(); try { Object o=map.get(key); Thread.sleep(300); System.out.println(Thread.currentThread().getName()+"读"+o); //System.out.println(Thread.currentThread().getName()+"正在读"); } catch (Exception e) { e.printStackTrace(); } finally { lock.readLock().unlock(); } } }
CountDownLatch/CyclicBarrier/Semaphore使用过吗?
前两个代码我直接贴过来了
import java.util.Objects; import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch =new CountDownLatch(6); for (int i = 1; i <=6 ; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"子线程"); countDownLatch.countDown(); }, Objects.requireNonNull(CountryEnum.forEach_enum(i)).getRetMessage()).start(); } countDownLatch.await(); System.out.println("主线程完毕"); } } public enum CountryEnum { ONE(1,"齐国"),TWO(2,"齐国"),THREE(3,"楚国"),FOUR(4,"燕国"),FIVE(5,"赵国"),SIX(6,"韩国"); CountryEnum(int retCode, String retMessage) { this.retCode = retCode; this.retMessage = retMessage; } private int retCode; private String retMessage; private String retMessage1; public int getRetCode() { return retCode; } public String getRetMessage() { return retMessage; } public static CountryEnum forEach_enum(int index) { CountryEnum[] myarr=CountryEnum.values(); for (CountryEnum element: myarr) { if (index==element.getRetCode()) { return element;//返回枚举 } } return null; } } CyclicBarrier(循环屏障) 跟CountDownLatch相反 public class CyclicBarrierDemo { public static void main(String[] args) throws InterruptedException { CyclicBarrier cyclicBarrier=new CyclicBarrier(6,()->{ System.out.println("最后执行的方法"); }); for (int i = 0; i <6 ; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getId()+"--"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }
里面用了一点 enum 这个我以前没用过也算学一下
Semaphore 使用过吗?这是一个六两车抢占三个停车位的例子(咱们学的信号量)
public class SemaphoreDemo { public static void main(String[] args) { Semaphore semaphore=new Semaphore(3);//模拟三个停车位 空闲区,参数而可以是公平非公pign锁 for (int i = 0; i <6 ; i++)//模拟6部车抢三个请车位 { new Thread(()->{ try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"抢到了停车位"); Thread.sleep(3000); System.out.println(Thread.currentThread().getName()+"离开停车位"); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } }).start(); } } }