多线程同步实现原理、同步函数、同步代码块、静态同步函数(关键字synchronized)的详解。
在说明同步函数和同步代码快、静态同步函数之前我们先想想:
什么是线程同步?
多线程为什么需要实现同步?
实现同步的原理是什么?
举个栗子:需求现在有100章火车票,有两个窗口同时抢火车票(相当于两个线程),请使用多线程模拟抢票效果。
代码如下:(此段代码非线程安全)。
class ThreadTrain1 implements Runnable{
private int trian1Count = 100;
@Override
public void run() {
//为了能够模拟程序一致抢票的过程
while (trian1Count>0){
try {
Thread.sleep(50);
} catch (Exception e) {
}
sale();
}
}
//模拟出出售火车票
public void sale(){
System.out.println(Thread.currentThread().getName()+"出售第"+(100-trian1Count+1)+"票");
trian1Count--;
}
}
public class ThreadDemo {
public static void main(String[] args) {
/**
* 错误模拟:应该是两个窗口同时抢票,这里产生了两个对象,意味着两个对象都有100张票,不是一个对象
* ThreadTrain1 threadTrain1 = new ThreadTrain1();
* ThreadTrain1 threadTrain2 = new ThreadTrain1();
* Thread t1 = new Thread(threadTrain1,"窗口①");
* Thread t2 = new Thread(threadTrain2,"窗口②");
* t1.start();
* t2.start();
*/
ThreadTrain1 threadTrain1 = new ThreadTrain1();
Thread t1 = new Thread(threadTrain1,"窗口①");
Thread t2 = new Thread(threadTrain1, "窗口②");
t1.start();
t2.start();
}
}
这段程序的运行结果是(中间有截掉一部分):

从结果看出,这段程序最终没有实现我们想要的结果,我们会看到卖出的票数为101张(票数总数就只有100张),这种现象叫做“超卖”现象,那问题出在哪里呢?原因是当两个进程同时访问同一个资源的时候,就会出现线程安全的问题,这里的资源相当于票数,两个线程就好比两个售票的窗口,由于每个线程执行的过程是不可控的,所以很可能导致最终的结果与实际上的愿望相违背或者直接导致程序出错。解决多线程的安全问题就需要用synchronized(同步实现)。
首先,什么叫线程同步?
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多,临界区对象就是其中一种。
多线程为什么需要实现同步?
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了该变量的原子性和准确性。
实现同步的原理是什么?
synchronized关键字在应用层的语义是可以把任何一个非null对象作为锁,当synchronized作用在方法上时,锁住的是对象实例(this),作用在静态方法上锁住的就是对象对应的Classs实例,由于Class实例存在于永久代,因此静态方法锁相当于类的一个全局锁,当synchronized作用在一个对象实例上,锁住的就是一个代码块
ps:在HotSpot JVM中 锁被称作对象监视器
当有多个线程同时请求某个对象监视器时,对象监视器会设置几种状态来区分请求的线程:
Contention List:所有请求锁的线程被首先放置在该竞争队列中
Entry List:Contention List 中有机会获得锁的线程被放置到Entry List
Wait Set:调用wait()方法被阻塞的线程被放置到Wait Set中
OnDeck:任何一个时候只能有一个线程竞争锁 该线程称作OnDeck
Owner:获得锁的线程成为Owner
!Owner:释放锁的线程
转换关系图如下:

个人对同步synchronized原理简单理解:
1.首先拿到锁,其他线程已经有cpu执行权,一直排队,等待其他线程释放锁。(没有锁获得cpu执行权也进不去)
2.锁是在什么时候释放,代码执行完毕或者抛出异常都会被释放掉。
3.锁被释放后,其他线程开始获得锁进行同步中去。
4.锁的资源竞争
5.如果一直没有释放锁,就会产生死锁问题。
synchronized同步函数:
public synchronized void synMethod(){
//方法体
}
同步代码块:
public Object synMethod(Object a1){
synchronized(a1){
//一次只能有一个线程进入
}
}
静态同步函数(模仿抢票):
class ThreadTrain2 implements Runnable{
private static int trian1Count = 100;
Object obj = new Object();
public boolean flag = true;
@Override
public void run() {
//为了能够模拟程序一致抢票的过程
if (flag) {
//执行同步代码块this锁
while (trian1Count>0) {
synchronized (ThreadTrain2.class) {
if (trian1Count>0) {
try {
Thread.sleep(50);
} catch (Exception e) {
//TODO
}
System.out.println(Thread.currentThread().getName()+"出售第"+(100-trian1Count+1)+"票");
trian1Count--;
}
}
}
}
else {
//执行同步函数
while (trian1Count>0) {
sale();
}
}
}
//模拟出出售火车票
public static synchronized void sale(){
// synchronized (obj) {
if (trian1Count>0) {
try {
Thread.sleep(50);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName()+"出售第"+(100-trian1Count+1)+"票");
trian1Count--;
}
// }
}
}
public class ThreadDemo2 {
public static void main(String[] args) throws InterruptedException {
ThreadTrain2 threadTrain2 = new ThreadTrain2();
Thread t1 = new Thread(threadTrain2,"窗口①");
Thread t2 = new Thread(threadTrain2, "窗口②");
t1.start();
Thread.sleep(40);
threadTrain2.flag=false;
t2.start();
}
}
运行结果:

解决了多线程的安全问题,分析得出结论:
1.同步函数使用的是this锁。
2.一个函数使用同步函数,另一线程使用同步代码块(非this锁)不能实现同步,因为synchronized同步函数使用的是this锁,所以只有同步代码块使用this锁才能实现同步
3.静态同步函数使用的锁是该函数所属字节码文件对象,可以使用this.getClass()方法获取,也可以用当前 类名.class表示。
静态方法中没有this对象,静态方法没有所属对象。而任何类在加载的时候都会有字节码类对象,用getClass获取。
本文深入探讨了多线程同步的必要性及其实现原理,通过抢票场景模拟,详细解释了synchronized关键字如何用于同步函数、同步代码块及静态同步函数,确保线程安全。
1万+

被折叠的 条评论
为什么被折叠?



