
进程状态:就绪:正在 CPU 上执行,或者随时可以去 CPU 上执行
阻塞:暂时不能参与 CPU 的执行
Java 的线程,对应状态做了更详细的区分,不仅仅是就绪和阻塞了
六种状态:
jconsole 可以直接看到线程的状态罪魁祸首是:线程的调度是随机的
在多个线程同时执行某个代码的时候,可能会引起一些奇怪的 bug。理解了线程安全,才能避免/解决上述的 bug
因为多个线程并发执行引起的 bug,称为“线程安全问题”或者“线程不安全”
public class Demo8 {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count++;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}若将 join 执行的时机改一下,就不会产生这种 bug
public class Demo8 {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count++;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
count++;
}
});
t1.start();
t1.join();
t2.start();
t2.join();
System.out.println(count);
}
}count++ 操作,在CPU 看来,是三个指令
1. 把内存中的数据读取到 CPU 寄存器—— load
2. 把 CPU 寄存器里的数据+1—— add
3. 把寄存器的值,写回内存—— save
由于不同架构的 CPU,有不同的指令集,不同的指令集里都有不同的指令,针对这三个操作,不同 CPU 里的对应指令名称肯定是不同的

count++是三个指令,可能会出现 CPU 执行了其中的一个或者两个或者三个指令调度走的情况count 进行++,就容易出现 bug

