栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java-日常练习-多线程-synchronized详解

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java-日常练习-多线程-synchronized详解

先举个例子线程不安全的

package a;

class  Test {
    public  int a=0;


    public void setValue(String name) throws InterruptedException {
            if(name.equals("a")){
                a=100;
                Thread.sleep(1000);
                System.out.println("a= "+a);
            }
            else {
                a=200;
                System.out.println("b= "+a);
            }
    }
    }
class RunA extends  Thread{
    Test a;
    public RunA(Test a){
        this.a=a;
    }
    @Override
    public void run() {
        super.run();
        try {
            a.setValue("a");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
class RunB extends  Thread{
    Test a;
    public RunB(Test a){
        this.a=a;
    }
    @Override
    public void run() {
        super.run();
        try {
            a.setValue("b");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
public class Sender extends Thread {

    public static void main(String[] args) throws InterruptedException {

    Test a= new Test();
    RunA A=new RunA(a);
    A.start();
    RunB B=new RunB(a);
    B.start();
    A.setName("A");
    System.out.println("end");

    }
}

打印结果:
end
b= 200
a= 200

原因是: 多个线程同时访问一个对象变量,有可能会出现线程安全。

解决方法:
在访问的方法加个关键字 synchronized 就变成了同步访问了。

 synchronized public void setValue(String name) throws   // 添加了关键字InterruptedException {
            if(name.equals("a")){
                a=100;
                Thread.sleep(1000);
                System.out.println("a= "+a);
            }
            else {
                a=200;
                System.out.println("b= "+a);
            }
    }

这样就变成了同步访问。
同步和异步的区别:
同步: 按照顺序执行业务,先执行A 业务,然后执行B 业务
异步: A业务执行的时候,B业务也继续执行。
打印结果如下:
end
a= 100
b= 200


经常犯的错误是 如果是多个对象一定是多个对象锁,他们之间是异步的情况,没有锁的竞争, 例子验证如下:

package a;

class  Test {
    public  int a=0;


    synchronized public void setValue(String name) throws InterruptedException {
            if(name.equals("a")){
                a=100;
                Thread.sleep(1000);
                System.out.println("a= "+a);
            }
            else {
                a=200;
                System.out.println("b= "+a);
            }
    }
    }
class RunA extends  Thread{
    Test a;
    public RunA(Test a){
        this.a=a;
    }
    @Override
    public void run() {
        super.run();
        try {
            a.setValue("a");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
class RunB extends  Thread{
    Test a;
    public RunB(Test a){
        this.a=a;
    }
    @Override
    public void run() {
        super.run();
        try {
            a.setValue("b");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
public class Sender extends Thread {

    public static void main(String[] args) throws InterruptedException {

    Test a= new Test();
    RunA A=new RunA(a);
    A.start();
    RunB B=new RunB(a);
    B.start();
    A.setName("A");
    Test c= new Test();
    RunA C=new RunA(c);
    C.start();
    RunB D=new RunB(c);
    D.start();
    A.setName("A");






    System.out.println("end");

    }
}

打印的结果为:
end
a= 100
a= 100
b= 200
b= 200

从结果分析是 异步的, 如果是同步的结果应该是如下:
end
a= 100
b= 200
a= 100
b= 200

大家记住两条就行了同步的条件是
1.synchronized 方法 和对象(同一个)生成一个对象锁
2. 有了对象锁还需要一个条件是 共享 ,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么就没有同步的必要。

对象锁只是针对有同步关键字的(synchronized)方法做同步,其他方法没有同步关键字是不做处理的,关注下边的例子

package a;

class  Test {
    public  int a=0;


    synchronized public void setValue(String name) throws InterruptedException {
            if(name.equals("a")){

                a=100;
                System.out.println("A对象同步方法开始运行了 ");
                Thread.sleep(1000);
                System.out.println("a= "+a);
            }
            else {
                System.out.println("B对象开始同步");
                a=200;
                System.out.println("b= "+a);
            }
    }
    public  void method2() throws InterruptedException {
        System.out.println("没有同步关键字method2 begin");
        Thread.sleep(500);

        System.out.println("method2 开始了,执行的线程为: "+Thread.currentThread().getName());
    }
    }

class RunA extends  Thread{
    Test a;
    public RunA(Test a){
        this.a=a;
    }
    @Override
    public void run() {
        super.run();
        try {
            a.setValue("a");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
class RunB extends  Thread{
    Test a;
    public RunB(Test a){
        this.a=a;
    }
    @Override
    public void run() {
        super.run();
        try {
            a.method2();
            a.setValue("B");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }


    }
}
public class Sender extends Thread {

    public static void main(String[] args) throws InterruptedException {

    Test a= new Test();
    RunA A=new RunA(a);
    RunB B=new RunB(a);
    A.setName("A");
    A.start();

    B.setName("B");
    B.start();
    System.out.println("end");

    }
}


打印的结果如下:

end
A对象同步方法开始运行了
没有同步关键字method2 begin
method2 开始了,执行的线程为: B
a= 100
B对象开始同步
b= 200

通过上边的例子可以看出, method2 没有同步, 如果想同步可以的, 直接加上关键字 修改如下:

synchronized public  void method2() throws InterruptedException	{

运行结果如下,他们的按照顺序进行运行,达到了同步:

end
A对象同步方法开始运行了
a= 100
没有同步关键字method2 begin
method2 开始了,执行的线程为: B
B对象开始同步
b= 200

同步运行总结如下:


通过以上实例可以得出, 在读取同一个实例变量的时候, 写入的方法虽然是 同步 ,但是如果 读取的不是同步,就会出现脏数据。解决方法就是在 读取数据的时候也要加入关键字。

synchronized同步语句块

synchronized关键字同步方法,是有局限的, 比如我同步方法里有耗时的操作, 所有线程只能一直等着。 解决方法 同步语句块, 同步语句块可以传入任意对象,并且把任意对象当成锁对象,哪个线程持有这个锁,就可以执行同步方法。

咱们验证下不用同步语句块时间消耗对比,代码如下:

class CommonUtils{
    public static  long beginTime1;
    public static  long beginTime2;
    public static  long endTime1;
    public static  long endTime2;

}
class  Test {
    public  int a=0;

    synchronized public void test() throws InterruptedException {
        Thread.sleep(3000);
        a++;
        System.out.println(Thread.currentThread().getName()+"  a的值  "+a);
    }
    }

class RunA extends  Thread{
    Test a;
    public RunA(Test a){
        this.a=a;
    }
    @Override
    public void run() {
        CommonUtils.beginTime1=System.currentTimeMillis();
        super.run();
        try {
            a.test();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        CommonUtils.endTime1=System.currentTimeMillis();
    }
}
class RunB extends  Thread{
    Test a;
    public RunB(Test a){
        this.a=a;
    }
    @Override
    public void run() {
        super.run();
        CommonUtils.beginTime2=System.currentTimeMillis();
        try {
            a.test();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        CommonUtils.endTime2=System.currentTimeMillis();
    }
}
public class Sender extends Thread {

    public static void main(String[] args) throws InterruptedException {

    Test a= new Test();
    RunA A=new RunA(a);
    RunB B=new RunB(a);
    A.setName("A");
    A.start();
    B.setName("B");
    B.start();
    Thread.sleep(10000);
    long begin=CommonUtils.beginTime1;
    if (begin>CommonUtils.beginTime2){
        begin=CommonUtils.beginTime2;
    }
    long end=CommonUtils.endTime1;
    if(end 

打印的结果如下:
A a的值 1
B a的值 2
耗时:6031
end

如果改成同步语句块的方式实现同步,修改方法为:

 public void test() throws InterruptedException {
        Thread.sleep(3000);
        synchronized (this){
            a++;
        }
        System.out.println(Thread.currentThread().getName()+"  a的值  "+a);
    }

打印的结果为:

B  a的值  1
A  a的值  2
耗时:3030
end

从结果看明显已经缩短了很多, 说明task 的sleep 方法是异步 , a++ 是同步的 ,方法内的其他是异步的,这就是半同步半异步的效果。

咱们证明下同步语句块是否真的就是通不了 ,修改方法为:

 public void test() throws InterruptedException {
        Thread.sleep(3000);
        synchronized (this){
            for (int i = 0; i < 50000; i++) {
                a++;
            }
        }
        System.out.println(Thread.currentThread().getName()+"  a的值  "+a);
    }

打印结果如下:
B a的值 50000
A a的值 100000
耗时:3034
end

确实是同步了,计算完全正确。

注意: 除了 synchronized(this) 格式来创建同步代码块,其实java还支持讲 任意对象作为锁来实现同步的功能, 这个任务对象 大多数是实例变量以及方法的参数,使用格式为synchronized(非this对象),

示例代码演示如下
修改下Test 类

class  Test {
    public  int a=0;
    Integer anything= 0;

     public void test() throws InterruptedException {
        Thread.sleep(3000);
        synchronized (anything){
            for (int i = 0; i < 50000; i++) {
                a++;
            }

        }

        System.out.println(Thread.currentThread().getName()+"  a的值  "+a);
    }
    }

Integer anything= 0;
anything 作为锁对象, 任何对象都可以作为锁对象,你也可以写 String anything=new String(), 好处是当有多个锁方法的时候, 他们运行是同步的极大的消耗了时间, 如果把场景分一下, 运行不同的锁对象就极大的提高了我们的运行效率,因为不同的锁对象之间是异步运行的。

打印结果也是同步了 结果如下:

A  a的值  100000
B  a的值  50000
耗时:3038
end

大家猜猜如果 想使用 synchronized(非this对象)同步代码块和 synchronized 方法 同时存在的效果,
答案是 : 这两个方法由于不是同一个锁,所以是异步的验证代码如下:

package a;

class  Test {
    public  int a=0;
    Integer anything= 0;

     public void test() throws InterruptedException {
         System.out.println(Thread.currentThread().getName()+"  a初始值  "+a);

        synchronized (anything){
            for (int i = 0; i < 50000; i++) {
                a++;
            }
        }

        System.out.println(Thread.currentThread().getName()+"  a的值  "+a);
    }
    synchronized  public void test2() throws InterruptedException {
        System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
        Thread.sleep(10);
        System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
    }
    }

class RunA extends  Thread{
    Test a;
    public RunA(Test a){
        this.a=a;
    }
    @Override
    public void run() {

        super.run();
        try {
            a.test();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
class RunB extends  Thread{
    Test a;
    public RunB(Test a){
        this.a=a;
    }
    @Override
    public void run() {
        super.run();

        try {

            a.test2();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

}}
public class Sender extends Thread {
        public static void main(String[] args) throws InterruptedException {

                Test a= new Test();
                RunA A=new RunA(a);
                RunB B=new RunB(a);
                A.setName("A");
                A.start();
                B.setName("B");
                B.start();
                Thread.sleep(10000);







                System.out.println("end");

    }
}

打印的结果如下:

线程BTest 2 开始运行
A a初始值 0
线程BTest 2 结束运行
A a的值 50000
end

从结果显示是异步的。

如果是不同的锁对象(synchronized(非this对象))依然是异步的。 请看下边的例子:

package a;

class  Test {
    public  int a=0;
    Integer anything= 0;
    String  string=new String();


     public void test() throws InterruptedException {


        synchronized (anything){  //anything 是锁对象
            System.out.println(Thread.currentThread().getName()+"  a初始值  "+a);
            for (int i = 0; i < 50000; i++) {
                a++;
            }
            System.out.println(Thread.currentThread().getName()+"  a的值  "+a);
        }


    }
    public void test2() throws InterruptedException {
        synchronized (string){ //string 是锁对象
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
            Thread.sleep(10);
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
        }

    }
    }

class RunA extends  Thread{
    Test a;
    public RunA(Test a){
        this.a=a;
    }
    @Override
    public void run() {

        super.run();
        try {
            a.test();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
class RunB extends  Thread{
    Test a;
    public RunB(Test a){
        this.a=a;
    }
    @Override
    public void run() {
        super.run();

        try {

            a.test2();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

}}
public class Sender extends Thread {
        public static void main(String[] args) throws InterruptedException {

                Test a= new Test();
                RunA A=new RunA(a);
                RunB B=new RunB(a);
                A.setName("A");
                A.start();
                B.setName("B");
                B.start();
                Thread.sleep(10000);
                System.out.println("end");

    }
}

打印结果:

线程BTest 2 开始运行
A  a初始值  0
A  a的值  50000
线程BTest 2 结束运行
end

如果换成同一个锁,一定是同步的, 修改如下:

   public void test2() throws InterruptedException {
        synchronized (anything){    // 换成同一个锁
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
            Thread.sleep(10);
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
        }

    }

打印结果如下:

A  a初始值  0
A  a的值  50000
线程BTest 2 开始运行
线程BTest 2 结束运行

下边的例子挺高级的,请注意看了
用synchronized(非this对象) 和 其他线程 实例方法相结合进行同步。
关键点是 同步语句块的对象 就是 其他线程的实例对象,这样可以达到同步的效果 。
代码如下

package a;
class MyObject{
    synchronized  public void test2() throws InterruptedException { //这个实例对象的同步方法
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
            Thread.sleep(10);
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
    }
}
class  Test {
    public  int a=0;
    Integer anything= 0;
    MyObject object;
    public void test(MyObject object) throws InterruptedException {
        synchronized (object){
            System.out.println(Thread.currentThread().getName()+"  a初始值  "+a);
            for (int i = 0; i < 50000; i++) {
                a++;
            }
            System.out.println(Thread.currentThread().getName()+"  a的值  "+a);
        }

    
    }

    }

class RunA extends  Thread{
    Test a;
    MyObject object;
    public RunA(Test a,MyObject object){
        this.a=a;
        this.object= object;
    }
    @Override
    public void run() {

        super.run();
        try {
            a.test(object);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}
class RunB extends  Thread{
    MyObject object;
    public RunB(MyObject object){
        this.object=object;
    }
    @Override
    public void run() {
        super.run();

        try {

            object.test2();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

}}
public class Sender extends Thread {
        public static void main(String[] args) throws InterruptedException {
            MyObject object=new MyObject();
            Test a=new Test();
            RunA A=new RunA(a,object);
            RunB B=new RunB(object);
            A.setName("A");
            A.start();
            B.setName("B");
            B.start();
            Thread.sleep(10000);

            System.out.println("end");

    }
}

打印结果为:
A a初始值 0
A a的值 50000
线程BTest 2 开始运行
线程BTest 2 结束运行
end

达到了同步效果。

同样也可以这么修改 用synchronized(this),依然同样同步的效果:

  synchronized(this){
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
            Thread.sleep(10);
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
        }

静态同步synchronized 方法 与synchronized(class)语句块

Class 类对象是单例的,在静态static方法上使用synchronized 关键字声明同步方法时,使用当前静态方法所在类对象Class类的单例对象作为锁。

看下下边的例子,就是类对象锁 ,两个方法对应同一个Class 类对象。

package a;
class  Test {
    public  static  int a=0;
    synchronized public static void test() throws InterruptedException {
            System.out.println(Thread.currentThread().getName()+"  a初始值  "+a);
            for (int i = 0; i < 50000; i++) {
                a++;
            }
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+"  a的值  "+a);
    }
    synchronized public static void test2() throws InterruptedException {
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
            Thread.sleep(10);
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
    }

    }
class RunA extends  Thread{
    @Override
    public void run() {
        super.run();
        try {
            Test.test();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class RunB extends  Thread{
    @Override
    public void run() {
        super.run();
        try {
            Test.test2();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
}}
public class Sender extends Thread {
        public static void main(String[] args) throws InterruptedException {
            RunA A=new RunA();
            RunB B=new RunB();
            A.setName("A");
            A.start();
            B.setName("B");
            B.start();
            Thread.sleep(10000);
            System.out.println("end");
    }
}

达到了同步效果如下:

A  a初始值  0
A  a的值  50000
线程BTest 2 开始运行
线程BTest 2 结束运行
end

注意区分一下的观点

同步 syn static 方法可以对类的所有对象实例起作用
说的通俗点 对象实例 调用类的静态方法 和 类名字.method()是一样的, 所以他们都是调用了同一个锁 单例类锁

验证代码如下:

package a;
class  Test {
    public  static  int a=0;
    synchronized public static void test() throws InterruptedException {
            System.out.println(Thread.currentThread().getName()+"  a初始值  "+a);
            for (int i = 0; i < 50000; i++) {
                a++;
            }
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName()+"  a的值  "+a);
    }
    synchronized public static void test2() throws InterruptedException {
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
            Thread.sleep(10);
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
    }

    }
class RunA extends  Thread{
    Test a;
    public RunA(Test a){
        this.a=a;
    }
    @Override
    public void run() {
        super.run();
        try {
            a.test();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class RunB extends  Thread{
    Test a;
    public RunB(Test a){
        this.a=a;
    }

    @Override
    public void run() {
        super.run();
        try {
            a.test2();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
}}
public class Sender extends Thread {
        public static void main(String[] args) throws InterruptedException {
            Test a =new Test();
            Test b=new Test();
            RunA A=new RunA(a);
            RunB B=new RunB(b);
            A.setName("A");
            A.start();
            B.setName("B");
            B.start();
            Thread.sleep(10000);
            System.out.println("end");
    }
}

同步结果;

A  a初始值  0
A  a的值  50000
线程BTest 2 开始运行
线程BTest 2 结束运行
end

Process finished with exit code 0

同步 syn(class) 代码块 和 synchronized static 方法 的作用是一样
修改方法如下:

 public static void test2() throws InterruptedException {
        synchronized (Test.class){
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
            Thread.sleep(10);
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
        }

运行的结果依然是同步的

A  a初始值  0
A  a的值  50000
线程BTest 2 开始运行
线程BTest 2 结束运行
end
不要修改锁对象,否则就变成异步了

实例代码如下:

package a;
class  Test {
    public  static  int a=0;
    public  Object ojbect=new Object();
    public void test() throws InterruptedException {
            synchronized (ojbect){
                System.out.println(Thread.currentThread().getName()+"  a初始值  "+a);
                for (int i = 0; i < 50000; i++) {
                    a++;
                }
                ojbect=new Object();//修改锁对象
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName()+"  a的值  "+a);

            }

    }
      public  void test2() throws InterruptedException {
        synchronized (ojbect){

            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 开始运行");
            Thread.sleep(10);
            System.out.println("线程"+Thread.currentThread().getName()+"Test 2 结束运行");
        }
    }

    }
class RunA extends  Thread{
    Test a;
    public RunA(Test a){
        this.a=a;
    }
    @Override
    public void run() {
        super.run();
        try {
            a.test();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class RunB extends  Thread{
    Test a;
    public RunB(Test a){
        this.a=a;
    }

    @Override
    public void run() {
        super.run();
        try {
            a.test2();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
}}
public class Sender extends Thread {
        public static void main(String[] args) throws InterruptedException {
            Test a =new Test();
            Test b=new Test();
            RunA A=new RunA(a);
            RunB B=new RunB(b);
            A.setName("A");
            A.start();
            B.setName("B");
            B.start();
            Thread.sleep(10000);
            System.out.println("end");
    }
}

打印结果:

线程BTest 2 开始运行
A  a初始值  0
线程BTest 2 结束运行
A  a的值  50000
end

最后总结:

同步的方法一共可以写出这几种

参考资料: Java多线程编程核心技术(第2版) 作者 高洪岩

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/336662.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号