原课程B站地址:全面深入学习java并发编程,中级程序员进阶必会
基础概念
临界区:一段代码块内如果对共享资源的多线程读写操作,称这段代码块为临界区
竞态条件:多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件
为了避免临界区内竞态条件发生,有两种主要手段,阻塞式(采用synchronized,lock)和非阻塞式(原子变量)
而此篇介绍的线程八锁指的是使用synchronized关键字对对象加锁的8种情况,意在说明synchronized关键字的正确使用姿势。
先说结论:
synchronized关键字只有锁住相同的对象才能使不同线程互斥的进入临界区
预备知识:
- 1.写在方法体的synchronized关键字与锁住this对象的语法等价
- 2.写在静态方法体上的synchronized关键字与锁住本类对象的语法等价
import lombok.extern.slf4j.Slf4j;
/**
*方法上的synchronized
* */
@Slf4j(topic = "c.Test4")
public class Test4 {
/**
* test1_1和test1_2等价
*
* 锁的是this对象
* */
public synchronized void test1_1(){
}
public void test1_2(){
synchronized (this){
}
}
/**
* test2_1和test2_2等价
*
* 锁住类对象
* */
public synchronized static void test2_1(){
}
public static void test2_2(){
synchronized (Test4.class){
}
}
}
开始分析
第一种情况
分析:
- 由于锁住了同一个对象,所以线程会互斥执行
输出:
- 1 2 或者 2 1
@Slf4j(topic = "c.Test")
public class TestTemp {
public static void main(String[] args) {
Number1 number1 = new Number1();
new Thread(() -> {
log.debug("a start");
number1.a();
},"t1").start();
new Thread(() -> {
log.debug("b start");
number1.b();
},"t2").start();
}
}
@Slf4j(topic = "c.Number1")
class Number1{
public synchronized void a(){
log.debug("1");
}
public synchronized void b(){
log.debug("2");
}
}
第二种情况
分析:
- 第二种比第一种多了一个睡眠1秒,但仍然锁住了同一个对象,所以线程依旧会互斥的执行
输出:
- a.一秒后输出 1 2
- b.先输出 2 ,一秒后输出 1
@Slf4j(topic = "c.Test")
public class TestTemp {
public static void main(String[] args) {
Number2 number2 = new Number2();
new Thread(() -> {
log.debug("a start");
try {
number2.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(() -> {
log.debug("b start");
number2.b();
},"t2").start();
}
}
@Slf4j(topic = "c.Number2")
class Number2{
public synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public synchronized void b(){
log.debug("2");
}
}
第三种情况
分析:
- 第三种比第二种多了一个方法c,c方法并没有被synchronized修饰,所以t3线程会和其他线程并发执行,并不会互斥
输出:
- a.先输出3,一秒后输出 1 2
- b.先输出 3 2 或者 2 3 ,一秒后输出 1
@Slf4j(topic = "c.Test")
@Slf4j(topic = "c.Test")
public class TestTemp {
public static void main(String[] args) {
Number3 number = new Number3();
new Thread(() -> {
log.debug("a start");
try {
number.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(() -> {
log.debug("b start");
number.b();
},"t2").start();
new Thread(() -> {
log.debug(" start");
number.c();
},"t3").start();
}
}
@Slf4j(topic = "c.Number3")
class Number3{
public synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public synchronized void b(){
log.debug("2");
}
public void c(){
log.debug("3");
}
}
第四种情况
分析:
- 虽然方法体使用了synchronized关键字,但是新建了两个对象,锁住的是不同的对象,所以无法实现互斥的访问
输出:
- 总是先输出2,一秒后输出 1
@Slf4j(topic = "c.Test")
public class TestTemp {
public static void main(String[] args) {
Number4 n1 = new Number4();
Number4 n2 = new Number4();
new Thread(() -> {
log.debug("a start");
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(() -> {
log.debug("b start");
n2.b();
},"t2").start();
}
}
@Slf4j(topic = "c.Number4")
class Number4{
public synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public synchronized void b(){
log.debug("2");
}
}
第五种情况
分析:
- 虽然在主方法中只新建了一个对象,但是方法a()的synchronized是在静态方法上,对Number5.class对象上锁,而b()方法 是对this对象上锁,对象不同,所以无法实现互斥访问
输出:
- 总是先输出2,一秒后输出 1
@Slf4j(topic = "c.Test")
public class TestTemp {
public static void main(String[] args) {
Number5 n = new Number5();
new Thread(() -> {
log.debug("a start");
try {
n.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(() -> {
log.debug("b start");
n.b();
},"t2").start();
}
}
@Slf4j(topic = "c.Number5")
class Number5{
public static synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public synchronized void b(){
log.debug("2");
}
}
第六种情况
分析:
- synchronized关键字作用在静态方法上,加锁对象是Number6.class,则是同一个对象,能实现互斥访问
输出:
- a. 先输出 2 ,一秒后输出 1
- b. 一秒后输出 1 2
@Slf4j(topic = "c.Test")
public class TestTemp {
public static void main(String[] args) {
Number6 n = new Number6();
new Thread(() -> {
log.debug("a start");
try {
n.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(() -> {
log.debug("b start");
n.b();
},"t1").start();
}
}
@Slf4j(topic = "c.Number6")
class Number6{
public static synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public static synchronized void b(){
log.debug("2");
}
}
第七种情况
分析:
- 很容易看出,加锁的对象不同,不能实现互斥访问
输出:
- 总是: 先输出2,一秒后输出 1
@Slf4j(topic = "c.Test")
public class TestTemp {
public static void main(String[] args) {
Number7 n1 = new Number7();
Number7 n2 = new Number7();
new Thread(() -> {
log.debug("a start");
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(() -> {
log.debug("b start");
n2.b();
},"t2").start();
}
}
@Slf4j(topic = "c.Number7")
class Number7{
public static synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public synchronized void b(){
log.debug("2");
}
}
第七种情况
分析:
- 虽然在main方法内新建了两个对象,但是a(),b()方法synchronized关键字均是加在静态方法上,即锁住的同样都是Number8.class对象,对象相同,可以实现互斥访问
输出:
- a. 先输出2,一秒后输出 1
- b. 一秒后输出 1 2
@Slf4j(topic = "c.Test")
public class TestTemp {
public static void main(String[] args) {
Number8 n1 = new Number8();
Number8 n2 = new Number8();
new Thread(() -> {
log.debug("a start");
try {
n1.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(() -> {
log.debug("b start");
n2.b();
},"t2").start();
}
}
@Slf4j(topic = "c.Number8")
class Number8{
public static synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public static synchronized void b(){
log.debug("2");
}
}
总结
以上就是线程八锁的全部内容,作为我的学习笔记记录在此,也希望大家学习愉快。最后,再重复一遍结论:synchronized关键字只有锁住相同的对象才能使不同线程互斥的进入临界区