并发编程专题
一、概述
串行:一个线程在处理操作;
并行:多个线程在处理操作;
并发编程:在多线程环境下,应用程序的执行;
并发编程的目的:同分运用到资源,提供程序的效率
什么情况下用到并发编程:
1.在线程阻塞时,导致应用程序停止;
2.处理任务时间过长,可以创建子任务,来进行分段处理;
3.间断任务执行;
二、并发编程中待解决的问题
1、并发编程中频繁上下文切换的问题
即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制;
时间片是CPU分配给各个线程的时间,因为时间非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是十几毫秒;
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下一次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换;
这就像我们同时读两本书,当我们读一本英文的技术书时,发现某个单词不认识,于是打开中英文字典,但是在的放下英文技术书之前,大脑必须先记住这本书读到了多少页的第多少行,等查完单词之后,能够继续读这本书。这样的切换是会影响读书效率的,同样上下文切换回影响多线程的执行速度;
2、如何减少上下文性能开销
①无锁并发编程;
②CAS;
③使用最少线程数量;
④协程:在单线程环境下进行多任务的调度,可以在多任务之间进行任务切换;
3、并发编程中死锁问题
锁是一个非常有用的工具,运用场景非常多,因为它使用起来非常简单,而且易于理解。但同时它也会带来一些困扰,那就是可能会引起死锁,一旦产生死锁,就会造成系统功能不可用;
模拟死锁代码实现:
package com.zn; public class DeadLockDemo { //线程 private static final Object obj_A =new Object(); private static final Object obj_B=new Object(); public static void main(String[] args){ //A线程 new Thread(new Runnable() { @Override public void run() { synchronized (obj_A){ System.out.println("使用obj_A线程!"); //延迟时间 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //抢占B线程 synchronized (obj_B){ System.out.println("抢占B线程"); } } } }).start(); //B线程 new Thread(new Runnable() { @Override public void run() { synchronized (obj_B){ System.out.println("使用obj_B线程!"); //延迟时间 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //抢占A线程 synchronized (obj_A){ System.out.println("抢占A线程"); } } } }).start(); } }
控制台效果:
线程A和线程B互相等待对象释放锁;
一旦出现死锁,业务是可感知的,因为不能继续提供服务了,可以通过jstack工具查看到底哪个线程出现了问题
jstack命令查看:
jstack 18208
死锁问题:
4、避免死锁的方法
①避免一个线程同时获取多个锁;
②避免一个线程在锁内同时占用多个资源,尽量保证每一个锁只占用一个资源;
③尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制;
④破坏请求和保持条件:在申请资源时,一次性将资源都申请到;
⑤破坏不可占用条件:抢占资源如果不满足,那就释放所有资源,以后如果需要则再次申请即可;
⑥破坏循环等待条件;
5、线程安全问题
多个线程同时操作同一个资源,可能会造成资源数据不安全问题
代码实现:
package com.zn; import java.util.concurrent.CountDownLatch; public class UnsafeThread { //资源 private static int num=0; //计算线程数量 private static CountDownLatch countDownLatch=new CountDownLatch(10); //对资源进行操作 public static void inCreate(){ num++; } public static void main(String[] args) throws InterruptedException { for (int i=0;i<10;i++){ new Thread(new Runnable() { @Override public void run() { for (int j=0;j<100;j++){ inCreate(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //每个线程执行完毕,让计数-1 countDownLatch.countDown(); } } }).start(); } //等待计算器为0或者小于执行await下面的代码 countDownLatch.await(); System.out.println(num); } }
控制台效果:
6、解决线程不安全问题
①同步方法
代码实现:
package com.zn; import java.util.concurrent.CountDownLatch; public class UnsafeThread { //资源 private static int num = 0; //计算线程数量 private static CountDownLatch countDownLatch = new CountDownLatch(10); //对资源进行操作 public static synchronized void inCreate() { num++; } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 100; j++) { inCreate(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } //每一个线程执行完毕,让计数-1 countDownLatch.countDown(); }).start(); } //等待计数器为0或者小于0执行await下面代码 countDownLatch.await(); //获取到当前计数器中的线程数量 /*while (true){ if(countDownLatch.getCount()<=5){ System.out.println(num); break; } }*/ System.out.println(num); } }
控制台效果:
使用synchronized修改的方法叫做同步方法,保证该线程执行该方法的时候,其他线程只能等着;
同步锁:1.非static方法,同步锁是this;
2.static方法,使用当前方法所在的字节码对象;
注意:synchronized不能修饰run方法,修饰之后,一个线程就执行了所有的功能,线程出现串行,相当于单线程;
②同步代码块
代码实现:
package com.zn; import java.util.concurrent.CountDownLatch; public class UnsafeThread { //资源 private static int num = 0; //计算线程数量 private static CountDownLatch countDownLatch = new CountDownLatch(10); //对资源进行操作 public static void inCreate(){ synchronized (UnsafeThread.class){ num++; } } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 100; j++) { inCreate(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } //每一个线程执行完毕,让计数-1 countDownLatch.countDown(); }).start(); } //等待计数器为0或者小于0执行await下面代码 countDownLatch.await(); //获取到当前计数器中的线程数量 /*while (true){ if(countDownLatch.getCount()<=5){ System.out.println(num); break; } }*/ System.out.println(num); } }
控制台效果:
synchronized(同步锁){
}
对象的同步锁只有一个概念,可以想象在对象上标记一个锁;
java程序运行可以使用任何对象作为同步监听对象,但是一般我们将当前i并发访问的共同资源(多个线程同步共享的资源对象)所谓同步监听对象;
注意:在任何时候,只允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他线程只能在外面等;
③ReentrantLock(锁机制)
锁是一种通过多个线程控制对共享资源的访问工具。通常,一个锁提供对共享资源的独占访问:在一个时间只有一个线程可以获取锁和所有访问共享资源,需要先获得锁;
代码实现:
package com.zn; import java.util.concurrent.CountDownLatch; import java.util.concurrent.locks.ReentrantLock; public class UnsafeThread { //资源 private static int num = 0; //计算线程数量 private static CountDownLatch countDownLatch = new CountDownLatch(10); //对资源进行操作 private static ReentrantLock reentrantLock=new ReentrantLock(); public static void inCreate(){ //上锁 reentrantLock.lock(); num++; //释放搜 reentrantLock.unlock(); } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { new Thread(() -> { for (int j = 0; j < 100; j++) { inCreate(); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } //每一个线程执行完毕,让计数-1 countDownLatch.countDown(); }).start(); } //等待计数器为0或者小于0执行await下面代码 countDownLatch.await(); //获取到当前计数器中的线程数量 /*while (true){ if(countDownLatch.getCount()<=5){ System.out.println(num); break; } }*/ System.out.println(num); } }
控制台效果: