Java拾贝第九天——synchronized关键字

发布时间 2023-10-23 14:45:22作者: ocraft

Java拾贝不建议作为0基础学习,都是本人想到什么写什么

当多个线程同时读取某一变量时候,容易出现和预期不符的结果

public class Test9 {
    static int i = 0;

    public  static void main(String[] args) {
        Thread m1 = new Thread() {
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {//给静态属性i加10000
                    Test9.i++;
                }
                System.out.println(i);//循环结束打印i
            }
        };
        Thread m2 = new Thread() {
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {//给静态属性i减10000
                    Test9.i--;
                }
                System.out.println(i);//循环结束打印i
            }
        };
        m1.start();
        m2.start();
        try {
            m1.join();//理想状态先加后减
            Thread.sleep(5000);
            System.out.println(Test9.i);//main线程睡眠5秒最后打印i值
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

程序运行结果:

2168
-80
-80

是的,不知道什么原因最后i值输出居然是-80。

这是因为线程的调度是操作系统决定的。因此,任何一个线程都有可能意外暂停,然后在某个时间段后继续执行。

想要使得上述问题解决须保证原子性。即一个或一系列操作无法被中断

synchronized

解决上述难题可以使用同步(synchronized)代码块和同步(synchronized)方法两种方式完成

同步代码块

代码块就是指用{}包括的一段代码,根据位置和声明的不同可以分为普通代码块,局部代码块,静态代码块和同步代码块。

同步代码块就是指在代码块前加上synchronized关键字:

synchronized(同步对象){
}

与其他代码块不同的是,同步代码块需要传入一个同步对象。同时也保证了代码块在任意时刻最多只有一个线程能执行。

同步对象也叫锁。而且也可以理解为锁,即谁拿到了锁谁就可以进门操作。

至此,修改上述的栗子代码:

public class Test9 {
    static int i = 0;
    static Object o=new Object();//新建一个成员变量o作为同步对象

    public static void main(String[] args) {
        Thread m1 = new Thread() {
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {
                    synchronized (Test9.o){//放到循环外线程不是并发
                        Test9.i++;
                    }
                }
                System.out.println(i);
            }
        };
        Thread m2 = new Thread() {
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {
                    synchronized (Test9.o){//放到循环外等于线程顺序执行
                        Test9.i--;
                    }
                }
                System.out.println(i);
            }
        };
        m1.start();
        m2.start();
        try {
            Thread.sleep(5000);
            System.out.println("main"+Test9.i);//main线程睡眠5秒最后打印i值
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

程序运行结果:

243
0
main0

成功了!预期的结果达到了!变量i在经过m1和m2两个线程一阵倒腾,最终为0;

m1和m2在执行各自的同步代码块时,必须先获取同步对象才能进入代码块运行。执行结束后会自动释放同步对象。这样一来,对变量i的读写就不能同时进行。

即便在同步代码块中抛出异常,同步对象也会在同步代码块结束处正确的释放

注意
对于上述栗子同步对象一定要相同!!否则预期结果还是会错误

public class Test9 {
    static int i = 0;
    static Object o=new Object();
    static Object o2=new Object();

    public static void main(String[] args) {
        Thread m1 = new Thread() {
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {
                    synchronized (Test9.o){//同步对象不一致
                        Test9.i++;
                    }
                }
                System.out.println(i);
            }
        };
        Thread m2 = new Thread() {
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {
                    synchronized (Test9.o2){//同步对象不一致
                        Test9.i--;
                    }
                }
                System.out.println(i);
            }
        };
       //省略线程启动和主线程休眠打印i值
}

程序运行结果:

1815
1241
main1241

同步方法

同步方法就是用synchronized关键字声明的方法

synchronized 返回类型 方法名(传参){

}

修改栗子:

public class Test9 {
    static int i = 0;

    public static void main(String[] args) {
        Test9 t9 = new Test9();
        Thread m1 = new Thread() {
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {
                    t9.addi();

                }
                System.out.println(i);
            }
        };
        Thread m2 = new Thread() {
            @Override
            public void run() {
                for (int j = 0; j < 10000; j++) {
                    t9.subi();
                }
                System.out.println(i);
            }
        };
        m1.start();
        m2.start();
        try {
            Thread.sleep(5000);
            System.out.println("main" + Test9.i);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public  synchronized int addi() {
        return ++i;
    }

    public  synchronized int subi() {
        return --i;

    }
}

程序运行结果:

1134
0
main0

注意
那么同步方法的同步对象是谁呢?

Test9 t9 = new Test9();
t9.addi();
t9.subi();

同步方法的同步对象,就是调用同步方法的对象。即变量t9

对于使用了sitatic关键字的同步方法,其同步对象就是静态方法所在的类。也称静态同步方法

public class Test {
	public static  synchronized void test() {
        System.out.println("对于这个栗子,同步对象就是Test类");
    }
}

因为静态属性和方法是属于类的