用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的“生产者,消费者模型”。
1、线程实现的方式
- 继承Thread类
- 实现Runnable接口
- 使用Callable和Future
2、Thread中的start()
和run()
有什么区别
start()
方法用来启动线程,真正实现了多线程,无需等待run方法执行完毕
run()
当作普通方法的方式调用,程序顺序执行,要等run方法执行完毕后才能继续执行下面的方法
3、线程的状态
NEW状态
new
了一个Thread对象,并没有启动该线程RUNNABLE状态
线程对象通过
start()
方法进入runnable状态,启动的线程不一定会立即得到执行,线程的运行与否要看cpu的调度,我们把这个中间状态叫可执行状态(RUNNABLE)RUNNING状态
一旦cpu通过轮询货其他方式从任务可以执行队列中选中了线程,此时它才能真正的执行自己的逻辑代码
BLOCKED状态
线程正在等待获取锁
调用了
wait()
或者sleep()
、进行了某个阻塞IO操作、获得了某个资源的锁TERMINATED状态
TERMINATED是一个线程的最终状态
线程运行正常结束、线程运行出错意外结束、JVM Crash 导致所有的线程都结束
线程状态转换
4、Java解决线程安全的四种方式
乐观锁与悲观锁
悲观锁,顾名思义它是悲观的。讲得通俗点就是,认为自己在使用数据的时候,一定有别的线程来修改数据,因此在获取数据的时候先加锁,确保数据不会被线程修改。形象理解就是总觉得有刁民想害朕。
而乐观锁就比较乐观了,认为在使用数据时,不会有别的线程来修改数据,就不会加锁,只是在更新数据的时候去判断之前有没有别的线程来更新了数据。具体用法在下面讲解。
方法一:使用synchronized关键字
一个表现为原生语法层面的互斥锁,它是一种悲观锁,使用它的时候我们一般需要一个监听对象 并且监听对象必须是唯一的,通常就是当前类的字节码对象。它是JVM级别的,不会造成死锁的情况。使用synchronized可以拿来修饰类,静态方法,普通方法和代码块。比如:Hashtable类就是使用synchronized来修饰方法的
方法二:Lock接口下的实现类
方法三:使用线程本地存储ThreadLocal
方法四:使用乐观锁机制
5、高并发中的集合有哪些问题
- 第一代线程安全集合类
Vector、Hashtable
使用synchronized修饰方法,效率低下
- 第二代线程非安全集合类
ArrayList、HashMap
线程不安全但是性能好
- 第三代线程安全集合类
在大量并发情况下如何提高集合的效率和安全呢
Java容器、Java的发展历史