锁与并发——多线程(二)

锁与并发

一、线程安全概念

  当多个线程对同一对象的同一个实例进行操作时,出现值被修改、值不同步问题,线程锁机制就是解决线程并发时的同步问题。非线程安全是基于实例变量而言,方法内部的私有变量不在线程安全的讨论范围之内,因为方法内部的变量是私有的。

二、非线程安全举例

  从下面这个小例子可以看出当线程threadA执行过程中,password被线程threadB篡改了,造成了线程threadA输出的password为bb。

  service类

@Data
public class UnThreadSafeService {
    private String username;
    private String password;
    public void getUsernameAndPassword(String username){
        if ("AA".equals(username)){
            try {
                this.password = "aa";
                Thread.sleep(3000);
                System.out.println("usernmae = " + username + " password = " + password);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if ("BB".equals(username)){
            try {
                this.password = "bb";
                Thread.sleep(3000);
                System.out.println("usernmae = " + username + " password = " + password);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

  线程类

public class UnThreadSafeA extends Thread{
    private UnThreadSafeService service;
    public UnThreadSafeA(UnThreadSafeService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.getUsernameAndPassword("AA");
    }
}

public class UnThreadSafeB extends Thread{
    private UnThreadSafeService service;
    public UnThreadSafeB(UnThreadSafeService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.getUsernameAndPassword("BB");
    }
}

  测试类

@SpringBootTest
public class UnThreadSafeTest {
    @Test
    public static void main(String[] args) {
        try {
            UnThreadSafeService service = new UnThreadSafeService();
            UnThreadSafeA threadA = new UnThreadSafeA(service);
            threadA.start();
            Thread.sleep(1000);
            UnThreadSafeB threadB = new UnThreadSafeB(service);
            threadB.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  控制台输出结果

usernmae = AA password = bb
usernmae = BB password = bb

三、synchronized同步方法

1、线程安全的解决

  学习同步方法之前,先解决上面一节非线程安全的小例子。只需要在申明的方法前面增加synchronized关键字就可实现线程安全。

  修改service类

@Data
public class UnThreadSafeService {
    private String username;
    private String password;
    synchronized public void getUsernameAndPassword(String username){
        if ("AA".equals(username)){
            try {
                this.password = "aa";
                Thread.sleep(3000);
                System.out.println("usernmae = " + username + " password = " + password);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if ("BB".equals(username)){
            try {
                this.password = "bb";
                Thread.sleep(3000);
                System.out.println("usernmae = " + username + " password = " + password);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

  控制台输出结果

usernmae = AA password = aa
usernmae = BB password = bb

2、对象锁的概念

  synchronized线程锁是基于对象而言的,而不是基于方法。当使用多个线程同时处理多个对象,那么这些线程将以异步的方式运行。例子如下:

  修改service类

@Data
public class UnThreadSafeService {
    private String username;
    private String password;
    synchronized public void getUsernameAndPassword(String username){
        if ("AA".equals(username)){
            try {
                System.out.println("线程:" + Thread.currentThread().getName() + "开始运行," + "时间:" + System.currentTimeMillis());
                this.password = "aa";
                Thread.sleep(3000);
                System.out.println("usernmae = " + username + " password = " + password);
                System.out.println("线程:" + Thread.currentThread().getName() + "结束运行," + "时间:" + System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if ("BB".equals(username)){
            try {
                System.out.println("线程:" + Thread.currentThread().getName() + "开始运行," + "时间:" + System.currentTimeMillis());
                this.password = "bb";
                Thread.sleep(3000);
                System.out.println("usernmae = " + username + " password = " + password);
                System.out.println("线程:" + Thread.currentThread().getName() + "结束运行," + "时间:" + System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

  线程类

public class UnThreadSafeA extends Thread{
    private UnThreadSafeService service;
    public UnThreadSafeA(UnThreadSafeService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.getUsernameAndPassword("AA");
    }
}

public class UnThreadSafeB extends Thread{
    private UnThreadSafeService service;
    public UnThreadSafeB(UnThreadSafeService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.getUsernameAndPassword("BB");
    }
}

  测试类

@SpringBootTest
public class ObjSynLockTest {
    @Test
    public static void main(String[] args) {
        UnThreadSafeService service = new UnThreadSafeService();
        UnThreadSafeA threadA = new UnThreadSafeA(service);
        threadA.setName("A");
        threadA.start();
        UnThreadSafeB threadB = new UnThreadSafeB(service);
        threadB.setName("B");
        threadB.start();
    }
}

  控制台输出结果

线程:A开始运行,时间:1598427272868
线程:B开始运行,时间:1598427272869
usernmae = AA password = aa
线程:A结束运行,时间:1598427275870
usernmae = BB password = bb
线程:B结束运行,时间:1598427275870

3、非同步方法

  当一个线程持有一个对象的锁时,另外一个线程可以调用该对象的非同步方法(非cynchronized方法)。

  service类

public class UnSynService {
    synchronized public void methodA(){
        try {
            System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "开始执行methodA");
            Thread.sleep(3000);
            System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "推出执行methodA");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void methodB(){
            System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "开始执行methodB");
            System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "推出执行methodB");
    }
}

  线程类

public class UnSynThreadA extends Thread{
    private UnSynService service;
    public UnSynThreadA(UnSynService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.methodA();
    }
}

public class UnSynThreadB extends Thread {
    private UnSynService service;
    public UnSynThreadB(UnSynService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.methodB();
    }
}

  测试类

@SpringBootTest
public class UnSynTest {
    @Test
    public static void main(String[] args) {
        try {
            UnSynService service = new UnSynService();
            UnSynThreadA threadA = new UnSynThreadA(service);
            threadA.setName("A");
            threadA.start();
            Thread.sleep(1000);
            UnSynThreadB threadB = new UnSynThreadB(service);
            threadB.setName("B");
            threadB.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  控制台输出结果

线程:A在1598430351543开始执行methodA
线程:B在1598430352544开始执行methodB
线程:B在1598430352544退出执行methodB
线程:A在1598430354544退出执行methodA

4、脏读

  脏读是在线程读取实例变量的时候,实例变量的值已经被其他线程更改过。

  service类

@Data
public class DirtyReadService {
    public String username = "AA";
    public String password = "aa";
    synchronized public void setUsernameAndPassword(String username, String password) {
        try {
            this.username = username;
            Thread.sleep(3000);
            this.password = password;
            System.out.println("username = " + username + " password = " + password);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void getUsernameAndPassword(){
        System.out.println("username = " + username + " password = " + password);
    }
}

  线程类

public class DirtyReadThreadA extends Thread{
    private DirtyReadService service;
    public DirtyReadThreadA(DirtyReadService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.setUsernameAndPassword("BB","bb");
    }
}

  测试类

@SpringBootTest
public class DirtyReadTest {
    @Test
    public static void main(String[] args) {
        try {
            DirtyReadService service = new DirtyReadService();
            DirtyReadThreadA threadA = new DirtyReadThreadA(service);
            threadA.start();
            Thread.sleep(1000);
            DirtyReadThreadB threadB = new DirtyReadThreadB(service);
            service.getUsernameAndPassword();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  控制台输出结果

username = BB password = aa
username = BB password = bb

5、锁重入

  synchronized锁重入是指当一个线程得到一个对象锁后,当再次请求此对象锁时可再次获得该对象锁。当线程在一个synchronized方法内部调用其他synchronized方法时,永远可以得到锁。

  service类

public class ClassLockService {
    synchronized public static void printA(){
        try {
            System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
            Thread.sleep(3000);
            printB();
            System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    synchronized public static void printB(){
        try {
            System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
            Thread.sleep(3000);
            System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

  测试类

@SpringBootTest
public class SynRepeatTest {
    @Test
    public static void main(String[] args) {
        try {
            ClassLockService service = new ClassLockService();
            Thread threadA = new Thread(){
                @Override
                public void run() {
                    service.printA();
                }
            };
            threadA.setName("A");
            threadA.start();
            Thread.sleep(1000);
            Thread threadB = new Thread(){
                @Override
                public void run() {
                    service.printB();
                }
            };
            threadB.setName("B");
            threadB.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  控制台输出结果

线程名称为:A在1598439196945进入printA
线程名称为:A在1598439199949进入printB
线程名称为:A在1598439202949离开printB
线程名称为:A在1598439202949离开printA
线程名称为:B在1598439202949进入printB
线程名称为:B在1598439205949离开printB

6、synchronized不具有继承性

  当子类继承了父类的同步方法,子类没有申明同步时,此时子类的方法将不具有同步性。因此,synchronized不具有继承性。

  父子类

public class Main {
    synchronized public void inheritMethod(){
        try {
            System.out.println("当前线程:" + Thread.currentThread().getName() + "进入父类inheritMethod方法时间:" + System.currentTimeMillis());
            Thread.sleep(3000);
            System.out.println("当前线程:" + Thread.currentThread().getName() + "退出父类inheritMethod方法时间:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Sub extends Main {
    @Override
    public void inheritMethod() {
        try {
            System.out.println("当前线程:" + Thread.currentThread().getName() + "进入子类inheritMethod方法时间:" + System.currentTimeMillis());
            Thread.sleep(3000);
            System.out.println("当前线程:" + Thread.currentThread().getName() + "退出子类inheritMethod方法时间:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  测试类

@SpringBootTest
public class SynInheritTest {
    @Test
    public static void main(String[] args) {
        try {
            Sub sub = new Sub();
            Thread threadA = new Thread(){
                @Override
                public void run() {
                    sub.inheritMethod();
                }
            };
            threadA.setName("A");
            threadA.start();
            Thread.sleep(1000);
            Thread threadB = new Thread(){
                @Override
                public void run() {
                    sub.inheritMethod();
                }
            };
            threadB.setName("B");
            threadB.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  控制台输出结果

当前线程:A进入子类inheritMethod方法时间:1598440920606
当前线程:B进入子类inheritMethod方法时间:1598440921606
当前线程:A退出子类inheritMethod方法时间:1598440923606
当前线程:B退出子类inheritMethod方法时间:1598440924608

  子类申明同步解决同步问题

  子类继承了父类的同步方法,如果子类的方法也要同步,则子类也需要申明同步。如下运行结果可以看出AB两个线程以同步的方式运行。

public class Sub extends Main {
    @Override
    synchronized public void inheritMethod() {
        try {
            System.out.println("当前线程:" + Thread.currentThread().getName() + "进入子类inheritMethod方法时间:" + System.currentTimeMillis());
            Thread.sleep(3000);
            System.out.println("当前线程:" + Thread.currentThread().getName() + "退出子类inheritMethod方法时间:" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  子类申明同步后,控制台输出结果

当前线程:A进入子类inheritMethod方法时间:1598441109382
当前线程:A退出子类inheritMethod方法时间:1598441112382
当前线程:B进入子类inheritMethod方法时间:1598441112382
当前线程:B退出子类inheritMethod方法时间:1598441115383

四、synchronized同步语句块

  当一个线程调用对象中一个需要比较长时间处理的同步方法时,其他线程就需要进行长时间的等待,等待该线程释放锁后其他线程才能获得这个对象的锁。这样可以使用synchronized同步语句块解决,synchronized方法是对当前对象加锁,synchronized代码块是对某个对象加锁。也就是说,当一个线程访问对象一个方法内部的同步代码块时,其他线程仍然可以访问这个方法的非同步代码块部分的代码。

  值得说明的是,在synchronized代码块中的程序时通过不执行,不在synchronized代码块中的程序时异步执行。从以下这个例子可以看出,使用了同步代码块后,AB两个线程的总用时变短了。

1、同步方法问题例举

  service类

public class SynQuestionService {
    synchronized public void methodA(){
        try {
            System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "开始运行methodA方法!");
            Thread.sleep(3000);
            System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "退出运行methodA方法!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  时间记录工具类

public class TimeUtil {
    public static long beginTime1;
    public static long beginTime2;
    public static long endTime1;
    public static long endTime2;
}

  线程类

public class SynQuestionThreadA extends Thread{
    private SynQuestionService service;
    public SynQuestionThreadA(SynQuestionService service) {
        this.service = service;
    }
    @Override
    public void run() {
        super.run();
        TimeUtil.beginTime1 = System.currentTimeMillis();
        service.methodA();
        TimeUtil.endTime1 = System.currentTimeMillis();
    }
}

public class SynQuestionThreadB extends Thread{
    private SynQuestionService service;
    public SynQuestionThreadB(SynQuestionService service) {
        this.service = service;
    }
    @Override
    public void run() {
        super.run();
        TimeUtil.beginTime2 = System.currentTimeMillis();
        service.methodA();
        TimeUtil.endTime2 = System.currentTimeMillis();
    }
}

  测试类

@SpringBootTest
public class SynQuestionTest {
    @Test
    public static void main(String[] args) {
        SynQuestionService service = new SynQuestionService();
        SynQuestionThreadA threadA = new SynQuestionThreadA(service);
        threadA.setName("A");
        threadA.start();
        SynQuestionThreadB threadB = new SynQuestionThreadB(service);
        threadB.setName("B");
        threadB.start();
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long beginTime = TimeUtil.beginTime1;
        if (TimeUtil.beginTime1 > TimeUtil.beginTime2){
            beginTime = TimeUtil.beginTime2;
        }
        long endTime = TimeUtil.endTime1;
        if (TimeUtil.endTime1 < TimeUtil.endTime2){
            endTime = TimeUtil.endTime2;
        }
        System.out.println("A线程开始时间:" + TimeUtil.beginTime1 + ";A线程结束时间:" + TimeUtil.endTime1
                + ";B线程开始时间:" + TimeUtil.beginTime2 + ";B线程结束时间:" + TimeUtil.endTime2);
        System.out.println("beginTime=" + beginTime + " endTime=" + endTime);
        System.out.println("总耗时:" + (endTime - beginTime)/1000);
    }
}

  控制台输出结果

线程:A在1598492837397开始运行methodA方法!
线程:A在1598492840398退出运行methodA方法!
线程:B在1598492840398开始运行methodA方法!
线程:B在1598492843398退出运行methodA方法!
A线程开始时间:1598492837396;A线程结束时间:1598492840398;B线程开始时间:1598492837397;B线程结束时间:1598492843398
beginTime=1598492837396 endTime=1598492843398
总耗时:6

  修改service方法,改用同步代码块

  当将同步方法改为同步代码块后,非同步代码块的程序将以异步的形式运行,因此大大减少了程序的运行时间,提高程序运行效率。将原来需要6s的运行时常缩短为3s。

public class SynQuestionService {
    public void methodA(){
        try {
            System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "开始运行methodA方法!");
            Thread.sleep(3000);
            synchronized(this) {
                System.out.println("线程:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "退出运行methodA方法!");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  使用同步代码块后,控制台输出结果

线程:A在1598492894091开始运行methodA方法!
线程:B在1598492894091开始运行methodA方法!
线程:A在1598492897092退出运行methodA方法!
线程:B在1598492897092退出运行methodA方法!
A线程开始时间:1598492894090;A线程结束时间:1598492897092;B线程开始时间:1598492894090;B线程结束时间:1598492897092
beginTime=1598492894090 endTime=1598492897092
总耗时:3

2、同步代码块锁定对象

  与synchronzied同步方法类似,synchronized(this)同步代码块时对this对象加锁。

  任务类

public class TaskA {
    synchronized public void methodA(){
        System.out.println("-------------运行methodA方法---------------");
    }
    public void methodB(){
        synchronized(this){
            for (int i = 0; i < 1000; i++){
                System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + i + ";");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  线程类

public class SynObjThreadA extends Thread{
    private TaskA task;
    public SynObjThreadA(TaskA task) {
        this.task = task;
    }
    @Override
    public void run() {
        super.run();
        task.methodA();
    }
}

public class SynObjThreadB extends Thread{
    private TaskA task;
    public SynObjThreadB(TaskA task) {
        this.task = task;
    }
    @Override
    public void run() {
        super.run();
        task.methodB();
    }
}

  测试类

@SpringBootTest
public class SynObjTest {
    @Test
    public static void main(String[] args) {
        TaskA task = new TaskA();
        SynObjThreadB threadB = new SynObjThreadB(task);
        threadB.setName("B");
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        SynObjThreadA threadA = new SynObjThreadA(task);
        threadA.setName("A");
        threadA.start();
    }
}

  控制台输出结果

...
线程:B;i = 7;
线程:B;i = 8;
线程:B;i = 9;
-------------运行methodA方法---------------
线程:B;i = 10;
线程:B;i = 11;
线程:B;i = 12;
...

  将methodA方法添加同步
  将methodA方法添加同步后,threadA将一直处于阻塞状态,没有获取锁,没有得到运行机会。

public class TaskA {
    synchronized public void methodA(){
        System.out.println("-------------运行methodA方法---------------");
    }
    public void methodB(){
        synchronized(this){
            for (int i = 0; i < 1000; i++){
                System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + i + ";");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  控制台输出结果

线程:B;i = 0;
线程:B;i = 1;
线程:B;i = 2;
线程:B;i = 3;
线程:B;i = 4;
线程:B;i = 5;
线程:B;i = 6;
线程:B;i = 7;
线程:B;i = 8;
线程:B;i = 9;
线程:B;i = 10;
线程:B;i = 11;
线程:B;i = 12;
线程:B;i = 13;
线程:B;i = 14;
线程:B;i = 15;
线程:B;i = 16;
线程:B;i = 17;
线程:B;i = 18;
线程:B;i = 19;
线程:B;i = 20;
...

3、对象监听器,非this锁

  使用非this锁可以提高代码运行效率,因为使用同步代码块锁非this对象,synchronzied(非this)代码块中的程序与同步方法时异步运行的。

代码略...

4、同步方法的脏读与同步代码块的解决

  使用不同方法时,如遇到判断分支,理论上有概率会引发脏读的产生,此时可使用同步代码块进行同步进而解决。

  list类

public class MyList {
    private List list = new ArrayList();
    synchronized public void addList(String str){
        list.add(str);
    }
    synchronized public int getSize(){
        return list.size();
    }
}

  service类

public class AsyDirtyReadService {
    public void addList(MyList list, String str){
        try {
            if (list.getSize() < 1){
                Thread.sleep(2000);
                list.addList(str);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  线程类

public class AsyDirtyReadThreadA extends Thread {
    private MyList list;
    public AsyDirtyReadThreadA(MyList list) {
        this.list = list;
    }
    @Override
    public void run() {
        AsyDirtyReadService service = new AsyDirtyReadService();
        service.addList(list, "A");
    }
}

public class AsyDirtyReadThreadB extends Thread {
    private MyList list;
    public AsyDirtyReadThreadB(MyList list) {
        this.list = list;
    }
    @Override
    public void run() {
        AsyDirtyReadService service = new AsyDirtyReadService();
        service.addList(list, "B");
    }
}

  测试类

@SpringBootTest
public class AsyDirtyReadTest {
    @Test
    public static void main(String[] args) {
        try {
            MyList list = new MyList();
            AsyDirtyReadThreadA threadA = new AsyDirtyReadThreadA(list);
            threadA.start();
            Thread.sleep(1000);
            AsyDirtyReadThreadB threadB = new AsyDirtyReadThreadB(list);
            threadB.start();
            Thread.sleep(5000);
            System.out.println("listSize = " + list.getSize());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

  控制台输出结果

listSize = 2

  从上面的输出结果可以看出,两条线程都对list执行了addList方法,ThreadA线程再判断完size后并没有立即执行addList方法,因此在threadB线程对list的size做判断的时候listSize依然是0。因此,两条线程都对list执行了addList方法。解决办法是在service类中对list进行同步。
  service类修改

public class AsyDirtyReadService {
    public void addList(MyList list, String str){
        try {
            synchronized(list){
                if (list.getSize() < 1){
                    Thread.sleep(2000);
                    list.addList(str);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

  控制台输出结果

listSize = 1

5、对象锁与Class锁

  synchronized关键字加到static静态方法中是给class类上锁,synchronized关键字加到非static静态方法上是给对象加锁。
  service类

public class ClassLockService {
    synchronized public static void printA(){
        try {
            System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA");
            Thread.sleep(3000);
            System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    synchronized public static void printB(){
        try {
            System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB");
            Thread.sleep(3000);
            System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    synchronized public void printC(){
        try {
            System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC");
            Thread.sleep(3000);
            System.out.println("线程名称为:"+Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

  线程类

public class ThreadA  extends Thread{
    private ClassLockService service;
    public ThreadA(ClassLockService service) {
        this.service = service;
    }
    @Override
    public void run() {
        super.run();
        service.printA();
    }
}

public class ThreadB extends Thread{
    private ClassLockService service;
    public ThreadB(ClassLockService service) {
        this.service = service;
    }
    @Override
    public void run() {
        super.run();
        service.printB();
    }
}

public class ThreadC extends Thread{
    private ClassLockService service;
    public ThreadC(ClassLockService service) {
        this.service = service;
    }
    @Override
    public void run() {
        super.run();
        service.printC();
    }
}

  测试类

@SpringBootTest
public class ClassLockTest {
    @Test
    public static void main(String[] args) {
        ClassLockService service = new ClassLockService();
        ThreadA a = new ThreadA(service);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("b");
        b.start();
        ThreadC c = new ThreadC(service);
        c.setName("c");
        c.start();
    }
}

  控制台输出结果

线程名称为:a在1598583085437进入printA
线程名称为:c在1598583085438进入printC
线程名称为:a在1598583088438离开printA
线程名称为:b在1598583088438进入printB
线程名称为:c在1598583088439离开printC
线程名称为:b在1598583091439离开printB

6、Class锁

  synchronized(class)的作用和synchronized的静态方法一样,是对类加锁。类锁可以对同一类的所有对象实例起作用。同类的所有实例将都以同步方式运行。从以下测试类可以看出,新建了两个不同的ClassLockService类,但是threadA和threadB两个线程以同步的方式运行,因为ClassLockService加了类锁。修改上个例子如下。

  测试类

@SpringBootTest
public class ClassLockTest {
    @Test
    public static void main(String[] args) {
        ClassLockService service1 = new ClassLockService();
        ClassLockService service2 = new ClassLockService();
        ThreadA a = new ThreadA(service1);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(service2);
        b.setName("b");
        b.start();
    }
}

  控制台输出结果

线程名称为:a在1598593236107进入printA
线程名称为:a在1598593239114离开printA
线程名称为:b在1598593239115进入printB
线程名称为:b在1598593242116离开printB

7、死锁

  死锁是因为不同线程都持有彼此的锁,而且都再等待其他线程能够释放锁,陷入长期的彼此等待中。从而导致程序无法继续运行下去。
  service类

public class DeadSynService {
    private Object lockA;
    private Object lockB;
    public DeadSynService(Object lockA, Object lockB){
        this.lockA = lockA;
        this.lockB = lockB;
    }
    public void methodA(Object lockA){
        synchronized (lockA){
            System.out.println("线程:" + Thread.currentThread().getName() + "进入methodA方法。");
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.methodB(lockB);
        }
    }
    public void methodB(Object lockB){
        synchronized (lockB){
            System.out.println("线程:" + Thread.currentThread().getName() + "进入methodB方法。");
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.methodA(lockA);
        }
    }
}

  线程类

public class DeadSynThreadA extends Thread {
    private Object lockA;
    private DeadSynService service;
    public DeadSynThreadA(DeadSynService service, Object lockA){
        this.service = service;
        this.lockA = lockA;
    }
    @Override
    public void run(){
        service.methodA(lockA);
    }
}

public class DeadSynThreadB extends Thread {
    private Object lockB;
    private DeadSynService service;
    public DeadSynThreadB(DeadSynService service, Object lockB){
        this.service = service;
        this.lockB = lockB;
    }
    @Override
    public void run(){
        service.methodB(lockB);
    }
}

  测试类

@SpringBootTest
public class DeadSynTest {
    @Test
    public static void main(String[] args) {
        Object lockA = new Object();
        Object lockB = new Object();
        DeadSynService service = new DeadSynService(lockA, lockB);
        DeadSynThreadA threadA = new DeadSynThreadA(service,lockA);
        threadA.setName("A");
        threadA.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        DeadSynThreadB threadB = new DeadSynThreadB(service,lockB);
        threadB.setName("B");
        threadB.start();
    }
}

  控制台输出结果

线程:A进入methodA方法。
线程:B进入methodB方法。

  jstack检查死锁

1、win+r命令进入cmd;
2、切换至JDK的bin文件夹下;
3、执行jps命令查询线程id;
4、jstack -l +线程id 查询死锁情况;

8、内部类的同步

  内部类和普通类在多线程的引用上类似,只是在申明内部类的时候不一样。1、当一个线程获取了内部类的对象锁,其他线程还是可以访问该内部类的非同步方法,两个线程异步运行;2、两个线程争取同个对象锁时,两个线程同步运行;
  sevice类

public class ParentClass {
    public class SonClassA{
         synchronized public void method1(){
            for (int i = 0; i < 10 ; i++){
                System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public void method2(){
            for (int j = 0; j < 10 ; j++){
                System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + j);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  测试类

@SpringBootTest
public class InnerClassTest {
    @Test
    public static void main(String[] args) {
        ParentClass parent = new ParentClass();
        ParentClass.SonClassA sonA =parent.new SonClassA();
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                sonA.method1();
            }
        },"A");
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                sonA.method1();
            }
        },"B");
        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                sonA.method2();
            }
        },"C");
        threadA.start();
        threadB.start();
        threadC.start();
}

  控制台输出结果

线程:C;i = 0
线程:B;i = 0
线程:C;i = 1
线程:B;i = 1
线程:B;i = 2
线程:C;i = 2
线程:B;i = 3
线程:C;i = 3
线程:B;i = 4
线程:C;i = 4
线程:C;i = 5
线程:B;i = 5
线程:C;i = 6
线程:B;i = 6
线程:C;i = 7
线程:B;i = 7
线程:C;i = 8
线程:B;i = 8
线程:C;i = 9
线程:B;i = 9
线程:A;i = 0
线程:A;i = 1
线程:A;i = 2
线程:A;i = 3
线程:A;i = 4
线程:A;i = 5
线程:A;i = 6
线程:A;i = 7
线程:A;i = 8
线程:A;i = 9

  内部类的线程同步和普通类的线程同步用法基本雷士。当一个线程获得了类锁,那么其他线程即使获得其他对象锁,也需要同步运行,需要等待前面一个线程结束后才能运行;
  service类

public class ParentClass {
    public class SonClassA{
        public void method3(SonClassB classB){
             synchronized(classB){
                 for (int k = 0; k < 10 ; k++){
                     System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + k);
                     try {
                         Thread.sleep(500);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             }
        }
    }
    public class SonClassB{
        public synchronized void method4(){
            for (int m = 0; m < 10 ; m++){
                System.out.println("线程:" + Thread.currentThread().getName() + ";i = " + m);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  测试类

@SpringBootTest
public class InnerClassTest {
    @Test
    public static void main(String[] args) {
        ParentClass parent = new ParentClass();
        ParentClass.SonClassA sonA =parent.new SonClassA();
        ParentClass.SonClassB sonB =parent.new SonClassB();
        Thread threadD = new Thread(new Runnable() {
            @Override
            public void run() {
                sonA.method3(sonB);
            }
        },"D");
        Thread threadE = new Thread(new Runnable() {
            @Override
            public void run() {
                sonB.method4();
            }
        },"E");
        threadD.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadE.start();
    }
}

  控制台输出结果

线程:D;i = 0
线程:D;i = 1
线程:D;i = 2
线程:D;i = 3
线程:D;i = 4
线程:D;i = 5
线程:D;i = 6
线程:D;i = 7
线程:D;i = 8
线程:D;i = 9
线程:E;i = 0
线程:E;i = 1
线程:E;i = 2
线程:E;i = 3
线程:E;i = 4
线程:E;i = 5
线程:E;i = 6
线程:E;i = 7
线程:E;i = 8
线程:E;i = 9

9、改变锁对象

  当线程运行过程中,锁对象改变了,其他线程可以获得该锁。另外,当锁对象未改变,只是改变了锁对象的属性,线程并不会释放锁。

  service类

public class ChangeLockService {
    private Object lock1;
    public ChangeLockService(Object lock1) {
        this.lock1 = lock1;
    }
    public void setMethod(){
        synchronized (lock1) {
            System.out.println("线程:" + Thread.currentThread().getName() + ",开始时间:" + System.currentTimeMillis() + "。");
            try {
                Thread.sleep(50);
                lock1 = new Object();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + ",结束时间:" + System.currentTimeMillis() + "。");
        }
    }
}

  线程类

public class ChangeLockThread extends Thread {
    private ChangeLockService service;
    public ChangeLockThread(ChangeLockService service) {
        this.service = service;
    }
    @Override
    public void run() {
        super.run();
        service.setMethod();
    }
}

  测试类

@SpringBootTest
public class changeLockTest {
    @Test
    public static void main(String[] args) {
        Object lock1 = new Object();
        ChangeLockService service = new ChangeLockService(lock1);
        ChangeLockThread threadA = new ChangeLockThread(service);
        threadA.setName("A");
        threadA.start();
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ChangeLockThread threadB = new ChangeLockThread(service);
        threadB.setName("B");
        threadB.start();
    }
}

  控制台输出结果

  从控制台输出结果可以看出,ThreadA在运性过程中将锁对象改变为新new出来的锁,ThreadB获得了线程锁。因此,ThreadA和ThreadB同步运行的。

线程:A,开始时间:1598838244210。
线程:B,开始时间:1598838244261。
线程:A,结束时间:1598838244261。
线程:B,结束时间:1598838244312。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值