概述
- 使用锁是为了解决安全问题
- 使用并发主要是让线程既安全,又效率
- 安全问题主要是由于内存的是否被安全使用了
- 效率问题主要考虑的是线程是否被cpu合理调用了
经测试,当并发执行累加操作不超过百万次时,并行速度会比串行化执行累加操作要慢。主要原因是因为并行会导致更多的线程的上下文切换 ```java public class TestThread { static final long count =1000010000; public static void main(String[] args) throws InterruptedException {
concurrency(); //多线程执行serial();//单线程执行
}
static void concurrency() throws InterruptedException {
long start = System.currentTimeMillis();Thread thread = new Thread(()->{int a =0;for(long i=0;i<count;i++){a+=5;}});thread.start();int b =0;for(long i=0;i<count;i++){b-=5;}long end =System.currentTimeMillis();thread.join();//确保thread线程执行完毕System.out.println("多线程使用时间:"+(end-start));
}
//单线程执行 static void serial(){
long start = System.currentTimeMillis();int a =0;for(long i=0;i<count;i++){a+=5;}int b =0;for(long i=0;i<count;i++){b-=5;}long end =System.currentTimeMillis();System.out.println("单线程使用时间:"+(end-start));
}
}
- 减少上下文切换的方式- 无锁并发编程- CAS算法- 使用最少线程,通过使用线程池控制线程的数量- 协程:比线程还要小调用cpu的单位<a name="L1O3r"></a>## Volatile- volatile是jdk提供一个关键字,可作为轻量级的锁,不会引起上下文件线程的切换- volatile的作用- 保证线程的可见性- 防止指令重排- volatile可以一定程度解决并发问题,但是遇到原子性导致的并发就无法解决- 想要保证线程安全需要解决以下三个问题- 原子性- 可见性- 有序性- volatile只能修饰成员变量,不能修饰局部变量<a name="YRhuV"></a>## 线程可见性问题- 可见性代码问题```javapublic static class RecoderExample {volatile int a=0;//实现了获取a的值的方法public void get(){int local_value = a;while (local_value < 15) {if (local_value != a) {System.out.println("获取a的值 : " + a);local_value = a;}}}//修改a的值public void change(){while (a < 15) {System.out.println("新增之后的a的值 " + (a + 1));a++;try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {RecoderExample recoderExample = new RecoderExample();//创建一个获取值的线程Thread t1 = new Thread(()->{recoderExample.get();});//创建一个线程修改值Thread t2 = new Thread(()->{recoderExample.change();});t1.start();t2.start();}
- 为什么会有可见性的问题?
- Java共享内存模型JMM的机制
- java中所有的变量是存储在主内存中的
- 每个线程要去修改主内存中的变量的时候,是将主内存中的变量复制一份到线程的本地内存中进行修改
- 然后线程修改变量成功之后,将线程的本地的内存的变量再刷新到主内存中
- 在多线程编程的时候,可能有几个线程同时将一个变量值复制到本地内存中然后进行修改,这样就导致了并发问题
- 所以线程中的变量默认情况下是不可见的
- Java共享内存模型JMM的机制
在使用了volatile修饰变量之后,当一个线程的本地变量被修改了,其他的变量都是可以见的
线程操作的原子性
所谓的原子性是不可分割的操作
- volatile不可以保证操作的原子性,synchronized可以保证
- volatile用于信号灯的操作,即一个线程改,多个线程查询等操作
可以使用JUC中提供的原子类解决原子性的问题
public class TestVolatile002 {int count =0;AtomicInteger atomicInteger = new AtomicInteger(0);void m() {for (int i = 0; i < 10000; i++) {count++;atomicInteger.incrementAndGet();}}public static void main(String[] args) {TestVolatile002 t = new TestVolatile002();List<Thread> threads = new ArrayList<>();//创建10个线程for (int i = 0; i < 10; i++) {threads.add( new Thread(t::m,"thread"+i));}//启动10个线程threads.forEach((o)->o.start());//当前主线程等待所有的线程完成threads.forEach((o)->{try {o.join();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}});System.out.println(t.count);System.out.println("原子操作的值:"+t.atomicInteger.get());}}
线程操作的顺序性
java的编译器为了提高程序运行的效率,在编译时候会将源码的顺序进行重排,以便提高程序运行的效率
int x = 10; int x = 10;int y = 9; x = x+10x = x+10; int y=9;
在单线程的情况下问题不大,但是在多线程的情况有可能导致并发的问题
public class TestVolatile003 {public static int a = 0,b=0;public static int i = 0,j=0;public static void main(String[] args) throws InterruptedException {int cnt=0;while(true){a = 0;b=0;i=0;j=0;Thread t1 = new Thread(()->{a=1;i=b;});Thread t2 = new Thread(()->{b=1;j=a;});t1.start();t2.start();t1.join();t2.join();cnt++;System.out.println("第"+cnt+"次执行,i="+i+",j="+j);if(i==0&& j==0){break;}}}}
在单例模式(Double Check Lock)解决指令重排的问题
public class MySingleton {static volatile MySingleton instance;List list;private MySingleton(){list = new ArrayList();//这个代码可能会在赋值之后执行}public int getNum(){return list.size();}public static MySingleton getInstance(){if(instance == null){//由于指令重排instance已经不为空,但是构造方法没有被调用synchronized(MySingleton.class){if(instance==null){instance = new MySingleton();}}}return instance;}}
new的正常顺序
- 1内存为对象分配空间
- 2调用构造方法
- 3将结果返回的引用
由于指令重排之后以上步骤变成一下情况
- 1内存为对象分配空间
- 3将结果返回的引用
- 2调用构造方法
volatile 和 synchronized
1、volatile只能修饰实例变量和类变量,而synchronized可以修饰方法,以及代码块。
2、volatile保证数据的可见性,但是不保证原子性; 而synchronized是一种排他(互斥)的机制,既保证可见性,又保证原子性。
3、volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
4、volatile可以看做是轻量版的synchronized,volatile不保证原子性,但是如果是对一个共享变量进行多个线程的赋值,而没有其他的操作,那么就可以用volatile来代替synchronized,因为赋值本身是有原子性的,而volatile又保证了可见性,所以就可以保证线程安全了。CAS算法
传统的锁会在加锁,或是释放的时候导致过多的上下文切换
CAS算法:compareAndSwap
public final int incrementAndGet() {for (;;) {int current = get();//获取当前的变量的值int next = current + 1; //预判操作if (compareAndSet(current, next))//判断预操作是否成功return next; //成功则返回//不成功则继续}}
通过以上的算法,线程就不会轻易的释放cpu资源,从而减少了上下文的切换
- CAS算法主要是通过Unsafe类实现
在CAS的基础上出现快速解决并发问题的各种工具包
JUC(原子包),提供各种类型原子操作类
AtomicBoolean:原子更新布尔类型。AtomicInteger:原子更新整型。AtomicLong:原子更新长整型。AtomicInteger
QAS(各种支持并发的集合)
- ArrayList并不是线程安全的,Vector是线程安全的,但是Vector使用synchronized
- CopyOnWriteArrayList是线程安全的,且效率比Vector高
- Hashmap并不是线程安全的,
- ConcurrentHashMap是线程安全
- 主要使用了分段锁进行实现
- ConcurrentLinkedQueue:线程安全的队列
阻塞队列的使用
public class TestBlockQueue {static AtomicInteger cnt = new AtomicInteger(0);public static void main(String[] args) {ArrayBlockingQueue<String> queue = new ArrayBlockingQueue(5);//多个消费则List<Thread> products = new ArrayList<>();for(int i=0;i<3;i++){products.add(new Thread(()->{String name = Thread.currentThread().getName();for(int j=0;j<5;j++){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}try {// System.out.println(name+"做的食品"+(cnt.get()+1));queue.put(name+"做的食品"+cnt.incrementAndGet()); //put是阻塞的方法} catch (InterruptedException e) {e.printStackTrace();}}}));}//启动线程products.forEach(thread -> {thread.start();});//创建消费者new Thread(()->{for(int i=0;i<5;i++){String food= null;//出队列try {food = queue.take();//获取队列中元素} catch (InterruptedException e) {e.printStackTrace();}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("消费1食品:"+food);}}).start();new Thread(()->{for(int i=0;i<5;i++){String food= null;//出队列try {food = queue.take();//获取队列中元素} catch (InterruptedException e) {e.printStackTrace();}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("消费2食品:"+food);}}).start();new Thread(()->{for(int i=0;i<5;i++){String food= null;//出队列try {food = queue.take();//获取队列中元素} catch (InterruptedException e) {e.printStackTrace();}try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("消费3食品:"+food);}}).start();}}
- ArrayList并不是线程安全的,Vector是线程安全的,但是Vector使用synchronized
