-->

java多线程编程之synchronized

2020-12-04 05:59发布

synchronized是用来解决多线程情况下的线程安全问题的,它可以修饰方法也可以修饰语句块 ,

那么什么情况下是线程安全和线程不安全呢 ?

 方法内的变量是线程安全的 , 类的实例变量是非线程安全的 

首先来看一个非线程安全的例子

public class HasSefPrivateNum {
    private int num;
    public void addNum(String userName) throws InterruptedException{
        if("a".equals(userName)){
            num=100;
            System.out.println("a set over");
            Thread.sleep(2000);
        } else {
            num = 200;
            System.out.println("b set over");
        }
        System.out.println("num="+num);
    }
}

public class ThreadA extends Thread {
    private HasSefPrivateNum numRef;
    public ThreadA(HasSefPrivateNum numRef) {
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        try {
            numRef.addNum("a");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

public class ThreadB extends Thread {
    private HasSefPrivateNum numRef;
    public ThreadB(HasSefPrivateNum numRef) {
        this.numRef = numRef;
    }

    @Override
    public void run() {
        super.run();
        try {
            numRef.addNum("b");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        HasSefPrivateNum numRef = new HasSefPrivateNum();
        ThreadA t1 = new ThreadA(numRef);
        t1.start();
        ThreadB t2 = new ThreadB(numRef);
        t2.start();
    }
}
View Code

结果如下,出现非线程安全的问题

a set over
b set over
num=200
num=200

此时synchronized派上用场了,只要在addNum方法上synchronized,就能避免非线程安全问题

public class HasSefPrivateNum {
    private int num;
    public synchronized void addNum(String userName) throws InterruptedException{
        if("a".equals(userName)){
            num=100;
                System.out.println("a set over");
            Thread.sleep(2000);
        } else {
            num = 200;
            System.out.println("b set over");
        }
        System.out.println("num="+num);
    }
}
View Code

结果如下:

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

 那么synchronized是怎么实现同步的呢

1. synchronized用在实例方法上那么它是持有对象锁,而不是把一段代码或方法当作锁,所以必须使用同一个对象当作锁才能达到同步的效果;

2. synchronized用在静态方法上那么它是对当前java文件对应的class类持锁。

首先看下synchronized修饰实例方法

public class Main {
    public static void main(String[] args) {
        HasSefPrivateNum numRef1 = new HasSefPrivateNum();
        HasSefPrivateNum numRef2 = new HasSefPrivateNum();
        ThreadA t1 = new ThreadA(numRef1);
        t1.start();
        ThreadB t2 = new ThreadB(numRef2);
        t2.start();
    }
}
View Code

结果如下,没有发生同步,因为两个线程拥有两个不同对象的锁

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

synchronized解决脏读问题

public class PublicVar {
    private String username="A";
    private String password="AA";
    public synchronized void setValue(String username,String password){
        try {
            this.username = username;
            Thread.sleep(5000);
            this.password = password;
            System.out.println("setValue method thread name=" + Thread.currentThread().getName()
                    +"username:"+username+",password:"+password);
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void getValue(){
        System.out.println("getValue method thread name=" + Thread.currentThread().getName()
                +"username:"+username+",password:"+password);
    }
}

public class ThreadA extends Thread {
    private PublicVar publicVar;
    public ThreadA(PublicVar publicVar) {
        this.publicVar = publicVar;
    }
    @Override
    public void run() {
        super.run();
        publicVar.setValue("B", "BB");
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        PublicVar publicVar = new PublicVar();
        ThreadA t1 = new ThreadA(publicVar);
        t1.start();
        Thread.sleep(2000);
        publicVar.getValue();
    }
}
View Code

结果如下,get方法出现脏读的情况,原因是getValue方法不是同步方法,只需将getValue方法变成同步方法后就可以解决这个问题

getValue method thread name=mainusername:B,password:AA
setValue method thread name=Thread-0username:B,password:BB

对同步方法总结:

1. synchronized方法是持有对象锁,对相同的对象的synchronized方法会进行同步执行

2. 对于同一个对象锁内不同的synchronized方法持有同一个锁,即A线程调用了synchronized方法X,那么其他线程就不能调用其他的synchronized方法,直到synchronized方法x执行完后,但是可以调用其他非synchronized方法

3.synchronized方法支持锁重入,自己可以再次获得自己的内部锁,即当一个线程获得synchronized方法执行权后,在该方法内能调用该对象其他synchronized方法,如果不能重入,将会造成死锁。

4. synchronized方法出现异常,锁自动释放。

5. synchronized方法不具有继承性

 使用同步代码块实现同步

synchronized的另一种用法就是同步代码块,和synchronized方法相比,它有两个有点

1. 它可以只针对方法内需要同步的代码进行同步,缩小同步范围,提高效率。

2. 它可以在同一个类中针对不同对象进行同步,提高效率

 

public class Task {
    private String getData1;
    private String getdate2;
    
    public void doLongTimeTask(){
        try {
            System.out.println("begin task");
            Thread.sleep(3000);
            String privateGetData1 = "返回值1 thread name="+Thread.currentThread().getName();
            String privateGetData2 = "返回值2 thread name="+Thread.currentThread().getName();
            
            synchronized (this) {
                getData1 = privateGetData1;
                getdate2 = privateGetData2;
            }
            System.out.println(getData1);
            System.out.println(getdate2);
            System.out.println("end task");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadA extends Thread {
    private Task task;
    public ThreadA(Task task) {
        this.task = task;
    }
    @Override
    public void run() {
        super.run();
        task.doLongTimeTask();
    }
}

public class ThreadB extends Thread {
    private Task task;
    public ThreadB(Task task) {
        this.task = task;
    }
    @Override
    public void run() {
        super.run();
        task.doLongTimeTask();
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        ThreadA t1 = new ThreadA(task);
        t1.start();
        ThreadB t2 = new ThreadB(task);
        t2.start();
    }
}
View Code

 

测试结果:当一个线程访问object的同步代码块时,另一个线程可以访问该object的非同步代码块。

begin task
begin task
返回值1 thread name=Thread-0
返回值2 thread name=Thread-1
返回值1 thread name=Thread-1
返回值2 thread name=Thread-1
end task
end task

另外synchronized(this)是锁定当前对象的,故和synchronized方法有相同的作用

1. synchronized同步方法

  1)对其他synchronized同步方法或者synchronized(this)同步代码块调用呈现阻塞状态

  2)同一时间内只有一个线程可以执行synchronized同步方法中的代码

2.synchronized(this)同步代码块

  1)对其他synchronized同步方法或者synchronized(this)同步代码块调用呈现阻塞状态

  2)同一时间内只有一个线程可以执行synchronized(this)同步方法中的代码

 

将任意对象作为对象监视器

public class Service {
    public void test(LockObject object){
        synchronized (object) {
            try {
                System.out.println("getLock time="+System.currentTimeMillis()+"thread name="+Thread.currentThread().getName());
                Thread.sleep(3000);
                System.out.println("releaseLock time="+System.currentTimeMillis()+"thread name="+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadA extends Thread {
    private Service service;
    private LockObject object;
    public ThreadA(Service service,LockObject object) {
        this.service = service;
        this.object = object;
    }
    @Override
    public void run() {
        super.run();
        service.test(object);
    }
}

public class ThreadB extends Thread {
    private Service service;
    private LockObject object;
    public ThreadB(Service service,LockObject object) {
        this.service = service;
        this.object = object;
    }
    @Override
    public void run() {
        super.run();
        service.test(object);
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        LockObject object = new LockObject();
        ThreadA t1 = new ThreadA(service,object);
        t1.start();
        ThreadB t2 = new ThreadB(service,object);
        t2.start();
    }
}
View Code

测试结果:同步调用,原因是使用了同一个对象监视器

getLock time=1516085547986thread name=Thread-0
releaseLock time=1516085550987thread name=Thread-0
getLock time=1516085550987thread name=Thread-1
releaseLock time=1516085553987thread name=Thread-1

如果不使用同一个对象监视器,则会出现异步的情况

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Service service = new Service();
        LockObject object1 = new LockObject();
        LockObject object2 = new LockObject();
        ThreadA t1 = new ThreadA(service,object1);
        t1.start();
        ThreadB t2 = new ThreadB(service,object2);
        t2.start();
    }
}
View Code
getLock time=1516085648021thread name=Thread-0
getLock time=1516085648021thread name=Thread-1
releaseLock time=1516085651021thread name=Thread-0
releaseLock time=1516085651022thread name=Thread-1

最后要理解synchronized,其实最主要的是理解synchronized方法和synchronized代码块使用的是什么对象监视器,它们是通过锁定对象监视器来实现同步功能的

1. synchronized同步实例方法和synchronized(this)的对象监视器是对象,它将持有对象锁

2. synchronized同步静态实例方法和synchronized(class)的对象监视器是class文件,它将持有Class锁

 

标签: