本文主要介绍了Synchronized和Lock的区别,具有很好的参考价值。希望对大家有帮助。来和边肖一起看看吧。
如下所示:
Synchronized是内置的java关键字,Lock是java类。
Synchronized不能判断锁是否被获取,但是Lock可以判断锁是否被获取。
Synchronized会自动释放锁,锁必须手动释放锁。
同步线程1在获得锁后阻塞,等待锁的线程2将永远等待(die等。).洛克不一定会死。
同步重入锁,不可中断和不公平锁。锁可以重新进入,可中断与否,公平与否。
Synchronized适用于锁定少量代码同步问题。Lock适用于锁定大量同步码。
补充知识:java synchronized关键字的用法以及锁的等级:方法锁、对象锁、类锁
首先我们来解释一下:方法锁和对象锁是一回事,也就是只有两种锁:方法锁或者对象锁和类锁。
在java编程中,经常需要同步,也许最常用的关键词就是synchronized。我们来看看这个关键词的用法。
因为synchronized关键字涉及到锁的概念,所以我们先学习一些相关的锁知识。
java的内置锁:每个Java对象都可以作为同步的锁,这些锁就变成了内置锁。当线程进入同步代码块或方法时,它将自动获取锁,当它退出同步代码块或方法时,它将释放锁。获得内置锁的唯一方法是输入受该锁保护的同步代码块或方法。
Java内置锁是互斥锁,也就是说最多只有一个线程可以获得这个锁。当线程A试图获取线程B持有的内置锁时,线程A必须等待或阻塞,直到线程B释放这个锁。如果线程B不释放这个锁,线程A将永远等待。
java的对象锁和类锁:Java的对象锁和类锁在锁的概念上和内置锁基本相同。然而,这两把锁实际上有很大的不同。对象锁用于对象实例方法或对象实例,类锁用于类的静态方法或类的类对象。我们知道,一个类可以有很多对象实例,但是每个类只有一个类对象,所以不同对象实例的对象锁互不干扰,但是每个类只有一个类锁。但必须注意的是,其实类锁只是一个概念上的东西,并不是真正的东西。它只是用来帮助我们理解锁定实例方法和静态方法之间的区别。
已经了解了锁的一些概念,让我们来讨论synchronized关键字的用法。
synchronized的用法:synchronized修饰方法和synchronized修饰代码块。
下面分别分析这两种用法对对象锁和类锁的影响。
对象锁的synchronized修饰方法和代码块:
公共类TestSynchronized
{
公共void test1()
{
同步(此)
{
int I=5;
while( i - 0)
{
system . out . println(thread . current thread()。getName()':' I);
尝试
{
thread . sleep(500);
}
catch(中断异常ie)
{
}
}
}
}
公共同步void test2()
{
int I=5;
while( i - 0)
{
system . out . println(thread . current thread()。getName()':' I);
尝试
{
thread . sleep(500);
}
catch(中断异常ie)
{
}
}
}
公共静态void main(String[] args)
{
final test synchronized myt 2=new test synchronized();
Thread test1=new Thread(new Runnable(){ public void run(){ my T2 . test1();} },' test1 ');
Thread test2=new Thread(new Runnable(){ public void run(){ myt 2 . test2();} },' test 2 ');
test1 . start();
test 2 . start();
//TestRunnable tr=new TestRunnable();
//Thread test3=新线程(tr);
//test 3 . start();
}
}
测试2 : 4
测试2 : 3
测试2 : 2
测试2 : 1
测试2 : 0
测试1 : 4
测试1 : 3
测试1 : 2
测试1 : 1
测试1 : 0
上面的代码,第一种方法使用同步代码块的方法来同步,传入的对象实例是这个,表示是当前对象。当然,如果需要同步其他对象实例,就不能传入其他对象实例;第二种方法是修改同步方法。因为这是由第一个同步代码块传入的,所以两个同步代码所需的对象锁都是相同的对象锁。在下面的main方法中,分别打开两个线程,分别调用test1和test2方法,所以两个线程都需要获取对象锁,另一个线程要等待。上面也给出了运行的结果。如您所见,test1线程直到test2线程完成执行并释放锁后才开始执行。(也许有人会对这个结果产生怀疑。代码显然首先启动了test1线程。它为什么先执行test2?这是因为java编译器在将代码编译成字节码时会对代码进行重新排序,也就是说,编译器会根据实际情况对代码进行合理排序。编译前代码写在前面,但是编译后字节码不一定排在前面,所以这个运行结果很正常。这里说个题外话,最重要的是检查同步用法的正确性)
如果我们删除test2方法的synchronized关键字,执行结果会怎样?
测试1 : 4
测试2 : 4
测试2 : 3
测试1 : 3
测试1 : 2
测试2 : 2
测试2 : 1
测试1 : 1
测试2 : 0
测试1 : 0
以上是执行结果。我们可以看到,结果的输出是交替输出的。这是因为一个线程获得了对象锁,但另一个线程仍然可以访问未同步的方法或代码。同步方法(锁定方法)和非同步方法(普通方法)互不影响。当一个线程进入同步方法并获得对象锁时,其他线程仍然可以访问那些未同步的方法(普通方法)。这就涉及到一个内建锁的概念(这个概念来自《java并发编程》第二章):一个对象的内建锁和对象的状态之间没有内在联系。虽然大多数类使用内置锁作为有效的锁定机制,但是对象的域不一定受内置锁的保护。当获得与对象相关联的内置锁时,它不能阻止其他线程访问该对象。当一个线程获得对象的锁时,它只能阻止其他线程获得相同的锁。每个对象都有内置锁的原因是为了避免显式创建锁对象。
因此,synchronized只是内置锁的一种锁定机制。当一个方法添加了synchronized关键字时,意味着只有获取了内置锁才能执行,并且不能阻止其他线程访问不需要获取内置锁的方法。
类锁的修饰(静态)方法和代码块:
公共类TestSynchronized
{
公共void test1()
{
已同步(TestSynchronized.class)
{
int I=5;
while( i - 0)
{
system . out . println(thread . current thread()。getName()':' I);
尝试
{
thread . sleep(500);
}
catch(中断异常ie)
{
}
}
}
}
公共静态同步void test2()
{
int I=5;
while( i - 0)
{
system . out . println(thread . current thread()。getName()':' I);
尝试
{
thread . sleep(500);
}
catch(中断异常ie)
{
}
}
}
公共静态void main(String[] args)
{
final test synchronized myt 2=new test synchronized();
Thread test1=new Thread(new Runnable(){ public void run(){ my T2 . test1();} },' test1 ');
Thread test 2=new Thread(new Runnable(){ public void run(){ test synchronized . test 2();} },' test 2 ');
test1 . start();
test 2 . start();
//TestRunnable tr=new TestRunnable();
//Thread test3=新线程(tr);
//test 3 . start();
}
}
测试1 : 4
测试1 : 3
测试1 : 2
测试1 : 1
测试1 : 0
测试2 : 4
测试2 : 3
测试2 : 2
测试2 : 1
测试2 : 0
其实类锁装饰方法和代码块的效果和对象锁是一样的,因为类锁只是一个抽象的概念,只是为了区分静态方法的特性。因为静态方法由所有对象实例共享,所以对应于同步修饰静态方法的锁是唯一的,所以抽象了一个类锁。事实上,这里的重点是下面的代码,它同步了静态和非静态方法。
公共类TestSynchronized
{
公共同步void test1()
{
int I=5;
while( i - 0)
{
system . out . println(thread . current thread()。getName()':' I);
尝试
{
thread . sleep(500);
}
catch(中断异常ie)
{
}
}
}
公共静态同步void test2()
{
int I=5;
while( i - 0)
{
system . out . println(thread . current thread()。getName()':' I);
尝试
{
thread . sleep(500);
}
catch(中断异常ie)
{
}
}
}
公共静态void main(String[] args)
{
final test synchronized myt 2=new test synchronized();
Thread test1=new Thread(new Runnable(){ public void run(){ my T2 . test1();} },' test1 ');
Thread test 2=new Thread(new Runnable(){ public void run(){ test synchronized . test 2();} },' test 2 ');
test1 . start();
test 2 . start();
//TestRunnable tr=new TestRunnable();
//Thread test3=新线程(tr);
//test 3 . start();
}
}
测试1 : 4
测试2 : 4
测试1 : 3
测试2 : 3
测试2 : 2
测试1 : 2
测试2 : 1
测试1 : 1
测试1 : 0
测试2 : 0
上面同步的代码修改了静态方法和实例方法,但是运行结果是交替的,证明了类锁和对象锁是两个不同的锁,控制不同的区域,互不干扰。同样,当一个线程获取一个对象锁时,也可以获取这种锁,即可以同时获取两个锁,这是允许的。
至此,我们对synchronized的用法有了一定的了解。这时,有一个问题。既然有同步的方式来修饰方法,为什么还需要同步的方式来修饰同步的代码块呢?而这个问题也是synchronized的缺陷。
Synchronized缺陷:当一个线程进入同步方法获取对象的锁时,其他线程在访问此处对象的同步方法时必须等待或阻塞,这对于高并发系统来说是致命的,它很容易导致系统崩溃。如果一个线程在同步方法中有一个无限循环,它将永远不会释放对象锁,其他线程将不得不永远等待。这是一个致命的问题。
当然,无论是同步方法还是同步代码块都存在这样的缺陷,只要使用了synchronized关键字,就会存在这样的风险和缺陷。既然这种缺陷无法避免,那就要把风险降到最低。这也是同步代码块在某些情况下优于同步方法的方面。比如在一个类的方法中:这个类声明了一个对象实例,synobjectso=new synobject();此实例的方法so.testsy()在方法中调用;但是调用这个方法需要同步,多个线程不能同时调用这个方法。
此时,用同步装饰直接调用if so . testsy();代码,那么当一个线程进入这个方法时,这个对象的其他同步方法就不能被其他线程访问。如果这个方法执行时间长,其他线程会一直阻塞,影响系统性能。
此时,如果代码块用synchronized:synchronized(so){ so . testsy();},那么这个方法锁定的对象就是如此,与执行这行代码的对象无关。当一个线程执行这个方法时,它对其他同步方法没有影响,因为它们持有完全不同的锁。
不过这里有个特例,就是上面演示的第一个例子。同步对象锁同时修改方法和代码块,也能体现同步代码块的优越性。如果test1方法的同步代码块后面有很多不同步的代码,并且有一个10万的循环,这就导致了test1方法的执行时间非常长。然后,如果synchronized方法是直接修饰的,那么在该方法完成之前,其他线程不能访问test2方法,但是如果使用synchronized代码块,那么当代码块退出时,对象锁将被释放,其他线程已经可以访问test2方法,而该线程仍然在执行test1的100,000次循环。这使得阻塞或线程的机会更少。让系统的性能更优越。
一个类的对象锁与另一个类的对象锁无关。当一个线程获得A类的对象锁时,它也可以获得b类的对象锁。
可能上面只有理论和代码,对刚接触的人比较难理解,下面举一个例子,
举个例子,一个物体就像一个大房子,门总是开着的。房子里有很多房间(也就是方法)。
这些房间被锁定(同步方法)和解锁(普通方法)。门口有一把钥匙,可以打开所有上锁的房间。
另外,我把所有想调用这个对象的方法的线程,和想进入这个房子的一个房间的人进行比较。仅此而已。让我们看看这些东西是如何工作的。
在这里,我们先明确自己的前提条件。这个对象至少有一个synchronized方法,不然这个键有什么意义?当然,不会有我们的主题。
一个男人想进入一个上锁的房间。他来到房子门口,看到钥匙在那里(表明还没有人想使用锁着的房间)。所以他上去拿了钥匙,按照他的计划使用房间。注意,他每次使用完上锁的房间后都会立即归还钥匙。即使他想连续使用两个上锁的房间,他也必须归还钥匙并在中途取回。
所以一般情况下,钥匙的使用原则是:“用了就借,用了就还。”
此时,其他人可以不受限制地使用未锁定的房间。一个人可以用一个房间,两个人可以用一个房间。没有限制。但是如果有人想进入一个锁着的房间,他必须跑到大门口去看一看。如果你有钥匙,当然,拿着它离开。如果没有,就只能等。
如果很多人都在等这把钥匙,那么钥匙还回来的时候谁会先拿到?不保证.就像上一个例子中的家伙,他想连续使用两个锁着的房间,如果他归还钥匙时有人在等着,就不能保证这个家伙会再次得到它。(JAVA规范中明确说明了很多地方没有保证,比如Thread.sleep()在一次中断后多长时间恢复运行,相同优先级的线程会先执行,等待池中的多个线程哪个会在被访问对象的锁被释放时获得优先级等等。我认为最终的决定是由JVM做出的。之所以不能保证,是因为JVM在做上述决定时,并不是简单地基于一个条件,而是基于多个条件。因为判断条件太多,可能会影响JAVA的推广,也可能是出于知识产权的保护。孙给了一个不保证,蒙混过关。没毛病。但我相信这些不确定性并不是完全不确定的。因为电脑本身是按照指令运行的。即使看起来是随机的,也是有规律可寻的。学过计算机的人都知道,计算机中随机数的学名是伪随机,是人用某种方式写出来的。只是看起来很随意。另外,可能是因为太麻烦了,做起来没有太大意义,所以不确定就不确定。)
再来看看同步代码块。和同步方法有小小的不同。
1.就大小而言,同步代码块比同步方法小。你可以把同步代码块想象成一个未上锁的房间里被上锁的屏幕隔开的空间。
2.同步代码块也可以人为地指定某个其他对象的密钥。就像指定用哪把钥匙可以打开这个屏幕的锁一样,可以用这个房间的钥匙;也可以用别的房子的钥匙打开。在这种情况下,你必须跑到另一个房子去拿那把钥匙,并用那把房子的钥匙打开这个房子的锁定屏幕。
记住你获得的另一间房子的钥匙不影响其他人进入那间房子未上锁的房间。
为什么要使用同步代码块?我觉得应该是这样的:首先程序的同步部分影响运行效率,一种方法通常是先创建一些局部变量,然后对这些变量做一些操作,比如操作、显示等等;代码同步覆盖的越多,对效率的影响就越严重。所以我们通常会尽量减少它的影响范围。
怎么做?同步代码块。我们只同步方法中应该同步的部分,比如操作。
此外,同步代码块可以指定键的特性还有一个额外的优点,即它可以在一定时间内占用一个对象的键。记住一般情况下使用钥匙的原则。现在不是一般的情况了。您获得的密钥并不总是返回,而是仅在您退出同步代码块时返回。
用之前那个想连续用两个锁着的房间的家伙打个比方。一个房间用完后怎么继续用?使用同步代码块。先创建另一个线程,做一个同步代码块,把那个代码块的锁指向这个房子的钥匙。然后启动那个线程。只要你在进入那个代码块的时候能抓到这个房子的钥匙,你就可以一直留着它,直到你退出那个代码块。也就是说,你甚至可以遍历这个房间所有上锁的房间,甚至可以睡觉(10*60*1000),但是门口还是有1000个线程在等这个钥匙。很过瘾。
大概就是这样。
这个关于Synchronized和Lock的区别的简短讨论是边肖分享的所有内容。希望给大家一个参考,支持我们。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。