多线程重点知识总结

多线程

1.Java中的多线程实现方式:
继承Thread
实现Runnable、Callable
线程池(推荐)

2.多线程常用操作方法:
sleep():线程休眠,运行态 -> 阻塞态,不会释放锁,立即交出CPU

yield():线程让步,运行态 -> 就绪态,不会释放对象锁,交出CPU时间不确定,由系统调度
只会让拥有相同优先级的线程有获取CPU的机会。

join():当前线程等待另一线程执行完毕后再恢复执行,运行态 -> 阻塞态,会释放对象锁

多线程等待与唤醒机制
wait()/notify():synchronized
要使用wait/notify,必须在同步方法或同步代码块中使用,会释放对象锁
wait() 运行态 -> 阻塞态
notify() 阻塞态 -> 就绪态

Object及其子类的每个对象都有两个队列:

同步队列:获取该对象锁失败的线程进入同步队列
等待队列:调用wait()的线程进入等待队列(等待被notify)

守护线程:
Java线程只有两类,用户线程,守护线程
创建的线程默认都是用户线程,包括主线程

守护线程:后台线程,只有当当前JVM进程中最后一个用户线程终止,守护线程会随着JVM一同停止。
GC线程就是典型的守护线程

setDeamn将用户线程置为守护线程

同步
保护的对象是谁 锁是谁

JVM 内存模型(JMM):并发程序
描述共享变量(类成员变量、静态变量)如何存储

工作内存:变量在线程中的操作(读写)必须在工作内存中进行,
工作内存中保存了所有变量的副本

主内存:所有变量必须在主内存中存储

线程特性:
原子性:一组操作要么同时发生,要么一个都不发生
基本数据类型的读写操作都属于原子性操作

可见性:某一线程对于变量的修改对于其他线程而言是立即可见的
synchronized(lock)、volatile、final

有序性:
在单线程场景下,代码执行顺序就是代码书写顺序。
多线程场景下,所有代码都是乱序的。

线程安全指的是以上三个特性同时满足

使用synchronized解决同步问题
同步代码块
synchronized(对象)
-任意类的对象
-类.class

同步方法
-修饰类成员方法 锁的是当前对象this
-修饰类方法(static) 锁的是当前类的反射对象

class Account {
	String password;
	short sal;
	Object salLock = new Object();
	Object passLock = new Object();
	short quQian() {
		synchornizd(salLock)
	}
	void cunQian() {
		synchornizd(salLock)
	}

	String seePassword() {
		synchornizd(passLock)
	}
	void gaiPassword() {
		synchornizd(passLock)
	}
}

锁对象
salLock -> sal
passLock -> password

synchronized底层实现:对象Monitor机制
任意Object及其子类对象内部在JVM中都附加Monitor.获取一个对象的锁,实际上就是获取该对象Monitor(计数器).

当一个线程尝试获取对象Monitor时,
I.若此时Monitor值为0,该对象未被任何线程获取,当前线程获取Monitor,将持有线程置为当前线程,Monitor值+1;
II.若此时Monitor值不为0,此时该Monitor已被线程持有
a.若当前线程恰好是持有线程,Monitor值再次+1,当前线程继续进入同步块(锁的可重入)
b.若持有线程不是当前线程,当前线程进入同步队列等待Monitor值减为0

加锁:monitorenter + 1
解锁:monitorexit - 1
任意时刻只有当Monitor值为0表示无锁状态。

JDK1.6后synchronized优化
CAS:Compare And Swap 无锁保证线程安全
CAS(O,V,N)
其中:
V:主内存存放的实际变量值
O:当前线程认为的变量值
N:希望将变量替换的值
例如:
主内存 ticket = 0;
线程1 ticket = 0; cas(1,1,0) O==V,认为此时没有线程修改主内存的值,0 -> 主内存
线程2 ticket = 0; cas(1,0,0) O != V,
认为此时已经有别的线程修改了主内存的值,修改失败,返回主内存的最新值
线程3 ticket = 1;

当O == V时,认为此时没有线程修改变量值,成功将N值替换回主内存
当O != V时,此时已有线程修改变量值,替换失败,返回主内存的最新值再次重试。

