java多线程-synchronized实现(四)
synchronized同步
Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题。
从语法上讲,Synchronized总共有三种用法:
(1)修饰普通方法
(2)修饰静态方法
(3)修饰代码块
操作实例变量非线程安全
package cn.thread.first.syn;
class SysThreadDome {
private int num;//公共变量
//synchronized public void printlnNum(int stem)
public void printlnNum(int stem) {
if (stem == 1) {
this.num = 100;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
this.num = 200;
}
System.out.println("num====" + num);
}
}
class PrintlnNum extends Thread {
private SysThreadDome sys;
public PrintlnNum(SysThreadDome sys) {
this.sys = sys;
}
@Override
public void run() {
sys.printlnNum(1);
}
}
class PrintlnNum2 extends Thread {
private SysThreadDome sys;
public PrintlnNum2(SysThreadDome sys) {
this.sys = sys;
}
@Override
public void run() {
sys.printlnNum(2);
}
}
public class RunSysDome {
public static void main(String orgs[]) {
SysThreadDome dome = new SysThreadDome();
PrintlnNum num = new PrintlnNum(dome);
num.start();
PrintlnNum2 num2 = new PrintlnNum2(dome);
num2.start();
}
}
结果输出:
num====200
num====200
解读:printlnNum 方法操作了一个公共变量num,在多线程下为非线程安全的,如果加上synchronized,则num,num2两个线程在操作printlnNum时必须排队。
num,num2两个线程必须操作的是同一个对象,如果是两个dome对象则不会出现上述bug
多个对象多把锁
public static void main(String orgs[]) {
SysThreadDome dome = new SysThreadDome();
PrintlnNum num = new PrintlnNum(dome);
num.start();
SysThreadDome dome2 = new SysThreadDome();
PrintlnNum2 num2 = new PrintlnNum2(dome2);
num2.start();
}
结果输出:
num====100
num====200
多线程线程不安全是发生在同一个对象上面的。num操作的是dome对象,num2操作的是dome2对象,dome和dome2没有半毛钱关系。
多线程不安全发生的在多个线程操作同一个对象上面。
synchronized方法与锁对象
package cn.thread.first.syn;
class SysThreadDome {
private int num;
synchronized public void printlnNum(int stem) {
if (stem == 1) {
this.num = 100;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
this.num = 200;
}
System.out.println("num1====" + num);
}
public void printlnNum2(int stem) {
if (stem == 1) {
this.num = 100;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
this.num = 200;
}
System.out.println("num2====" + num);
}
}
class PrintlnNum extends Thread {
private SysThreadDome sys;
public PrintlnNum(SysThreadDome sys) {
this.sys = sys;
}
@Override
public void run() {
sys.printlnNum(1);
}
}
class PrintlnNum2 extends Thread {
private SysThreadDome sys;
public PrintlnNum2(SysThreadDome sys) {
this.sys = sys;
}
@Override
public void run() {
sys.printlnNum2(2);//调用第二个方法
}
}
public class RunSysDome {
public static void main(String orgs[]) {
SysThreadDome dome = new SysThreadDome();
PrintlnNum num = new PrintlnNum(dome);
num.start();
PrintlnNum2 num2 = new PrintlnNum2(dome);
num2.start();
}
}
输出结果为:
num2====200
num1====200
结论:
1. A线程先持有dome对象Lock锁,但是B线程仍然可以以异步的方式调用dome对象中的非同步方法。
2. A线程先持有dome对象Lock锁,B线程如果调用dome对象中的同步方法(synchronized标记的方法)则需要等待,也就是同步。
上面这种情况也称为“脏读”。
synchronized可重入
synchronized关键字拥有锁重入功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时也可以在此获得该对象的锁。
synchronized第一次进入的时候获取到了锁,在此进入另一个synchronized时只是状态+1,并不会获得两把synchronized锁;执行完毕一个synchronized退出时-1,只有退出最后一个synchronized时才会做释放锁操作。(Lock的可重入也是这样的)。
多次进入synchronized只会状态改变,只有在第一次进入,最后一次推出时才会发生加锁,解锁操作。
package cn.thread.first.syn;
class ThreadA extends Thread{
synchronized public void service(){
System.out.println("service1");
service2();
}
synchronized public void service2(){
System.out.println("service2");
service3();
}
synchronized public void service3(){
System.out.println("service3");
}
@Override
public void run(){
service();
}
}
class ThreadB extends ThreadA{
@Override
synchronized public void service(){
System.out.println("service1");
super.service();//锁有可以继承
}
public void service2(){
System.out.println("service2");
super.service3();//没有继承锁,需要排队,等待锁
}
public void service3(){
System.out.println("service3");
}
@Override
public void run(){
service();
}
}
public class SysDome2 {
public static void main(String orgs[]){
ThreadA threadA=new ThreadA();
threadA.start();
}
}
ThreadB 中的service2没有继承锁,以后调用的时候尽管父类加锁变成同步的了,但是子类中仍然是异步的。
ThreadB 中的service继承了锁,锁既有继承性。ThreadB获取锁后,还可以继续获取锁。
synchronized 同步语句块
上面讲的synchronized是锁住整个方法,但是有时候方法中操作时间很长,则A线程获取锁后,B需要等待很长时间。
一半同步一半不同步
package cn.thread.first.syn;
class ObjectService {
public void service() {
for (int i = 0; i < 10; i++) {//非同步
System.out.println(Thread.currentThread().getName() + "-no sys i=" + (i + 1));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (this) {//同步
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "- sys i=" + (i + 1));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Thread3A extends Thread {
private ObjectService service;
public Thread3A(ObjectService service) {
this.service = service;
}
public void run() {
service.service();
}
}
class Thread3B extends Thread {
private ObjectService service;
public Thread3B(ObjectService service) {
this.service = service;
}
public void run() {
service.service();
}
}
public class SysDome3 {
public static void main(String args[]) {
ObjectService service = new ObjectService();
Thread3A thread3A = new Thread3A(service);
thread3A.start();
Thread3B thread3b = new Thread3B(service);
thread3b.start();
}
}
输出结果:
Thread-0-no sys i=1
Thread-1-no sys i=1
Thread-0-no sys i=2
Thread-1-no sys i=2
Thread-0-no sys i=3
Thread-1-no sys i=3
Thread-0-no sys i=4
Thread-1-no sys i=4
Thread-0-no sys i=5
Thread-1-no sys i=5
Thread-0-no sys i=6
Thread-1-no sys i=6
Thread-0-no sys i=7
Thread-1-no sys i=7
Thread-0-no sys i=8
Thread-1-no sys i=8
Thread-0-no sys i=9
Thread-1-no sys i=9
Thread-0-no sys i=10
Thread-1-no sys i=10
Thread-0- sys i=1
Thread-0- sys i=2
Thread-0- sys i=3
Thread-0- sys i=4
Thread-0- sys i=5
Thread-0- sys i=6
Thread-0- sys i=7
Thread-0- sys i=8
Thread-0- sys i=9
Thread-0- sys i=10
Thread-1- sys i=1
Thread-1- sys i=2
Thread-1- sys i=3
Thread-1- sys i=4
Thread-1- sys i=5
Thread-1- sys i=6
Thread-1- sys i=7
Thread-1- sys i=8
Thread-1- sys i=9
Thread-1- sys i=10
代码块间的同步性
package cn.thread.first.syn;
class ObjectService {
public void service() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "- sys i=" + (i + 1));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void serviceB(){
synchronized (this) {//和service中的锁是同一把锁
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "- sys i=" + (i + 1));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Thread3A extends Thread {
private ObjectService service;
public Thread3A(ObjectService service) {
this.service = service;
}
public void run() {
service.service();
}
}
class Thread3B extends Thread {
private ObjectService service;
public Thread3B(ObjectService service) {
this.service = service;
}
public void run() {
service.serviceB();
}
}
public class SysDome3 {
public static void main(String args[]) {
ObjectService service = new ObjectService();
Thread3A thread3A = new Thread3A(service);
thread3A.start();
Thread3B thread3b = new Thread3B(service);
thread3b.start();
}
}
结果:先顺序输出A线程的,再顺序输出B线程的
当一个线程访问objeck对象的synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞。
A线程调用service方法遇到同步代码块,这个时候他获取的是this对象锁;B线程调用serviceB方法时遇到同步代码块,该同步代码块的锁和service代码块的锁是同一把锁,这个时候锁已经被A锁住了,只能等待A解锁后B才能拥有这把锁。
任意对象锁
....class {
private String id = "a";
public void serviceB() {
synchronized (id) {//可以定义任意对象做为锁
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "- sys i=" + (i + 1));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
接上面的代码
这个时候输出的为A线程,B线程交替打印,不再是A线程先输出,然后再B线程输出了,因为他们的锁对象不是同一个了,A线程中调用serviceA,里面的同步锁时this当前对象锁;B线程调用的是sercieB方法,里面的同步锁是id,而不是this,他们不是同一把锁,所以B可以获取锁。
说明代码块同步时:持有相同对象锁才为同步,不然为异步调用。
静态同步方法与静态同步块
package cn.thread.first.syn;
class ObjectService4 {
synchronized public static void service() { //Class锁
try {
System.out.println("threadA ...start");
Thread.sleep(100);
System.out.println("threadA ...end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void serviceB() { //对象锁,锁住当前对象
try {
System.out.println("threadB ...start");
Thread.sleep(100);
System.out.println("threadB ...end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public static void serviceC() {//Class锁,锁住的是所有该类的对象
try {
System.out.println("threadC ...start");
Thread.sleep(100);
System.out.println("threadC ...end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void serviceD() {
synchronized(ObjectService4.class){ //Class锁,和serviceC同步中的锁是同一把锁
try {
System.out.println("threadC ...start");
Thread.sleep(100);
System.out.println("threadC ...end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void serviceE(String param) {
synchronized (param) { //对象锁,对象param锁。不要看着方法里面有个static就是以为是class锁。
try {
System.out.println("threadC ...start");
Thread.sleep(100);
System.out.println("threadC ...end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Thread4A extends Thread {
private ObjectService4 service;
public Thread4A(ObjectService4 service) {
this.service = service;
}
public void run() {
ObjectService4.service();
}
}
class Thread4B extends Thread {
private ObjectService4 service;
public Thread4B(ObjectService4 service) {
this.service = service;
}
public void run() {
service.serviceB();
}
}
class Thread4C extends Thread {
private ObjectService4 service;
public Thread4C(ObjectService4 service) {
this.service = service;
}
public void run() {
ObjectService4.serviceC();
}
}
public class SysDome4 {
public static void main(String args[]) {
ObjectService4 service = new ObjectService4();
Thread4A thread4A = new Thread4A(service);
thread4A.start();
Thread4B thread4b = new Thread4B(service);
thread4b.start();
Thread4C thread4c = new Thread4C(service);
thread4c.start();
}
}
serviceB属于对象锁;serviceA,serviceC,serviceD是静态锁,也称为类锁,serviceA,serviceC,serviceD是同一把锁;在多线程中,serviceB,由于是两把不同的锁,所以B与A,C,D不会产生互斥效果。serviceA,serviceC,serviceD是同一把锁,他们会同步调用。
对象锁:这对当前对象有用。Class锁:可以对类的所有对象实例起作用。
ObjectService4 service = new ObjectService4();
Thread4A thread4A = new Thread4A(service);
thread4A.start();
Thread4B thread4b = new Thread4B(service);
thread4b.start();//A与B同一个对象,但是不是同一把锁,a是类锁,b是对象锁,所以A,B不会产生互斥
public static void main(String args[]) {
ObjectService4 objs = new ObjectService4();
Thread4A thread4A = new Thread4A(objs);//对象一
thread4A.start();
Thread4C thread4c1 = new Thread4C(objs);//
thread4c1.start();//A与C1同一个对象是同一把锁
ObjectService4 objs2 = new ObjectService4();
Thread4C thread4c = new Thread4C(objs2);//对象二
thread4c.start();//A与C是不同对象,但是他们是同一把锁
}
输出结果为A先输出,C1,C后输出,表现为同步结果。
线程A中调用的是objs这个对象,而线程C中调用的事objs2这个对象,按道理他们是不同的对象不存在多线程资源共享问题。但这里他们调用的是serviceA ,serviceC是静态锁,就是类锁,类对该类的所有对象起作用,就是objs,objs2中的静态对象锁是同一把锁。
A与C不同对象,但是是同一把锁。所以a,c互斥了。为什么A,C不同对象会是同一把锁呢?因为他们是类锁,类锁对所有对象是一把锁
注意:A,C都为类锁,对于该类的不同对象,他们都是同一把锁。要和A,B做对比区分,A是类锁,锁住了整个对象,那么B和A是同一个对象,所以A,B应该互斥嘛?不是的。首选要判断是不是锁的属性,然后在来判断对象。由于A,B不是同一把锁,所以不管A,B锁住的是不是同一个对象,他们都不会产生互斥。A,C是同一把ObjectService4类锁,所以不管是不是同一个对象,他们都是一把锁,锁住的东西都是一样的。
对于是不是同一把锁的问题?
首选要判断是不是锁的属性,是类锁还是对象锁,他们之间是互不干扰的。
如果是类锁,则A类锁,B类锁,如果锁住的是同一个类,则A,B是同一把锁,对于A,B来说不管他们的对象是不是同一个,他们都将互斥。
如果是对象锁,A,B必须都是对象锁,然后A,B都是同一个对象,这样他们才能产生互斥。
数据类型String 常量池特性
String a = "ac";
String b = "ac";
System.out.println(a == b);
System.out.println(a == b);
输出结果:
true
true
//类A中
public static void serviceE(String param) {
synchronized (param) {
try {
System.out.println("threadC ...start");
Thread.sleep(100);
System.out.println("threadC ...end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Thread4E extends Thread {
private ObjectService4 service;
public Thread4E(ObjectService4 service) {
this.service = service;
}
public void run() {
ObjectService4.serviceE("A");
}
}
class Thread4F extends Thread {
private ObjectService4 service;
public Thread4F(ObjectService4 service) {
this.service = service;
}
public void run() {
ObjectService4.serviceE("A");
}
}
由于字符串常量的问题,因此在大多数情况下,同步synchronized代码块不能使用String作为锁对象。
多线程的死锁
synchronized public void serviceA() {
while (true) {
}
}
synchronized public void serviceB() {
try {
System.out.println("threadB ...start");
Thread.sleep(100);
System.out.println("threadB ...end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
final Object object = new Object();
public void serviceC() {
synchronized (object) {
while (true) {
}
}
}
```
serviceB由于A的问题导致serviceB永远得不到锁,这个时候,可以将serviceA改为serviceB这种方式执行,使用代码块锁,用不同的锁来处理不同的业务逻辑,当然serviceA,需要与serviceB没有业务关系,不然serviceA与serviceB为不同锁了,他们仍然不能保证同步性了。
### 内部类同步
```java
package cn.thread.first.syn;
class InnerDome {
class InnerA {
public void serviceA(InnerB innerB) {
synchronized (innerB) {
for (int i = 0; i < 5; i++) {
System.out.println("1:===" + i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("1:InnerA.serviceA");
}
}
synchronized public void serviceB() {
for (int i = 0; i < 5; i++) {
System.out.println("2:===" + i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("2:InnerA.serviceB");
}
}
class InnerB {
synchronized public void serviceA() {
for (int i = 0; i < 5; i++) {
System.out.println("3:===" + i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("3:InnerB.serviceA");
}
}
}
public class SyDome6 {
public static void main(String orgs[]) {
final InnerDome innerDome = new InnerDome();
final InnerDome.InnerA innerA = innerDome.new InnerA();
final InnerDome.InnerB innerB = innerDome.new InnerB();
Thread thread1 = new Thread(new Runnable() {
public void run() {
innerA.serviceA(innerB);//innerB对象锁
}
});
thread1.start();
Thread thread2 = new Thread(new Runnable() {
public void run() {
innerA.serviceB();
}
});
thread2.start();
Thread thread3 = new Thread(new Runnable() {
public void run() {
innerB.serviceA();//调用的方法中加锁了,为对象锁,innerB对象锁,所以和thread1中的对象锁是同一把锁
}
});
thread3.start();
}
}
<div class="se-preview-section-delimiter"></div>
输出结果:
thread1,与thread3表现为同步,与thread2表示为异步。
thread1中调用的serviceA中的锁使用innerB对象作为锁,而thread3中的serviceA也是对象锁,且对象也是innerB对象,所以thread1与thread3使用的是同一把锁。thread2中的serviceB 也是对象锁,但是他的对象锁为innerA,所以表现为异步。
锁对象属性改变不影响锁
class ObjectService {
private String id = "a";
public void serviceB() {
synchronized (id) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "- sys i=" + (i + 1));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
id="b";// id属性值改变了,但是不影响外边的线程抢购锁,他们抢购的还是id=a这把锁
}
}
}
<div class="se-preview-section-delimiter"></div>
锁的属性改变了,但是不会影响锁。
synchronized总结
- synchronized有对象锁和类锁,他们是不同的锁;类锁对所有该类产生的对象都是同一把锁,而对象锁则只对同一个对象才为同一把锁。
Class ThreadA
public void service(){
synchronized(Object obj){//obj对象锁
}
synchronized(“123”){//常量锁
}
}
public void serviceA(){
synchronized(ThreadA.class){//类锁
}
}
synchronized static public void serviceA(){//类锁
}
- 只要是同一把锁,不管在静态方法,静态锁,内部类,方法中都将起到堵塞作用,使其具有同步性。
- 加了synchronized的地方为同步,没有synchronized的地方为异步。
- synchronized 具有继承性,如果父类中的方法A加了synchronized,子类A方法中也加了synchronized,那么他们是同一把锁,这个就是synchronized继承性。如果父类A方法加了synchronized,而子类方法A没加synchronized,则子类方法A不具有同步性。
null对象不能作为synchronized的锁对象,因为synchronized是通过操作对象的对象头来实现的,null没有对象头,所以不能作为锁。
对于同步方法,锁是当前实例对象。
- 对于静态同步方法或者Object.class锁,锁是类锁,全局锁,锁住的是所有对象,对所有对象来说是同一把锁。
- 对于同步方法块,锁是Synchonized括号里配置的对象。
- 互斥就是A线程获取锁后,排斥B线程,从而达到A,B线程同步。
- Synchronized 是非公平锁;公平锁是A,B按照时间顺序获取锁,而Synchronized是不确定时间顺序,就是A,B不知道谁会先获取锁。
- A线程先获取锁后,B线程会线程挂起释放CPU,当A解锁后,B才会恢复线程从新获取到锁,获取CPU资源。B在这个过程中从线程挂起,到恢复经过了用户态线程到内核态线程之间的切换所有非常耗时,所有jvm团队优化了一下B线程等待锁的过程比如使用自旋锁,自适应自旋锁。自旋锁就是在合理的情况下不用线程挂起,而是让线程自我循环一下,类似重试机制,看看能不能获取锁,A线程只要解锁,B线程就立马能获取锁,这样B就不用线程挂起在恢复了。注意,这种自旋锁只有在多核情况下才有用,A占用一个CPU资源在执行任务,B也同时占用另外一个CPU资源,只是展示无法执行任务,需等待A线程通知解锁了。