java多线程-synchronized实现(四)

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总结

  1. synchronized有对象锁和类锁,他们是不同的锁;类锁对所有该类产生的对象都是同一把锁,而对象锁则只对同一个对象才为同一把锁。
Class ThreadA
public void service(){
    synchronized(Object obj){//obj对象锁
    }

    synchronized(“123”){//常量锁
    }
}
public void serviceA(){
    synchronized(ThreadA.class){//类锁
    }
}

synchronized  static public void serviceA(){//类锁

}
  1. 只要是同一把锁,不管在静态方法,静态锁,内部类,方法中都将起到堵塞作用,使其具有同步性。
  2. 加了synchronized的地方为同步,没有synchronized的地方为异步。
  3. synchronized 具有继承性,如果父类中的方法A加了synchronized,子类A方法中也加了synchronized,那么他们是同一把锁,这个就是synchronized继承性。如果父类A方法加了synchronized,而子类方法A没加synchronized,则子类方法A不具有同步性。
  4. null对象不能作为synchronized的锁对象,因为synchronized是通过操作对象的对象头来实现的,null没有对象头,所以不能作为锁。

  5. 对于同步方法,锁是当前实例对象。

  6. 对于静态同步方法或者Object.class锁,锁是类锁,全局锁,锁住的是所有对象,对所有对象来说是同一把锁。
  7. 对于同步方法块,锁是Synchonized括号里配置的对象。
  8. 互斥就是A线程获取锁后,排斥B线程,从而达到A,B线程同步。
  9. Synchronized 是非公平锁;公平锁是A,B按照时间顺序获取锁,而Synchronized是不确定时间顺序,就是A,B不知道谁会先获取锁。
  10. A线程先获取锁后,B线程会线程挂起释放CPU,当A解锁后,B才会恢复线程从新获取到锁,获取CPU资源。B在这个过程中从线程挂起,到恢复经过了用户态线程到内核态线程之间的切换所有非常耗时,所有jvm团队优化了一下B线程等待锁的过程比如使用自旋锁,自适应自旋锁。自旋锁就是在合理的情况下不用线程挂起,而是让线程自我循环一下,类似重试机制,看看能不能获取锁,A线程只要解锁,B线程就立马能获取锁,这样B就不用线程挂起在恢复了。注意,这种自旋锁只有在多核情况下才有用,A占用一个CPU资源在执行任务,B也同时占用另外一个CPU资源,只是展示无法执行任务,需等待A线程通知解锁了。

猜你喜欢

转载自blog.csdn.net/piaoslowly/article/details/81475999