zoukankan      html  css  js  c++  java
  • java多线程编程之synchronized

    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锁

  • 相关阅读:
    踩坑纪录——Lombok加Builder注解后mybatis无法识别字段正确类型
    安装node
    PostgreSQL DISTINCT ON
    RabbitMQ安装到使用入门
    springboot整合rabbitMQ时遇到的消息无法入列问题
    不同版本springboot上传文件大小设置
    thymeleaf报错元素类型必须由匹配的结束标记终止
    不同版本springboot端点开启方法
    mybatis匹配字符串的坑
    杂记
  • 原文地址:https://www.cnblogs.com/lostyears/p/8295593.html
Copyright © 2011-2022 走看看