count 的大小永远小于 10000,但也有可能小于 5000t1++一次的过程中,t2++两次,这样得到的结果,正常应该++3 次,而实际只++1 次综上,原因为:
线程在操作系统中,随机调度,抢占式执行
多个线程,同时修改同一个变量
修改操作不是“原子“的(比如++就是三个指令)
内存可见性
指令重排序
count++变成原子的,也没有干预线程的调度,只不是通过这种加锁的方式,使一个线程在执行 count++的过程中,其他的线程的 count++不能插队进来synchornized 关键字,来完成加锁操作
- 这是一个关键字,不是函数,后面的() 并非参数,而是需要指定一个“锁对象”,然后通过“锁对象”来进行后续的判定
- () 里的对象可以指定任何对象
- {}里面的代码,就是要打包到一起的,() 里面还可以放任意的其他代码,包括调用别的方法啥的,只要是合法的 Java 代码,都是可以放的 
public class Demo8 {
private static int count = 0;
private static Object locker = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized(locker){
count++;
};
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (locker){
count++;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}locker 对象加锁,t1 先加锁,就加锁成功了,于是 t1 继续执行 () 里面的代码(进厕所,执行上厕所的操作)locker 对象已经被别人先锁了,就只能等(说明现在厕所有人,于是只能排队等待)unlock 一定是在 save 之后,确保了 t2 执行 load 的时候,t1 已经 save double 这种内置类型写到() 里面,但是其他类型,只要是 Object 或者其子类,都可以。例如:一个字符串 s = “hello”,一个链表 list...... 都行注意:
join 的串行执行效率是要高很多的Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized(locker){
count++;
};
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (locker){
count++;
}
}
});count++是串行执行的,而 for 循环、比较、i++都是并发执行的如果是三个线程针对同一个对象加锁,也是类似的情况
此处
synchronized是JVM提供的功能,synchronized底层实现就是JVM中,通过C++来实现的。进一步的,也是依靠操作系统提供的API实现的加锁,操作系统的API则是来自于CPU上支持的特殊指令来实现的系统原生的加锁 API 和很多编程语言的加锁操作的封装方式是两个函数:
lock(),unlock()像 Java 这种通过
synchronized关键字,来同时完成加锁/解锁的,比较少见 - 原生的这种做法,最大的问题在于unlock可能执行不到,后面排队想用这个锁的就用不了(占着坑不拉屎) - Java 中的synchronized是进入代码块就加锁,出了代码块就解锁,无论是return还是抛出异常,不管以哪种方式出了代码块都会自动解锁,有效避免了没有执行解锁操作的情况
类对象:
synchronized() 里面synchronized() 还可以修饰一个方法
class Counter {
public int count = 0;
synchronized public void add() {
count++;
}
//等价于
public void add() {
synchronized (this) {
count++;
}
}
} 针对这个写法,锁对象就是
this,谁调用add谁就是锁对象在这里调用
add的都是counter,所以他们是同一个锁对象
synchronized public static void func() {
//错
}
//对
public static void func() {
synchronized (Counter.class)
}
static方法没有this
static方法也叫类方法,和具体的实例无关,只是和类相关此时
static方法和类对象相关,此时的写法就是给类对象加锁
synchronized 就一定线程安全,还是得看代码咋写synchronized 的几种使用方式:
synchronized(){},() 里面指定锁对象synchronized 修饰一个普通的方法,相当于针对 this 加锁synchronized 修饰一个静态方法,相当于针对对应的类对象加锁理解锁对象的作用,可以把任意的 Object/Object 子类的对象作为锁对象。锁对象是啥不重要,重要的是两个线程的锁对象是否是同一个。是同一个才会出现阻塞/锁竞争,不是同一个是不会出现的
使用锁的过程中,一种典型的、严重的 bug
class Counter {
public int count = 0;
public void add() {
//随后调用add方法,又尝试对counter加锁
//但counter已经被加锁了,如果再次尝试对counter加锁,就会出现阻塞等待
synchronized (this){
count++;
}
}
}
public class Demo9 {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
//首先执行到这里,对counter加锁成功
synchronized (counter){
counter.add();
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (counter){
counter.add();
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter.count);
}
}synchronized 想要拿到锁,就需要外面的 synchronized 释放锁undefinedsynchronized 要释放锁,就需要执行到 }} ,就需要执行完这里的 addadd 阻塞着呢~可重入锁:
synchronized 是“可重入锁”,针对上述一个线程连续加锁两次的情况做了特殊处理。C++/Python 中的锁就没有这样的功能
比如你向一个女生表白了,她同意了,那你就对她加锁了,她的持有人就是你
再有人对她加锁的时候,她就会进行判定,看当前这个要加锁的人,是不是她的持有人
若是隔壁老王找到她说“美女,我好喜欢你”,那她就会告诉他“你是个好人”
若是你找到她说“我好喜欢你”,那她就会说“我也好喜欢你”
//真加锁
synchronized (this) {
//直接放行,不会真加锁
synchronized (this){
}
}若有两个线程 1 和 2,两把锁 A 和 B
线程 1 先针对 A 加锁,线程 2 针对 B 加锁
线程 1 不释放锁 A 的情况下,再针对 2 加锁;同时,线程 2 在不释放 B 的情况下针对 A 加锁
undefined
比如你和你女朋友出去吃饺子,她拿的醋,你拿的酱油
你说:你把醋给我,我用完了给你
她说:凭什么,你把酱油给我,我用完了给你
结果是你俩互不相让,僵持住了
public class Demo1 {
private static Object locker1 = new Object();
private static Object locker2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (locker1){
System.out.println("t1 加锁 locker1 完成");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (locker2){
System.out.println("t1 加锁 locker2 完成");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (locker2) {
System.out.println("t2 加锁 locker2 完成");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (locker1){
System.out.println("t2 加锁 locker1 完成");
}
}
});
t1.start();
t2.start();
}
}
打印结果:
t1 加锁 locker1 完成
t2 加锁 locker2 完成代码中的
sleep是为了确保 t1 和 t2 都先分别拿到了locker1和locker2,然后再分别拿对方的锁。如果没有sleep,执行顺序就不可控,可能出现某个线程一口气拿到两把锁,另一个线程还没开始执行的情况,无法构造出死锁两个线程的第二次交叉加锁都没执行到,说明这两个线程都在第二次 synchronized 的时候阻塞住了,如果不人为干预,就会永远堵在这
image.png|659

死锁的四个必要条件:(缺一不可)
public class Demo1 {
private static Object locker1 = new Object();
private static Object locker2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (locker1){
System.out.println("t1 加锁 locker1 完成");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (locker2){
System.out.println("t1 加锁 locker2 完成");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (locker1) {
System.out.println("t2 加锁 locker1 完成");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (locker2){
System.out.println("t2 加锁 locker2 完成");
}
}
});
t1.start();
t2.start();
}
}
输出结果:
t1 加锁 locker1 完成
t1 加锁 locker2 完成
t2 加锁 locker1 完成
t2 加锁 locker2 完成locker1,再上 locker2 就能解决这里的死锁问题了原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。