ABA问题:
添加版本号
num = 0;
线程1 cas(0,0,1) 1-> 主内存 num.1
线程2 cas(1,1,0) 0 -> 主内存 num.2
线程3 cas(0,0,5) 5 -> 主内存 num.0
此时线程2恰好将值修改为主内存中的值,线程3会认为在他前面并没有线程修改过主内存中的值,但实际上线程1和线程2都修改过,为了避免此问题,使用添加版本号来解决。

关于锁的问题:
偏向锁 -> 轻量级锁 -> 重量级锁(JDK1.6之前 synchronized就是重量级锁)

重量级锁(悲观锁):
获取Monitor的失败的线程进入同步队列,状态置为阻塞态
同一时刻有不同线程在进入同步块

偏向锁(乐观锁):
认为只有一个线程在来回进入同步块,
直接将加锁与解锁的过程都省略,每次进入同步块之前
只是判断一下同步块线程是否是当前线程

轻量级锁:
不同时刻有不同的线程进入同步块
每次线程在进入同步块时都需要加锁与解锁

随着竞争的不断升级,锁也会不断升级,锁不会降级。

自适应自旋:重量级锁的优化
获取锁失败的线程不会立即阻塞,而是在CPU空跑一段无用代码,若在此时间段成功获取锁,
则下次再获取锁失败时,空跑时间适当延长;否则下次空跑时间缩短。

锁粗化

    static StringBuffer sb = new StringBuffer();
    public static void main(String[] args) {
        sb.append("hello");
        sb.append("world");
        sb.append("bit");
    }

将多次连续的加减锁过程粗化为一次大的加锁与解锁过程,减少无用的加减锁过程,提高效率.

锁消除

  public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        sb.append("hello");
        sb.append("world");
        sb.append("bit");
    }

当变量为线程私有变量时,将原先方法上的synchronized消除掉。

死锁

class Boy{}
class Girl{}
public class DeadLockTest {
    public static void main(String[] args) {
        Boy boy = new Boy();
        Girl girl = new Girl();

        Thread boyThread = new Thread(() -> {
           synchronized (boy) {
               System.out.println("my name is zs");
               synchronized (girl) {
                   System.out.println("我是男孩!");
               }
           }
        });

        Thread girlThread = new Thread(() -> {
           synchronized (girl) {
               System.out.println("my name is beauty");
               synchronized (boy) {
                   System.out.println("我是女孩!");
               }
           }
        });
        boyThread.start();
        girlThread.start();
    }
}

产生死锁的四个条件:
1.互斥:资源x在任意一个时刻只能被一个线程持有
2.占有且等待:线程1占有资源x的同时等待资源y,并不释放x
3.不可抢占:资源x一旦被线程1占有后,其他线程不能抢占x
4.循环等待:线程1持有x,等待y;线程2持有y,等待x;

死锁的产生原因:以上四个条件同时满足

synchronized如何解锁死锁?
JDK1.5 Lock体系
a.使用格式

try {
	// 同步代码块
	// 显式加锁
	lock.lock();
}catch (Exception e) {
	
}finally {
	// 显式解锁
	lock.unlock();
}

b.常用方法
lock() : 加锁,语义与synchronized完全一致

unlock() : 解锁

void lockInterruptibly() throws InterruptedException:响应中断加锁

boolean tryLock():非阻塞式获取锁.获取锁成功返回true,进入同步块;
获取锁失败返回false,线程继续执行其他代码

boolean tryLock(long time, TimeUnit unit)

throws InterruptedException: 支持超时

synchronized与ReentrantLock的关系与区别:
1.都属于独占锁(任意一个时刻,只有一个线程能获取到资源)的实现,
都支持可重入锁。

2.synchronized是关键字,JVM层面实现;
ReentrantLock是Java语言层面实现的"管程"

3.ReentrantLock具备一些synchronized不具备的功能:
响应中断、非阻塞式获取锁、支持超时获取锁、支持公平锁

公平锁:等待时间最长的线程最先获取到锁

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值