一、锁对象的概念
- 非静态同步方法:
锁的是当前实例对象(this)
。当一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁。 - 静态同步方法:
锁的是当前类的Class对象
。当一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,这种互斥行为适用于同一个类的所有实例对象。 - 修饰代码块:锁的是括号中的对象。
synchronized(对象){
}
二、线程“八锁”
所谓的线程八锁:其实就是从八段代码例子中,理解synchronized的用法,不要太纠结这个名称,不需要纠结八种情况里面好像有些情况是相似的,只需要理解这八个代码例子里面的synchronized 的用法,其他都不用管。
(其实就是考察 synchronized 锁住的是哪个对象)
- 如果纠结这八种情况是否有重复或者是相似的事情,就像我一样掉坑里了,就比如情况一和情况二,它两的区别就是情况二多了一个sleep方法,我当时就一直想,这两种情况不都是一样的嘛,为啥是两种情况呢?唉,他喵的,后来我就悟了,我他喵的想那么多干嘛,我理解它每个情况synchronized 的用法就行了呀。
1情况一:两个线程分别访问 同一个对象(实例对象) 的两个普通同步方法
- synchronized修饰普通方法默认使用的
锁对象是this
,由于是同一个对象的两个方法,所以他们的锁对象也是同一个,因此这两个方法会互斥执行。当一个线程执行其中一个方法时,另一个线程必须等待。
- 因为这里使用的是用一个Number对象n1,所以线程1和线程2锁住的对象都是this(n1),它们是互斥的。
public class Tongbu {
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n1.b(); }).start();
}
}
class Number{
public synchronized void a() {
System.out.println("a()");
}
public synchronized void b() {
System.out.println("b()");
}
}
结果1:(先执行第一个线程)
a()
b()
结果2:(先执行第二个线程)
b()
a()
情况二:两个线程分别访问 同一个对象(实例对象) 的两个普通同步方法,与情况一相比就多了一个sleep方法
- synchronized修饰普通方法默认使用的
锁对象是this
,由于是同一个对象的两个方法,所以他们的锁对象也是同一个,因此这两个方法会互斥执行。当一个线程执行其中一个方法时,另一个线程必须等待。
- 因为这里使用的是用一个Number对象n1,所以线程1和线程2锁住的对象都是this(n1),它们是互斥的。(
sleep方法不会释放锁的
)
public class Tongbu {
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n1.b(); }).start();
}
}
class Number{
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a()");
}
public synchronized void b() {
System.out.println("b()");
}
}
结果1:(先执行第一个线程)
-- 等待1s然后输出
a()
b()
结果2:(先执行第二个线程)
b()
-- 等待1s然后输出
a()
情况三:在情况二的基础上新增了一个没有被synchronized修饰的普通方法,再启动一个新的线程执行新增的方法
- synchronized修饰普通方法默认使用的
锁对象是this
,由于是同一个对象的两个方法,所以他们的锁对象也是同一个,因此这两个方法会互斥执行。当一个线程执行其中一个方法时,另一个线程必须等待。
- 因为这里使用的是用一个Number对象n1,所以线程1和线程2锁住的对象都是this(n1),它们是互斥的。(
sleep方法不会释放锁的
),但是c方法没有synchronized 修饰,所以它没有锁,所以c方法启动的时候不会有互斥效果,它的执行不会受到a或b方法执行的影响。
public class Tongbu {
public static void main(String[] args) {
Number n1 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n1.b(); }).start();
new Thread(()->{ n1.c(); }).start();
}
}
class Number{
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a()");
}
public synchronized void b() {
System.out.println("b()");
}
public void c() {
System.out.println("c()");
}
}
结果1:
c()
-- 等待1s后输出
a()
b()
结果2:
c()
b()
-- 等待1s后输出
a()
结果3:
b()
c()
-- 等待1s后输出
a()
2情况四:两个线程分别访问两个不同对象的两个普通同步方法:
- 由于synchronized修饰普通方法默认使用的
锁对象是this
,由于是两个实例对象,所以它们使用的锁也不同(两个不同的对象,它们的this不一样)。因此,这两个方法可以同时被不同的线程访问,不会互斥。
- 这里的n1和n2不是一个对象实例,所以它俩不互斥。线程1锁住的对象是n1,线程2锁住的对象是n2,这两个线程是并行执行的。
public class Tongbu {
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n2.b(); }).start();
}
}
class Number{
public synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a()");
}
public synchronized void b() {
System.out.println("b()");
}
}
结果1:(即线程2总是先执行完(因为线程1要等待1s))
b()
-- 等待1s后输出
a()
3情况五:一个线程访问访问类的静态同步方法,另一个线程对象的普通同步方法:
- 由于锁对象不同(
一个是类的Class对象,另一个是实例对象this
),因此这两个方法不会互斥执行。它们可以同时
被不同的线程访问,即它们是同时执行的
public class Tongbu {
public static void main(String[] args) {
Number n = new Number();
new Thread(()->{ n.a();}).start();
new Thread(()->{ n.b(); }).start();
}
}
class Number{
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a()");
}
public synchronized void b() {
System.out.println("b()");
}
}
结果1:
b()
-- 等待1s后输出
a()
4情况六:两个线程分别访问同一个类的两个静态同步方法:
- 由于静态方法默认使用的锁对象是
类的Class对象
,所以两个线程获取的锁对象都是同一个,因此这两个方法会互斥执行。当一个线程执行其中一个静态方法时,另一个线程必须等待。
public class Tongbu {
public static void main(String[] args) {
new Thread(()->{ Number.a();}).start();
new Thread(()->{ Number.b(); }).start();
}
}
class Number{
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a()");
}
public static synchronized void b() {
System.out.println("b()");
}
}
结果1:先执行第一个线程
-- 等待1s后输出
a()
b()
结果2:先执行第二个线程
b()
-- 等待1s后输出
a()
情况七:两个线程分别访问两个不同对象的静态方法和普通方法:
- 由于锁对象不同(
一个是类的Class对象,另一个是实例对象this
),因此这两个方法不会互斥执行。它们可以同时
被不同的线程访问,即它们是同时执行的
- 虽然线程1是使用实例对象调用静态方法a,但是它锁的对象还是Number类.class对象,线程2锁的对象是n2对象(this),所以它们锁的不是同一个对象,所以它们不互斥。
public class Tongbu {
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n2.b(); }).start();
}
}
class Number{
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a()");
}
public synchronized void b() {
System.out.println("b()");
}
}
结果1:
b()
-- 等待1s后输出
a()
情况八:两个线程分别访问两个不同对象的两个静态同步方法:
- 由于静态方法默认使用的锁对象是
类的Class对象
,所以两个线程获取的锁对象都是同一个,因此这两个方法会互斥执行。当一个线程执行其中一个静态方法时,另一个线程必须等待。
- 虽然线程1和线程2调用静态a、b方法是两个不同的实例对象,但是它们锁的都是Number.class对象,所以它们锁的是同一个对象,所以它们是互斥的。
public class Tongbu {
public static void main(String[] args) {
Number n1 = new Number();
Number n2 = new Number();
new Thread(()->{ n1.a();}).start();
new Thread(()->{ n2.b(); }).start();
}
}
class Number{
public static synchronized void a() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a()");
}
public static synchronized void b() {
System.out.println("b()");
}
}
结果1:(先执行第一个线程)
a()
b()
结果2:(先执行第二个线程)
b()
a()
正常来讲,静态方法是有类对象调用的,而不是由类的实例对象调用的(在面向对象编程中,静态方法(static method)是属于类本身的,而不是属于类的某个实例对象。)
- Java语法允许通过实例对象调用静态方法,这不会引发编译错误或运行时错误。
- 实例对象调用静态方法时,不会隐式地传递实例对象(即 this 引用),静态方法内无法直接访问实例属性或实例方法(除非通过其他方式获取实例对象的引用)。