1、J.U.C并发包概述
(1)j.u.c是JDK的核心工具包,是JDK1.5之后,由 Doug Lea(撰写)实现并引入。
整个java.util.concurrent包,按照功能可以大致划分如下:
- juc-locks 锁框架
- juc-atomic 原子类框架
- juc-sync 同步器框架、工具类
- juc-collections 集合框架
- juc-executors 执行器框架
2、并发编程
(1)死锁问题
什么是死锁:
多线程竞争共享资源,导致线程相互等待,程序无法向下执行。
代码演示:
public class DeadLock {
// 定义两个对象 private static Object objA = new Object(); private static Object objB = new Object(); public static void main(String[] args) {
// 线程一 Thread t1 = new Thread(()->{
synchronized (objA){
try {
Thread.sleep(1000); // 线程等待 } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("AAAAAAA"); synchronized (objB){
System.out.println("BBBBBBB"); } } }); // 线程二 Thread t2 = new Thread(()->{
synchronized (objB){
System.out.println("CCCCCCC"); synchronized (objA){
System.out.println("DDDDDDD"); } } }); // 启动线程 t1.start(); t2.start(); } // synchronized锁:一定要等待锁释放,别的线程才可以进行下一步操作 }
讯享网
cmd命令查看线程执行情况:
- jps命令:查看java程序进程id信息
- jstack命令+进程ID:查看指定进程堆栈信息
避免死锁:
- 避免一个线程同时获取多个锁。
- 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
- 尝试使用定时锁,使用lock .tryLock (timeout )来替代使用内部锁机制。
- 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况
mysql锁表语句:
lock锁:

讯享网
行级锁/排他锁 [使用FOR UPDATE]
- 启动事务,BEGIN;
- 锁定某一行 where条件需要主键,SELECT * FROM products WHERE id=1 FOR UPDATE
- 事务提交[或者 ROLLBACK 事务回滚],COMMIT
在启动事务然后锁定某一行后,在事务提交之前或者事务回滚之前该行会一直处于锁定状态,此时只允许当前线程进行读写操作,其他线程只能进行读操作,其他写操作全部被堵塞…
读:可以并发
写:不可并发操作
小结:
1、什么是死锁:
- 多线程竞争共享资源,导致线程相互等待,程序无法向下执行
2、死锁产生的条件:

- A.有多个线程
- B.有多把锁
- C.有同步代码块嵌套
3、如何避免死锁:
- 干掉其死锁产生的条件中一个条件即可
(2)上下文过度切换
介绍上下文切换:
大概意思:CPU来回切换执行进程中的单个线程,切换速度太快了,给人的感觉就是同时在执行。
- 单核处理器也支持多线程执行代码,CPU 通过给每个线程分配CPU 时间片来实现这个机制。
- 时间片:CPU 分配给各个线程的时间,因为时间片非常短,所以CPU 通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms)。
- CPU 通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。
举例介绍:
这就像我们同时读两本书,当我们在读一本英文的技术书时,发现某个单词不认识,于是便打开中英文字典,但是在放下英文技术书之前,大脑必须先记住这本书读到了多少页的第多少行,等查完单词之后,能够继续读这本书。这样的切换是会影响读书效率的,同样上下文切换也会影响多线程的执行速度。
多线程的执行速度:
代码演示:
讯享网public class ConcurrencyTest {
// 定义总次数 private static final long count = ; public static void main(String[] args) throws InterruptedException {
concurrency(); serial(); } // 并发执行函数 private static void concurrency() throws InterruptedException {
// 定义开始时间 long start = System.currentTimeMillis(); // 定义线程 Thread thread = new Thread(new Runnable() {
@Override public void run() {
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--; } // 计算执行完毕的时间 long time = System.currentTimeMillis() - start; thread.join(); // 线程阻塞将继续向下执行 // 打印时间 System.out.println("concurrency :" + time + "ms,b=" + b); } // 单线程执行函数 private 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--; } // 计算时间差,并打印结果 long time = System.currentTimeMillis() - start; System.out.println("serial:" + time + "ms,b=" + b + ",a=" + a); } }
测试结果:
- 当并发执行累加操作不超过百万次时,速度会比单线程执行累加操作要慢。
- 并发执行的速度会比串行慢,是因为线程有创建和上下文切换的开销。
- 数据量较大,多线程优于单线程。
减少上下文切换:
- 无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash 算法取模分段,不同的线程处理不同段的数据。
- CAS算法:Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
- 使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。
- 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/118197.html