Monitor 是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。
其结构如下:
-
Owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL。
-
EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程。
-
RcThis:表示blocked或waiting在该monitor record上的所有线程的个数。
-
Nest:用来实现重入锁的计数。HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)。
-
Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值,0表示没有需要唤醒的线程,1表示要唤醒一个继任线程来竞争锁。
============================================================================
synchronized是Java中的关键字,是一种同步锁,它修饰的对象有以下几种:
| 序号 | 类别 | 作用范围 | 作用对象 |
| — | — | — | — |
| 1 | 同步代码块 | 被synchronized修饰的代码块 | 调用这个代码块的单个对象 |
| 2 | 同步方法 | 被synchronized修饰的方法 | 调用该方法的单个对象 |
| 3 | 同步静态方法 | 被synchronized修饰的静态方法 | 静态方法所属类的所有对象 |
| 4 | 同步类 | 被synchronized修饰的代码块 | 该类的所有对象 |
同步代码块就是将需要的同步的代码使用同步锁包裹起来,这样能减少阻塞,提高程序效率。
同步代码块格式如下:
synchronized(对象){
同步代码;
}
同样对于文章开头卖票的例子,进行线程安全改造,代码如下:
public class SellTickets {
public static void main(String[] args) {
TicketSeller seller = new TicketSeller();
Thread t1 = new Thread(seller, "窗口1");
Thread t2 = new Thread(seller, "窗口2");
Thread t3 = new Thread(seller, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
class TicketSeller implements Runnable {
private static int tickets = 100;
@Override
public void run() {
while (true) {
synchronized (this) {
try {
Thread.sleep(10);
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
同步代码块的关键在于锁对象,多个线程必须持有同一把锁,才会实现互斥性。
将上面代码中的 synchronized (this) 改为 synchronized (new Objcet()) 的话,线程安全将得不到保证,因为两个线程的持锁对象不再是同一个。
又比如下面这个例子:
public class SyncTest implements Runnable {
// 共享资源变量
int count = 0;
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
// test1();
test2();
}
public static void test1() {
SyncTest syncTest1 = new SyncTest();
Thread thread1 = new Thread(syncTest1, "thread-1");
Thread thread2 = new Thread(syncTest1, "thread-2");
thread1.start();
thread2.start();
}
public static void test2() {
SyncTest syncTest1 = new SyncTest();
SyncTest syncTest2 = new SyncTest();
Thread thread1 = new Thread(syncTest1, "thread-1");
Thread thread2 = new Thread(syncTest2, "thread-2");
thread1.start();
thread2.start();
}
}
从输出结果可以看出,test2() 方法无法实现线程安全,原因在于我们指定锁为this,指的就是调用这个方法的实例对象,然而 test2() 实例化了两个不同的实例对象 syncTest1,syncTest2,所以会有两个锁,thread1与thread2分别进入自己传入的对象锁的线程执行 run() 方法,造成线程不安全。
如果要使用这个经济实惠的锁并保证线程安全,那就不能创建出多个不同实例对象。如果非要想 new 两个不同对象出来,又想保证线程同步的话,那么 synchronized 后面的括号中可以填入SyncTest.class,表示这个类对象作为锁,自然就能保证线程同步了。
synchronized(xxxx.class){
//todo
}
一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。
例如下面的例子:
public class SyncTest {
public static void main(String[] args) {
Counter counter = new Counter();
Thread thread1 = new Thread(counter, "线程-1");
Thread thread2 = new Thread(counter, "线程-2");
thread1.start();
thread2.start();
}
}
class Counter implements Runnable {
private int count = 0;
public void countAdd() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + " 同步计数:" + (count++));
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void printCount() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + " 非同步输出:" + count);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void run() {
String threadName = Thread.currentThread().getName();
if (threadName.equals("线程-1")) {
countAdd();
} else if (threadName.equals("线程-2")) {
printCount();
}
}
}
我们也可以用synchronized 给对象加锁。这时,当一个线程访问该对象时,其他试图访问此对象的线程将会阻塞,直到该线程访问对象结束。也就是说谁拿到那个锁谁就可以运行它所控制的那段代码,,例如下例:
public class SyncTest {
public static void main(String args[]) {
Account account = new Account("zhang san", 10000.0f);
AccountOperator accountOperator = new AccountOperator(account);
final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i++) {
threads[i] = new Thread(accountOperator, "Thread-" + i);
threads[i].start();
}
}
}
class Account {
String name;
double amount;
public Account(String name, double amount) {
this.name = name;
this.amount = amount;
}
//存钱
public void deposit(double amt) {
amount += amt;
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取钱
public void withdraw(double amt) {
amount -= amt;
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public double getBalance() {
return amount;
}
}
class AccountOperator implements Runnable {
private Account account;
public AccountOperator(Account account) {
this.account = account;
}
public void run() {
synchronized (account) {
String name = Thread.currentThread().getName();
account.deposit(500);
System.out.println(name + "存入500,最新余额:" + account.getBalance());
account.withdraw(400);
System.out.println(name + "取出400,最新余额:" + account.getBalance());
System.out.println(name + "最终余额:" + account.getBalance());
}
}
}
同步锁可以使用任意对象作为锁,当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:
class Test implements Runnable {
private byte[] lock = new byte[0]; // 特殊的instance变量
public void method() {
synchronized(lock) {
// todo 同步代码块
}
}
public void run() {
}
}
Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。
public synchronized void method(){
// todo
}
下面用同步函数的方式解决售票场景的线程安全问题,代码如下:
public class SellTickets {
public static void main(String[] args) {
TicketSeller seller = new TicketSeller();
Thread t1 = new Thread(seller, "窗口1");
Thread t2 = new Thread(seller, "窗口2");
Thread t3 = new Thread(seller, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
class TicketSeller implements Runnable {
private static int tickets = 100;
@Override
public void run() {
while (true) {
sellTickets();
}
}
public synchronized void sellTickets() {
try {
Thread.sleep(10);
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
同步方法有以下特征:
线程、数据库、算法、JVM、分布式、微服务、框架、Spring相关知识
一线互联网P7面试集锦+各种大厂面试集锦
学习笔记以及面试真题解析
true) {
sellTickets();
}
}
public synchronized void sellTickets() {
try {
Thread.sleep(10);
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
同步方法有以下特征:
#### 线程、数据库、算法、JVM、分布式、微服务、框架、Spring相关知识
[外链图片转存中...(img-TB5IlpFA-1720021138314)]
#### 一线互联网P7面试集锦+各种大厂面试集锦
[外链图片转存中...(img-81qzO9mX-1720021138314)]
#### 学习笔记以及面试真题解析
[外链图片转存中...(img-5ngyDdZs-1720021138315)]