多线程
- 程序
- 一系列的cpu指令
- 执行的时候位于内存中
- 在外存中储存
- 进程
- 运行中的程序
- 进程之间禁止数据干涉,数据通信异常麻烦
- 线程
Thread类
- 主要方法
- 线程信息
- static Thread currentThread() 获取线程名称 Returns a reference to the currently executing thread object.
- String getName() 获取线程名称 Returns this thread's name.
- void setName(String name) 设置线程名称 Changes the name of this thread to be equal to the argument name.
- boolean isAlive() 线程是否活着 Tests if this thread is alive.
- void join() 添加进程,等到他运行完成 Waits for this thread to die.
- 线程调节
- static void sleep(long millis) 线程休眠,释放cpu资源,但监控器不释放,对象锁不释放 Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and
- void interrupt() 打断进程 Interrupts this thread.
- 线程启动
- void run() 方法体,线程入口 If this thread was constructed using a separate Runnable run object, then that Runnable object's run method is called; otherwise, this method does nothing and returns.
- void start() 启动线程 Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.
创建新线程方式
- 实现Runnable接口
- 通过具体类
- 初始化,写一个具体类,实现Runnable接口,run方法就是线程体,是线程的入口
- 创建,创建Thread类,具体类为实参
- 开始,调用线程的start方法,启动线程,会调用的run方法
- 停止,线程直接改变标志变量来停止不同循环,通知的方式停止线程
- 通过匿名内部类,的方式来实现Runnable接口,并重写需要执行的方法体
- 匿名类的缺点是没有保存对象名及类名,不能对重写的匿名类和对象进行操作
- 原理
- 理解, 通过关联Runnable的方式来调用run方法体,来执行另一个线程
- 内存原理, 开辟另一个栈
- 代码原理, Thread对象的run方法调用runnable类的run方法,栈里两层run方法
- 继承Thread类
- 通过具体类继承
- 初始化,写一个类继承Thread,重写父类run方法
- 创建,创建具体类的对象,相当于创建创建了线程对象
- 调用这个对象的Start的方法
- 通过匿名内部类,的方式来继承Thread类,创建的同时重写run方法体
调度策略
- 调度策略
- java调度方法
- 同级优先度的线程组成先进先出对列(先到先服务),仍然使用抢占式策略
- 高优先度优先, 使用优先度的抢占策略
- 线程的优先级控制
- MAX_PRIORITY(10);
- MIN _PRIORITY (1);
- NORM_PRIORITY (5);
- 涉及的方法:
- getPriority() :返回线程优先值
- setPriority(int newPriority) :改变线程的优先级
- Thread类方法
- static void yield():线程让步
- 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
- 若队列中没有同优先级的线程,忽略此方法
- join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
- static void sleep(long millis):(指定时间:毫秒)
- 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
- 抛出InterruptedException异常
- 导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态,在调用sleep()方法的过程中,线程不会释放对象锁。
- stop(): 强制线程生命期结束
- boolean isAlive():返回boolean,判断线程是否还活着
- void interrupt() 打断 Interrupts this thread.
生命周期
- 新建: 创建Tread类对象
- 就绪: 调用start方法,线程进入排队状态
- 运行: 线程获取到cpu资源,进行执行
- 阻塞: 当前资源得不到满足,无法执行,等待其他进程释放
- 死亡: 完成了当前进程所有任务,或被其他进程终止, 一旦死亡,不可以使用start重启,即每个创建的Thread对象只能启动一次
分类
- 守护线程
- 保护用户线程
- 服务用户线程,通过在start()方法前调用
- thread.setDatemon(true)可以把一个用户线程变成一个守护线程
- gc()线程,即垃圾回收器
- jvm中都是守护线程,没有用户线程运行时当前jvm将退出。
- 用户线程
线程同步与安全
- 问题提出,同时修改同一个数据
- 多个线程对同一个数据的共享
- 多个线程对同一账号的操作
- 当前线程的条件判断和执行不在同一单位执行,导致条件改变依旧执行
- sysnchronized(this){ ...} //
- 对象锁,使用同步代码块,代码块需要一个同步锁对象:同一时间内,只允许一个线程进入此代码
- 存在于Object里面
- 一旦被锁定,则锁定内容具有原子性,不可分割
- sysnchronized可以修饰run函数,表示要执行完才释放资源,不就和单线程就没什么区别了
- sysnchronized(),参数为多锁对象,当执行到sysnchronized代码块时检查锁对象是否还在,还在则持有执行,没有等待
- 多线程的对象锁必须时一个才能互斥
- 最好的锁对象时常量
- 隐式锁,自动解锁
- 缺点,效率降低
死锁问题
- 不同的线程分别占用字节所需的资源,等对方释放资源
//死锁问题举例
public class TestDeadLock {
static StringBuffer s1 = new StringBuffer();
static StringBuffer s2 = new StringBuffer();
public static void main(String[] args) {
new Thread() { //线程1,运行需要s1,s2并依次获取
public void run() {
synchronized (s1) { //对象锁S1
s2.append("A");
synchronized (s2) { //对象锁s1
s2.append("B");
System.out.print(s1);
System.out.print(s2);
}}}}.start();
new Thread() { //线程2,运行需要s2,s1,并依次获取
public void run() {
synchronized (s2) { //对象锁s2
s2.append("C");
synchronized (s1) { //对象锁s1
s1.append("D");
System.out.print(s2);
System.out.print(s1);
}}}}.start();
}
}
线程通信&对象锁方法
- wait()
- 令当前线程挂起并放弃cpu资源,监视器,对象锁,使别的线程可以访问并修改共享资源,而当前的线程被唤醒之后再排队等候拿到锁资源之后再次执行
- 结束等待需要其他线程使用notify唤醒,否则线程死亡。
- notify()
- 唤醒正在排队等待同步资源的线程中优先级最好者结束等待
- 只能通知一个线程
- notifyAll() ,通知多个线程,一般指通过wait方法睡眠的线程
- 对象锁方法在sysnchronized语句块中可以调用,其他位置调用报错
线程同步问题(一个线程不间歇的访问另一个线程的标志变量,不会察觉到标志变量的改变)
- 解决方式加volatile
- volatile //主存的数据不会子线程储存副本
- 百度查询
- 问题来源&内存模型
- cpu速度远远大于主存,为了加速优化,cpu所需要用到的数据会在高速缓冲中保留副本,之行完当前指令会再刷新回去,单线程的化不存在任何问题,但当多线程的时候当主存的内存改变了,而cpu还在使用高速缓存中的数据(不同的cpu有不同的缓存,或者单cpu使用轮回来实现多线程的时候也有不同的缓存)
- 解决方式:
- 总线加锁方式,类似于读写保护,当有cpu进行访问主存一个数据时,其他cpu不允许访问,直达当前cpu完成操作。早期采用方式,但是这种方式会导致效率低
- 缓存一致协议,当前cpu写数据的时候发现它时共享变量时,向其他cpu发送消息,表明其他缓冲中的数据无效,当其他cpu需要用到缓存数据时要再次从缓存中读取。例:Intel 的MESI协议
- 多线程中的几个概念(多线程会导致的问题)
- 原子性
- 原子性可保证当前执行不会被其他线程打断
- 原子性指当前命令是否由cpu一次执行完
- 可见性
- 修改共享数据会让相关的cpu立即可知
- 有序性
- 指令是否重排
- cpu为了优化加速,对没有拓扑关系的命令会进行重排,保证结果的正确性,但多线程会打乱这个过程
- volatile关键字的两层语义
- 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
- 禁止进行指令重排序。
- 作用效果
- 使用volatile关键字会强制将修改的值立即写入主存;
- 使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效);
- 由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。