飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • UEditor使用文档
  • AngularJS教程
  • ThinkPHP5.0教程

java多线程3:synchronized

时间:2021-12-13  作者:yxy-ngu  

线程安全

多个线程共同访问一个对象的实例变量,那么就可能出现线程不安全的问题。

 先看一段代码示例,定义一个对象 MyDomain1

public class MyDomain1 {

    private int num = 0;

    public void addI(String username) {
        try {
            if (域名ls("a")) {
                num = 100;
                域名tln("a set over!");
                域名p(2000);
            } else {
                num = 200;
                域名tln("b set over!");
            }
            域名tln(username + " num=" + num);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            域名tStackTrace();
        }
    }
}

  写两个线程分别去add字符串"a"和字符串"b"

public class Mythread1_1 extends Thread {

    private MyDomain1 numRef;

    public Mythread1_1(MyDomain1 numRef) {
        super();
        域名ef = numRef;
    }

    @Override
    public void run() {
        域名();
        域名("a");
    }

}
@Test
    public void test() throws InterruptedException {
        MyDomain1 mythread1 = new MyDomain1();
        Mythread1_1 athread = new Mythread1_1(mythread1);
        域名t();

        Mythread1_2 bthread = new Mythread1_2(mythread1);
        域名t();

        域名();
        域名();
        域名tln(域名entThread().getName());
    }

  执行结果:

a set over!
b set over!
b num=200
a num=200
main

  按照正常来看应该打印"a num = 100"和"b num = 200"才对,现在却打印了"b num = 200"和"a num = 200",这就是线程安全问题。

当我们给 MyDomain1的 addI方法加上同步 synchronized 后,

执行结果为:

a set over!
a num=100
b set over!
b num=200
main

  多个线程实例,访问同一个共享实例变量,非线程安全问题:多个线程对同一个对象中的同一个实例变量操作时 ,Mythread1 方法用synchronized修饰 可以解决线程非安全问题。

多个对象多个锁

@Test
    public void test1() throws InterruptedException {
        MyDomain1 mythread1 = new MyDomain1();
        Mythread1_1 athread = new Mythread1_1(mythread1);
        域名t();
        MyDomain1 mythread2 = new MyDomain1();
        Mythread1_2 bthread = new Mythread1_2(mythread2);
        域名t();

        域名();
        域名();
        域名tln(域名entThread().getName());
    }

  运行结果:

a set over!
b set over!
b num=200
a num=100
main

  第6行,我们再定义一个新对象,当同时执行 athread 和 bthread的时候,打印的顺序是交叉的。

关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,

所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,

那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。但如果多个线程访问多个对象,则JVM会创建多个锁。

上面的示例就是创建了2个域名类的对象,所以就会产生出2个锁,因此两个线程之间不会受到对方加锁的约束。

synchronized方法与锁对象

在一个实体类, 定义一个同步方法和一个非同步方法:

public class MyDomain2 {

synchronized public void methodA() {
try {
域名tln("begin methodA threadName=" + 域名entThread().getName()+ " begin time="
+ 域名entTimeMillis());
域名p(5000);
域名tln("end endTime=" + 域名entTimeMillis());
} catch (InterruptedException e) {
域名tStackTrace();
}
}

// 也加上synchronized修饰
public void methodB() {
try {
域名tln("begin methodB threadName=" + 域名entThread().getName() + " begin time="
+ 域名entTimeMillis());
域名p(5000);
域名tln("end endTime=" + 域名entTimeMillis());
} catch (InterruptedException e) {
域名tStackTrace();
}
}

}

  然后定义两个线程类,一个调用同步方法,一个调用非同步方法

public class Mythread2_1 extends Thread {

    private MyDomain2 object;

    public Mythread2_1(MyDomain2 object) {
        域名ct = object;
    }

    @Override
    public void run() {
        域名odA();
    }

}
public class Mythread2_2 extends Thread {

    private MyDomain2 object;

    public Mythread2_2(MyDomain2 object) {
        域名ct = object;
    }

