Java 多线程
多线程
进程:程序的基本执行实体
线程:操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位 简单理解:应用软件中相互独立,可以同时运行的功能
有了多线程,我们就可以让程序同时做多件事情,提高效率
并发:同一时刻有,多个指令在单个CPU上交替执行
并行:在同一时刻,多个指令在多个CPU上同时执行
多线程的实现方式
继承Thread类的方式
自己定义一个类继承Thread
重写run方法
创建子类的对象,并启动线程
实现Runnable接口的方式
自己定义一个类实现Runnable接口
重写里面的run方法
创建自己的类的对象
创建一个Thread类的对象并开启线程
利用Callable接口和Future接口
前两种方法都没有返回值,如果想获取多线程运行的结果:
创建一个类实现Callable接口
重写call(是有返回值的,表示多线程运行的结果)
创建MyCallable的对象(表示多线程要执行的任务)
创建FutureTask的对象(作用为管理多线程运行的结果)
创建Thread类的对象,并启动(表示线程)
三种方式对比

常见的成员方法

细节:
如果我们没有给线程设置名字,其默认名字为Thread-X(序号从0开始)
如果要给线程设置名字,可以用set方法进行设置,也可以用构造方法
当JVM虚拟机启动时候,会自动地启动多条线程,其中有一条线程就叫做main线程,作用就是去调用main方法,并执行里面的代码.
sleep方法哪条线程执行到了,哪条线程就会在这里停留对应的时间,时间到了之后就会自动醒来,继续执行下面的其他代码
Java采用抢占式调度的方法,也就是随机
最小优先级为1,最大为10,默认为5,优先级越高抢到CPU的概率越高
守护线程: 当其他非守护线程执行完毕后,守护线程会陆续结束 应用场景:如QQ聊天窗口与传文件,如果聊天窗口关闭了传文件就没必要了,所以可以把传文件设为守护线程
yield方法可以让线程分配尽可能均匀一点,较少使用
join方法表示将该线程插到当前线程之前,该线程结束后才会继续执行当先线程
线程的生命周期
创建线程对象:新建 调用start()后:有执行资格,没有执行权,需要不停地抢CPU:就绪 抢到CPU的执行权:执行资格,没有执行权:运行(可能又被其他线程抢走CPU的执行权回到上一个状态 run()执行完毕:线程死亡,变成垃圾:死亡
sleep()或其他阻塞式方法:没有执行资格没有执行权,等到时间到进入就绪状态争抢CPU
所以:sleep方法睡眠时间到后不会立即执行下面的代码,只有抢到CPU后才能执行
线程的安全问题
需求:100张票3个窗口售票
上述代码运行的结果有问题:
会出现同一张票卖给多个人
会出现超出100张票,如101,102
原因:线程执行时有随机性,某个线程执行到一半就会被其他线程抢走,加入刚好在ticket++执行完被抢走,别的线程执行完ticket++再回到原线程时,sout打印的ticket就变了
同步代码块
把操作共享数据的代码锁起来
特点1:锁默认打开,有一个线程进去了,锁自动关闭
特点2:里面代码全部执行完毕,线程出来,锁自动打开
同步方法
就是把synchronized关键字加到方法上
格式:
特点1:同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己制定
非静态:this
静态:当前类的字节码文件对象
StringBuilder与StringBuffer:后者每个方法都加了synchronized,是线程安全的
Lock锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰地表达如何枷锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作 Lock中提供了获得锁和释放锁的方法 void lock()获得锁 void unlock()释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化 ReentrantLock的构造方法 ReentrantLock()创建一个ReentrantLock的实例
上述代码有问题,在19:break;行,假设线程1拿到了CPU的执行权,此时线程1调出了循环,没有执行22:lock.unlock();,此时程序不会结束,锁没有关上
正确写法:使用try{}catch(){}finally{}
死锁
死锁是一个错误,我们应该避免发生
千万不要让两个锁嵌套起来
生产者和消费者(等待唤醒机制)
生产者和消费者是一个十分经典的多线程协作的模式
生产者:生产数据 消费者:消费数据
常见方法:

阻塞队列
继承结构
接口:Iterable,Collection,Queue,BlockingQueue 实现类:
ArrayBlockingQueue(底层是数组,有界)
LinkedBlockingQueue(底层是链表,无界但不是真正的无界,最大值为int的最大值)
线程的六种状态
新建状态(new)全部代码运行完毕
线程池
核心原理
创建一个池子,池子中是空的
提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
代码实现:
创建线程池
提交任务
所有任务全部执行完毕,关闭线程池
Executors:线程池的工具类,通过调用方法返回不同类型的线程池对象

自定义线程池

线程池多大合适?
CPU密集型运算:最大并行数+1 +1的目的是当前面线程出问题时可以顶上去
IO密集型运算:最大并行数$\times$期望CPU利用率$\times\frac{总时间(CPU计算时间+等待时间)}{CPU计算时间}$
4核8线程最大并行数为8
多线程的额外扩展
见阿玮老师的多线程(额外扩展).md,含乐观锁,悲观锁,volatile关键字,原子性,一些并发工具类