博学谷IT学习技术支持
实现多线程
计算机执行程序就跟人类大脑运算是一样的,同一时间只能思考一件事,但是我们发现操作系统能同时执行很多程序,这是怎么办到的?
那就是时间切片,一段时间执行A程序,一段时间执行B程序,由于CPU切换的很快,所以一般我们感觉不到它在切换。
并发和并行的含义:
并发:有一堆任务同时提交,但是同一时间只运行一个。
并行:一堆任务同时提交同时允许。
如果我们的电脑是单CPU,但是提交多任务,那么就是并发模式。
如果我们的电脑是多CPU,提交任务数量没超过,那就就是并行模式。
进程和线程的含义:
进程可以理解为一个程序,每个程序都有一个进程,在任务管理器中可以查看。
线程可以理解成程序中的一个任务,一个程序可以同时提交多个任务。
实现线程有三种方式:
- 重写Thread的Run方法
- 实现Runlable接口然后传递给Thread
- 实现Callable接口,传递给FutureTask,然后再给Thread。
线程休眠有两种方式:
- await
- sleep
线程优先级
因为切换线程是由Cpu来控制,我们程序控制不了,只能调整优先级,提高执行的概率。
守护线程
等所有线程执行完后,它就结束运行。只要有一个线程存活它就存活
线程同步
因为Cpu切换任务充满了随机性,比如卖票案例,总票数100,1号卖货员刚刚卖了1张,还没传给中央系统,Cpu就切换到了2号卖货员,2号也卖了一张,然后传给中央系统,中央系统就剩下99,然后Cpu在切换到2号卖货员,然后2号卖货员不知道中央系统变了,所以它还是传99给它。那么这个时候就出问题对吧。
那这个问题出在哪?
就是1号卖货员的任务还没结束,就切换到2号卖货员了。
那么我们可以通过同步代码块,让每个卖货员任务结束后在切换到下一个就可以解决这个问题。
除了同步代码块外,我们还能通过Lock锁来实现这个功能
死锁:
锁的嵌套有可能会引起死锁的现象,因为1号线程拿到了1号锁,2号线程拿到了2号锁,如果这个时候锁嵌套了,1号线程又去拿2号锁,2号线程又去拿1号锁,这样就僵持住了,就是死锁。
生产者消费者
有生产才能有消费,如果没生产消费者就要等待,如果消费者没消费完,生产者也要等待。所以我们需要一个东西来协调这件事。我们就可以通过阻塞队列来实现。
线程池
我们使用的普通的线程就是,先创建一个线程去执行任务,执行完后就释放。下次在重复这个过程。
这个就比较耗费资源了。
我们可以通过一个线程池,线程用完后我们归还给线程池,下次要用的时候在拿出来用,这样是不是比较好?
线程池主要有这几个参数:
- 核心线程
- 临时线程
- 空闲时间
- 线程队列
- 拒绝规则
线程状态
有6个状态
- 创建
- 可运行
- 计时等待
- 非计时等待
- 阻塞
- 终止状态
原子性
还是卖票案例,1号卖票员先从中央系统获得票数100,2号卖票员从也中央系统获得票数100.
这个时候1号卖票员卖出去了,也写入到了中央系统。
但是2号卖票员不知道1号卖出去了,他的记忆里面还是100,然后再去卖票,写入中央系统,那么票数就是错误的。
那么可以让2号卖票员在操作票数的时候在看一下中央系统,就能解决。
用Volatile这个关键字来修饰票数,就能让每个卖货员及时看中央系统
当然同步代码块也能解决这个问题,同步代码块有个特性就是,拿到锁之后,会先同步内存中的再去操作,所以也变相的解决了这个问题。
还有一个是原子性问题。
什么是原子性,就是一组事情要么同时成功要么同时失败。就类似与SQL里面的事务。
count++,就存在原子性问题。
它有3步骤:
- 先把内存值读到线程栈
- 然后再栈里面对这个值+1
- 在然后重新写到内存
如果这个时候多个线程同时操作这个。就会引起数据错乱。
用AotmicInteger就能修复这个问题。
它是一种乐观锁的模式,原理主要是3个值
- 内存值
- 线程栈的旧值
- 线程栈的新值
当内存值跟旧值相等时,那么就直接用新值覆盖。
当内存值跟新值不相等时,那么就回旋,重新读去内存,在重新执行原有的步骤。
并发工具
平时我们使用的集合很多都不是线程安全。
那么有哪些在并发情况下能用的集合?
- hashtable 是一种悲观锁的思想,直接锁死整个集合,效率比较低。
- ConcurrentHashMap,这个不会锁死整个集合,只会锁死单个地址的链表,效率会比较高些。
CountDownLatch
解决监听多个线程执行完毕后的动作。
SemaPhore
相当于通道管制,同一时间只允许多少线程通过。