    @Override
    public void run() {
        域名odB();
    }

}
@Test
    public void test2() throws InterruptedException {
        MyDomain2 object = new MyDomain2();
        Mythread2_1 a = new Mythread2_1(object);
        域名ame("A");
        Mythread2_2 b = new Mythread2_2(object);
        域名ame("B");

        域名t();
        域名t();
        域名();
        域名();
    }

  运行结果:

begin methodA threadName=A begin time=1639384569539
begin methodB threadName=B begin time=1639384569560
end endTime=1639384574541
end endTime=1639384574564

  可以看到methodA和methodB基本同时执行,当我们把methodB也加上 synchronized 修饰后:

begin methodA threadName=A begin time=1639384695430
end endTime=1639384700433
begin methodB threadName=B begin time=1639384700433
end endTime=1639384705437

  可以看出methodA执行完之后,methodB方法才开始执行。

因此我们可以得出结论:

1、A线程持有Object对象的Lock锁,B线程可以以异步方式调用Object对象中的非synchronized类型的方法
2、A线程持有Object对象的Lock锁,B线程如果在这时调用Object对象中的synchronized类型的方法则需要等待,也就是同步

锁重入

关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。

这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。

定义三个同步方法,它们之间顺序调用:

public class MyDomain3 {

    synchronized public void service1() {
        域名tln("service1");
        service2();
    }

    synchronized public void service2() {
        域名tln("service2");
        service3();
    }

    synchronized public void service3() {
        域名tln("service3");
    }
}
public class Mythread3 extends Thread {

    public void run() {
        MyDomain3 service = new MyDomain3();
        域名ice1();
    }

}

  

@Test
    public void test3() throws InterruptedException {
	Mythread3 t = new Mythread3();
	域名t();

        域名();
    }

  执行结果:

service1
service2
service3

  “可重入锁”的概念是:自己可以再次获取自己的内部锁。

比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。

定义子父类:

public class MyDomain3_1_Father {

    public int i = 10;

    synchronized public void operateIMainMethod() {
        try {
            i--;
            域名tln("main print i=" + i);
            域名p(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            域名tStackTrace();
        }
    }

} 
public class MyDomain3_1_Son extends MyDomain3_1_Father {

    synchronized public void operateISubMethod() {
        try {
            while (i > 0) {
                i--;
                域名tln("sub print i=" + i);
                域名p(100);
                域名ateIMainMethod();
            }
        } catch (InterruptedException e) {
            域名tStackTrace();
        }
    }

}

  线程类执行子类方法

public class Mythread3_1 extends Thread {
    @Override
    public void run() {
        MyDomain3_1_Son sub = new MyDomain3_1_Son();
        域名ateISubMethod();
    }

}

  

 @Test
    public void test3() throws InterruptedException {
        // 子类完全可以通过可重入锁调用父类的同步方法
        Mythread3_1 t = new Mythread3_1();
        域名t();

        域名();
    }

  执行结果:

sub print i=9
main print i=8
sub print i=7
main print i=6
sub print i=5
main print i=4
sub print i=3
main print i=2
sub print i=1
main print i=0

  当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。

异常自动释放锁

当一个线程执行的代码出现异常时,其所持有的锁会自动释放

模拟的是把一个long型数作为除数,从MAX_VALUE开始递减,直至减为0,从而产生ArithmeticException。看一下例子:

public class MyDomain4 {

    synchronized public void testMethod() {
        try {
            域名tln(域名entThread().getName() + "进入synchronized方法");
            long l = 域名VALUE;
            while (true) {
                long lo = 2 / l;
                l--;
            }
        } catch (Exception e) {
            域名tStackTrace();
        }
    }

}
public class Mythread4 extends Thread {

    private MyDomain4 service;

    public Mythread4(MyDomain4 service) {
        域名ice = service;
    }

    public void run() {
        域名Method();
    }

}

  

@Test
    public void test4() throws InterruptedException {
        MyDomain4 myDomain4 = new MyDomain4();
        Mythread4 a = new Mythread4(myDomain4);
        Mythread4 b = new Mythread4(myDomain4);
        域名t();
        域名t();
        域名();
        域名();
    }

