private void synMethod() {
for (int k = 0; k < 1000; k++) {
count++;
Log.v(“MyThreadText”,Thread.currentThread().getName()+" k:" +k + " count:"+count);
}
}
在上面的代码,我们可以看到我们开启了10个线程去执行count++操作,如果没有多线程的话,我们知道最后count的值一定是10000。
根据打印结果:
我们可以分析出上面十个线程都已经完成了1000次的循环,但是我们的打印并不如我们所料的是10000而是一个小于10000的值。
因为i++这个操作并不是原子性的(原子性就是操作不可中断),导致最后的结果并不是我们想要的,这就是我们所说的线程不安全。那我们如何实现线程安全呢,那就要用到我们今天的主角了,synchronized关键字。
现在我们来改造上面的代码,实现线程同步。
private synchronized void synMethod() {
for (int k = 0; k < 1000; k++) {
count++;
Log.v(“MyThreadText”,Thread.currentThread().getName()+" k:" +k + " count:"+count);
}
}
改造上面的代码后,我在看我们的打印输出:
就可以正确的得到我们想要的值了。
为什么synchronized能够实现同步?
我们知道我们写的java代码,最后都会编译class文件运行在虚拟机上(Android中编译成dex文件,dex文件是根据class文件演变而来的,去除了class文件中的冗余信息,效率更高,适合运行在移动设备上。)synchronized在经过编译后,会在同步块前后分别形成monitorenter和monitorexit这两个字节码指令。这个两个字节码呢,都需要一个reference类型参数来指明锁定和解锁的对象。
在执行monitor指令时,首先去尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了这个对象的锁,把锁的计数+1,在执行monitorexit指令时,会将计数器减一,当计数器为0时,锁就被释放了。
如果获取锁失败,那么当前线程就要阻塞,直到其他线程释放这个锁为止。
你以为到这就结束了?还没有。
下面我们再来看几段代码:
private void synMethod() {
synchronized (this){
for (int k = 0; k < 1000; k++) {
count++;
Log.v(“MyThreadText”,Thread.currentThread().getName()+" k:" +k + " count:"+count);
}
}
}
private void synMethod() {
synchronized (Test.class){
for (int k = 0; k < 1000; k++) {
count++;
Log.v(“MyThreadText”,Thread.currentThread().getName()+" k:" +k + " count:"+count);
}
}
}
private void synMethod() {
synchronized (object){
for (int k = 0; k < 1000; k++) {
count++;
Log.v(“MyThreadText”,Thread.currentThread().getName()+" k:" +k + " count:"+count);
}
}
}
可以发现我们修改了synchronized的位置,但是无一例外,上面的几种写法,都能保证我们实现同步得到正确的值。
上面几种写法唯一不同的是,我们的synchronized关键字修饰的东西不一样。我们的synchronized可以修饰方法,代码块。
具体可以细分位以下几种场景:
1.synchronized 类的实例方法;
2.synchronized 类的静态方法;
3.synchronized 类的class对象;
4.synchronized this(当前类的实例);
5.synchronized obj(任意的实例对象);
第一种场景 synchronized 关键字锁住的是当前类的实例对象。
第二种场景 synchronized 关键字锁住的当前的类对象。
第三种场景 synchronized 关键字锁住的还是当前的类对象。
第四种场景 锁住的当前类的实例。
第五种场景 锁住的任意的实例对象。
注意如果锁主的是类对象的话,尽管new出多个实例,但他们仍然属于同一个类依然会被锁住。
以上五种场景我们主要分为三种情况去讨论:
1锁住当前类的实例,2锁住当前类的类对象,3锁住其他对象的实例
这三种情况有什么区别呢?
下面我们来看代码:
场景1:
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synMethod();
}
},“thread1”);
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synMethod2();
}
},“thread2”);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.v(“MyThreadText”,count+“”);
private void synMethod() {
synchronized (this){
for (int k = 0; k < 1000; k++) {
count++;
Log.v(“MyThreadText”,Thread.currentThread().getName()+" k:" +k + " count:"+count);
}
}
}
private synchronized void synMethod2() {
for (int k = 0; k < 1000; k++) {
count++;
Log.v(“MyThreadText”,Thread.currentThread().getName()+" k:" +k + " count:"+count);
}
}
场景2:
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synMethod();
}
},“thread1”);
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synMethod2();
}
},“thread2”);
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
synMethod3();
}
},“thread3”);
thread1.start();
thread2.start();
thread3.start();
try {
thread1.join();
thread2.join();
thread3.join();
} catch (InterruptedException e) {
总结
就写到这了,也算是给这段时间的面试做一个总结,查漏补缺,祝自己好运吧,也希望正在求职或者打算跳槽的 程序员看到这个文章能有一点点帮助或收获,我就心满意足了。多思考,多问为什么。希望小伙伴们早点收到满意的offer! 越努力越幸运!
金九银十已经过了,就目前国内的面试模式来讲,在面试前积极的准备面试,复习整个 Java 知识体系将变得非常重要,可以很负责任的说一句,复习准备的是否充分,将直接影响你入职的成功率。但很多小伙伴却苦于没有合适的资料来回顾整个 Java 知识体系,或者有的小伙伴可能都不知道该从哪里开始复习。我偶然得到一份整理的资料,不论是从整个 Java 知识体系,还是从面试的角度来看,都是一份含技术量很高的资料。
总结,查漏补缺,祝自己好运吧,也希望正在求职或者打算跳槽的 程序员看到这个文章能有一点点帮助或收获,我就心满意足了。多思考,多问为什么。希望小伙伴们早点收到满意的offer! 越努力越幸运!**
金九银十已经过了,就目前国内的面试模式来讲,在面试前积极的准备面试,复习整个 Java 知识体系将变得非常重要,可以很负责任的说一句,复习准备的是否充分,将直接影响你入职的成功率。但很多小伙伴却苦于没有合适的资料来回顾整个 Java 知识体系,或者有的小伙伴可能都不知道该从哪里开始复习。我偶然得到一份整理的资料,不论是从整个 Java 知识体系,还是从面试的角度来看,都是一份含技术量很高的资料。
[外链图片转存中…(img-a3X5Vdxd-1719266517433)]