JavaSE-synchronized

        在多线程的操作里,多个线程可以自由的操作,但是可能存在多个线程同时操作一个实例的情况。可能会存在线程不安全的问题,假设现在要从一个银行账户中取钱。我们的代码可以这么设计。

class MyThread implements Runnable{
    private int menoy = 3000;
    @Override
    public void run() {
        while(this.menoy>0){
            try{
                Thread.sleep(1000);
            }catch (InterruptedException i){
                i.printStackTrace();
            }
            this.menoy-=1000;
            System.out.println(Thread.currentThread().getName()+"取款成功,还有"+this.menoy);
        }
    }
}
public class Main{
    public static void main(String[] args){
        MyThread mt = new MyThread();
        new Thread(mt,"用户1").start();
        new Thread(mt,"用户2").start();
        new Thread(mt,"用户3").start();
    }
}

        现在可能会出现这么一种情况:线程A在取款之前睡眠的一秒中,其他的线程已经把钱取走了,那么这里就会出现问题,使存款变为负数,就像这种结果。


        这就是在多个线程同时操纵一个实例时可能会带来的线程不安全问题。所以我们需要有一种限制手段:当有线程在执行某个部分时,别的线程不能操作这个部分。这就是线程的共享互斥。

        Java中,在处理这种问题时,会用到synchronized关键字,这个关键字有两种用法:同步方法,同步代码块。

synchronized方法

        当一个方法加上关键字synchronized后,就可以在同一时刻只让一个线程操作这个这个方法。又称为同步方法。

        例如上面取钱的例子,在run方法前加上synchronized关键字后,该方法会变成同步方法。

class MyThread implements Runnable{
    private int menoy = 3000;
    @Override
    public synchronized void run() {
        while(this.menoy>0){
            try{
                Thread.sleep(1000);
            }catch (InterruptedException i){
                i.printStackTrace();
            }
            this.menoy-=1000;
            System.out.println(Thread.currentThread().getName()+"取款成功,还有"+this.menoy);
        }
    }
}
public class Main{
    public static void main(String[] args){
        MyThread mt = new MyThread();
        new Thread(mt,"用户1").start();
        new Thread(mt,"用户2").start();
        new Thread(mt,"用户3").start();
    }
}

        最后来看结果


        当一个线程在执行run方法时,其他的线程就不能执行该实例的run方法。这样的话就得到了我们想要的结果。

        关于synchronized方法的使用与注意事项,我们可以画图来更好的说明。


        在这里做一下解释:synchronized方法不允许有一个以上的线程执行它,所以当线程A执进入了一个synchronized方法时,就会出现一个像上图竖立的长方体一样的锁,防止其他线程进入synchronized方法。同时,线程A获得了这个对象的锁定,也就是拿到了它的锁。

        但是,对于对对象中的普通方法,synchronized关键字完全没有作用,即使有线程已经获取锁定,其他的线程仍然可以自由的进入非synchronized方法。

        当线程A执行完synchronized方法后,锁就会被释放。那么刚刚因为锁定而被挡到锁外面的线程就会开始抢夺锁定,但是,在同一时间内能拿到锁的只有一个线程,所以没抢到锁的线程任然需要继续等候。

        同步操作虽然可以保证线程安全,但是执行的效率会降低。

同步代码块

        在使用同步代码块时,必须要设置一个锁定的对象,一般来说锁定的对象被设置为this。来看下面的代码。

class MyThread implements Runnable{
    private int menoy = 3000;
    @Override
    public void run() {
        synchronized (this){
        while(this.menoy>0) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException i) {
                i.printStackTrace();
            }
            this.menoy -= 1000;
            System.out.println(Thread.currentThread().getName() + "取款成功,还有" + this.menoy);
        }
        }
    }
}
public class Main{
    public static void main(String[] args){
        MyThread mt = new MyThread();
        new Thread(mt,"用户1").start();
        new Thread(mt,"用户2").start();
        new Thread(mt,"用户3").start();
    }
}

        上面的代码在效果和上面使用synchronized方法是一样的,可以更精确的的控制共享互斥的代码范围。同步代码块还有另外一种作用:在对类方法使用synchronized关键字和使用同步代码块时,意义是不一样的。来看下面的代码。

    public static void printsomething(){
        synchronized (MyThread.class) {
            System.out.println(110);
        }
    }
    public synchronized static void printsomething(){
            System.out.println(110);
    }

        这两种实现方法的效果是一样的,换句话说:synchronized类方法是使用该类的类对象的锁定去实现线程的共享互斥。而synchronized(MyThread.class)是对应MyThread的java.lang.class的实例。

synchronized锁多对象

        首先看一下下面的代码

class My_Class{
    public synchronized void test(){
        System.out.println("test 方法开始"+Thread.currentThread().getName());
        try{
            Thread.sleep(1000);
        }catch (InterruptedException i){
            i.printStackTrace();
        }
        System.out.println("test 方法结束"+Thread.currentThread().getName());
    }
}


class MyThread extends Thread{
    @Override
    public void run() {
        My_Class my_class = new My_Class();
        my_class.test();
    }
}

public class Main{
    public static void main(String[] args){
        for(int i = 0;i<3;i++){
            Thread thread = new MyThread();
            thread.start();
        }
    }
}


        虽然synchronized修饰了test()方法,但是还是出现了三个线程跑一个方法的情况,就算使用synchronized块也不能解决这个问题,因为本质上来讲,synchronized锁住的是括号里的对象,而不是同步块里的代码。对于同步方法来说,锁住的对象本来就是this。

        那么我们应该如何解决这个问题呢?

class My_Class{
    public void test() {
        synchronized (My_Class.class) {
            System.out.println("test 方法开始" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException i) {
                i.printStackTrace();
            }
            System.out.println("test 方法结束" + Thread.currentThread().getName());
        }
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        My_Class my_class = new My_Class();
        my_class.test();
    }
}
public class Main{
    public static void main(String[] args){
        for(int i = 0;i<3;i++){
            Thread thread = new MyThread();
            thread.start();
        }
    }
}
    

        这种思路是直接锁住MyThread类所对应的class对象,这就实现了全局锁的效果,这样可以实现锁住多个对象,锁的是类而不是this。还有另一种方法也可以解决这个问题:使用static synchronized方法,这种方法中没有办法使用this,所以它锁的也是类的class对象。这也相当于一个全局锁,相当于更精确的锁住了代码块。

        先总结到这里,会有后续。

猜你喜欢

转载自blog.csdn.net/qq_38449518/article/details/80151791