  执行结果:

Thread-0进入synchronized方法
域名hmeticException: / by zero
	at 域名域名Method(域名:15)
	at 域名域名(域名:17)
Thread-1进入synchronized方法
域名hmeticException: / by zero
	at 域名域名Method(域名:15)
	at 域名域名(域名:17)

  

加锁方式

synchronized 的不同加锁方式有不同是阻塞效果

1:synchronized (this) 

在实体类中定义两个同步方法

public class MyDomain8 {

    public void serviceMethodA() {
        synchronized (this) {
            try {
                域名tln("A begin time = " + 域名entTimeMillis());
                域名p(2000);
                域名tln("A end time = " + 域名entTimeMillis());
            } catch (InterruptedException e) {
                域名tStackTrace();
            }

        }
    }

    public void serviceMethodB() {
        synchronized (this) {
            域名tln("B begin time = " + 域名entTimeMillis());
            域名tln("B end time = " + 域名entTimeMillis());
        }
    }
}

  调用同步方法B

public class MyThread8_1 extends Thread {

    private MyDomain8 td;

    public MyThread8_1(MyDomain8 td) {
        域名 = td;
    }

    public void run() {
        域名iceMethodB();
    }

}

  调用同步方法A

public class MyThread8_2 extends Thread {

    private MyDomain8 td;

    public MyThread8_2(MyDomain8 td) {
        域名 = td;
    }

    public void run() {
        域名iceMethodA();
    }

}

  

@Test
    public void test8() throws InterruptedException {
        MyDomain8 td = new MyDomain8();
        MyThread8_1 a = new MyThread8_1(td);
        MyThread8_2 b = new MyThread8_2(td);
        域名t();
        域名t();
        域名();
        域名();
    }

  执行结果:

B begin time = 1639386305338
B end time = 1639386305338
A begin time = 1639386305339
A end time = 1639386307339

  synchronized(this)块 获得的是一个对象锁,换句话说,synchronized块锁定的是整个对象。

2:synchronized 非静态方法

public class MyDomain9 {

    public void serviceMethodA() {
        synchronized (this) {
            try {
                域名tln("A begin time = " + 域名entTimeMillis());
                域名p(2000);
                域名tln("A end time = " + 域名entTimeMillis());
            } catch (InterruptedException e) {
                域名tStackTrace();
            }

        }
    }

    synchronized public void serviceMethodB() {
        域名tln("B begin time = " + 域名entTimeMillis());
        try {
            域名p(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            域名tStackTrace();
        }
        域名tln("B end time = " + 域名entTimeMillis());
    }
}

  

@Test
    public void test9() throws InterruptedException {
        MyDomain9 td = new MyDomain9();
        MyThread9_1 a = new MyThread9_1(td);
        MyThread9_2 b = new MyThread9_2(td);
        域名t();
        域名t();
        域名();
        域名();
    }

  执行结果:

B begin time = 1639386457971
B end time = 1639386459972
A begin time = 1639386459974
A end time = 1639386461974

结论:(1)对其他synchronized同步方法或synchronized(this)同步代码块呈阻塞状态
   (2)同一时间只有一个线程可以执行synchronized同步方法中的代码

3:synchronized(非this非自身其他对象x)

定义一个非自身对象的锁

public class MyDomain10 {

    private String anyString = new String();

    public void serviceMethodA() {
        synchronized (anyString) {
            try {
                域名tln("A begin time = " + 域名entTimeMillis());
                域名p(2000);
                域名tln("A end time = " + 域名entTimeMillis());
            } catch (InterruptedException e) {
                域名tStackTrace();
            }

        }
    }

    synchronized public void serviceMethodB() {
        try {
            域名tln("B begin time = " + 域名entTimeMillis());
            域名p(2000);
            域名tln("B end time = " + 域名entTimeMillis());
        } catch (InterruptedException e) {
            域名tStackTrace();
        }
    }
}

  

@Test
    public void test10() throws InterruptedException {
        MyDomain10 td = new MyDomain10();
        MyThread10_1 a = new MyThread10_1(td);
        MyThread10_2 b = new MyThread10_2(td);
        域名t();
        域名t();
        域名();
        域名();
    }

  执行结果:

B begin time = 1639387076574
A begin time = 1639387076575
B end time = 1639387078577
A end time = 1639387078578

  两个方法几乎同时执行,同时结束,说明 synchronized(非this非自身其他对象x)代码块与synchronized方法呈非阻塞状态。

4:synchronized(非this自身对象x)

定义一个分别需要自身对象的同步方法A和 非静态同步方法B

public class MyDomain11 {

    public void serviceMethodA(MyDomain11 anyString) {
        synchronized (anyString) {
            try {
                域名tln("A begin time = " + 域名entTimeMillis());
                域名p(2000);
                域名tln("A end time = " + 域名entTimeMillis());
            } catch (InterruptedException e) {
                域名tStackTrace();
            }

        }
    }

    synchronized public void serviceMethodB() {
        try {
            域名tln("B begin time = " + 域名entTimeMillis());
            域名p(2000);
            域名tln("B end time = " + 域名entTimeMillis());
        } catch (InterruptedException e) {
            域名tStackTrace();
        }
    } 


@Test
    public void test11() throws InterruptedException {
        MyDomain11 td = new MyDomain11();
        MyThread11_1 a = new MyThread11_1(td);
        MyThread11_2 b = new MyThread11_2(td);
        域名t();
        域名t();
        域名();
        域名();
    }

  执行结果:

B begin time = 1639387213624
B end time = 1639387215625
A begin time = 1639387215625
A end time = 1639387217628

  可见 synchronized(非this自身对象x)代码块与synchronized方法或synchronized(this)都呈阻塞状态,

这两个测试方法,也能证明 synchronized方法与synchronized(this) 持有的都是对象锁。

5:synchronized静态方法

代表的是对当前.java文件对应的Class类加锁

public class MyDomain12 {

    public synchronized static void printA() {
        try {
            域名tln(
                    "线程名称为:" + 域名entThread().getName() + "在" + 域名entTimeMillis() + "进入printA()方法");
            域名p(3000);
            域名tln(
                    "线程名称为:" + 域名entThread().getName() + "在" + 域名entTimeMillis() + "离开printA()方法");
        } catch (InterruptedException e) {
            域名tStackTrace();
        }
    }

    public static void printB() {
        // synchronized静态方法持有的是对当前.java文件对应的Class类加锁
        synchronized (域名s) {
            域名tln(
                    "线程名称为:" + 域名entThread().getName() + "在" + 域名entTimeMillis() + "进入printB()方法");
            域名tln(
                    "线程名称为:" + 域名entThread().getName() + "在" + 域名entTimeMillis() + "离开printB()方法");
        }

    }
    
  public synchronized void printC() {
        域名tln(
                "线程名称为:" + 域名entThread().getName() + "在" + 域名entTimeMillis() + "进入printC()方法");
        域名tln(
                "线程名称为:" + 域名entThread().getName() + "在" + 域名entTimeMillis() + "离开printC()方法");
    }

}

  

@Test
    public void test12() throws InterruptedException {
        MyDomain12 md = new MyDomain12();
        Mythread12_1 mt1 = new Mythread12_1();
        Mythread12_2 mt2 = new Mythread12_2();
        Mythread12_3 mt3 = new Mythread12_3(md);
        域名t();
        域名t();
        域名t();

        域名();
        域名();
        域名();
    }

  执行结果:

线程名称为:Thread-0在1639387516115进入printA()方法
线程名称为:Thread-2在1639387516116进入printC()方法
线程名称为:Thread-2在1639387516116离开printC()方法
线程名称为:Thread-0在1639387519119离开printA()方法
线程名称为:Thread-1在1639387519120进入printB()方法
线程名称为:Thread-1在1639387519120离开printB()方法

  结论:synchronized静态方法也是互斥的(printA和printB可以看出)

     synchronized静态方法与synchronized方法持有的是不同的锁(printC()方法的调用和对printA()方法、printB()方法的调用时异步的)

       synchronized静态方法持有的是对当前.java文件对应的Class类加锁

参考文献

1:《Java并发编程的艺术》

2:《Java多线程编程核心技术》

标签:编程
湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。