synchronized简单理解

这是我个人对synchronized简单理解,我们都知道多线程在访问同一个资源时,为了保证数据的安全性,我们一般使用synchronized去给资源加锁,但是锁在加在哪里?线程怎么访问该资源,这些,都是摆在我们面前的问题,我们必须要正视这个问题,因而,经过这么多天的研究,我才慢慢地品出其中的道理,如果有什么不对的地方,希望大家多多指教。

再说synchronized之前,我们需要说说volatile这个保留字,它的意思是“可变的,不稳定的”。为什么说这个保留字呢?因为java内存模型中的可见性、原子性、有序性。每个线程都有各自的内存空间,如果每个线程都去操作主线程的某个变量,当它会吧该变量装载到自己的内存中,而其他线程并不知道,该变量已经改变了。因为每个线程都是相对独立的,并不知道还有别的线程存在。因而,当一个线程对主内存的数据进行操作时,在操作该数据时,必须为该数据进行加锁,不能让别的线程去访问,直到该线程使用完该数据,释放锁后,别的线程才能访问。

因而,我们可以使用volatile去修饰类的成员变量(属性),不能修饰类的方法和类。一旦用这个保留字去修饰的话,那么每个线程去使用完这个变量之后,会立即将其送还给主内存,供其他的线程去使用,当然,我们也可以使用synchronized去修饰,但synchronized只能修饰修饰类的方法和同步块,而不能修饰变量和类。但是synchronized的开销太大,因而,可以在多个线程访问的成员变量上使用volatile,但是要访问的变量已在synchronized代码块中,或者为常量时,不必使用。

volatile使用的三个说明:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)它无法保证某个成员属性(资源)的原子性。
2)禁止进行指令重排序,进而保证java内存模型中的有序性。

ps:java是面向对象的语言,我会以面向对象的思想,去分析这个问题。

同步的前提是至少有一个以上的线程去访问同一个资源,就像是购买火车票一样,很多人同时去买火车票这个对象,我们不去在乎火车票对象的其他属性,只在乎火车票对象的数量这个属性。我们怎么才能去访问这个属性呢?我们都知道多线程实现的方式,一般有两种情况,一时继承Thread类,然后重写其run方法,另外一种,就是实现runnable接口,重写run方法,并通过线程的代理,来执行该实现类。

线程怎么访问火车票对象的资源?我们先看一段普通代码:

package com.zbygroup.booklib.config;

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class DBConnection {

    /**
     * 通过配置文件configurationl.xml 创建SqlSessionFactory
     * 
     * @return
     * @throws IOException
     */
    private static SqlSessionFactory getsqlSessionFactory() {
        String resource="configuration.xml";
        InputStream inputStream=null;
        try {
            inputStream =Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new SqlSessionFactoryBuilder().build(inputStream);
    }

    /**
     * 通过SqlSession 获取studentmapper.xml文件中的id值来获取SQL语句,执行SQL语句
     * 
     * @return
     * @throws IOException
     */
    public static SqlSession getSqlSession() {      
        return  getsqlSessionFactory().openSession();
    }
}

上面一段代码有什么用呢?我们需要SqlSessionFactory来创建一个会话对象,通过会话对象openSession()来获取SqlSession对象,通过SqlSession来获取Connection对象,而Connection对象正是连接数据库的对象。我们通过私有方法来创建SqlSessionFactory对象,但我们怎么获取SqlSession对象,从而获取Connection对象,因而,我们可以在另一个类中,通过DBConnection调用静态方法SqlSession 来获取该Connection对象,比如以下的代码:

public class ImpleDao<T> implements BaseDao<T> {

    @Override
    public int insert(String mapper, T t) {
        SqlSession session = DBConnection.getSqlSession();
        int line = session.insert(mapper, t);
        SQLSessionExecute.executeCommitClose(session);
        return line;
    }
}

通过DBConnection.getSqlSession();来获取Connection对象,也就是,获取连接数据库的资源。因而,我们常说的获取资源,一般是调用对象的方法或其他,来返回指向该资源(对象)的引用。

我们也可以结合db.properties来说明,我们通过方法来返回properties对象,也就是导入properties中的数据,也就是说获取properties的资源。

如果我们把这种思想放进线程中,我们就可以清晰的发现,所谓的多个线程访问同一资源,也就是获取该资源的引用。通过类的实例对象来调用对象的方法,来获取对象的属性值(也是成员变量的值),从而操作属性的值,这就是所谓的访问资源。

那么问题又来了,我们在哪里使用对象的资源(方法),不论是继承Thread类,还是实现runnable接口,其内部必定要重写run方法。这是线程的执行体,我们千万不要调用这个run方法,如果调用了run方法,你会发现,该线程和普通分支没什么两样。如果我们不是使用线程,一般是线性的执行程序,我们如果使用了多个线程,每个线程有自己的、栈、堆、程序计数器等,而main函数就是一个主线程。

因而,我们可以在run方法中,调用我们需要的对象的方法,来进行设置其属性值,或返回需要的属性值,或进行数据的传递。这就像我们在普通方法中,调用其他的方法,来返回或传递对象的引用,间接地说是获取资源。

但唯一摆在我们面前的是,我们在run方法中,为什么要调用某对象的方法?我们调用这个方法,能够返回给我们什么对象(内容),或者去设置什么对象(内容)?如果我明白了这些,我们就好操作了。

我们要把锁(synchronized)加在获取某个对象的属性的方法上。我们为什么要在对象中加锁,而不是在线程中加锁?因为,如果某个线程访问该对象,或者该对象的方法,就把该对象给锁住,不让其他线程去访问。因为每个线程都是相互独立的个体,彼此不知道除了自己之外,还有其他线程的存在,因而,每个线程都有访问该资源的执行体,如果我们不告诉其他线程,现在已经有一个线程获取该对象了,正在通过该对象的方法或属性,获取该对象的属性值(也可以说资源,属性值就是资源),或在传递其他对象的属性值(也可以说资源,属性值就是资源)。因而,一旦有线程访问该对象(资源),我们就把该对象锁起来,知道线程执行完毕,(比如通过当前线程的挂起,例如Thread.sleep(millis);该线程处于中断状态,其他就绪状态的线程,立即抢占访问该对象,该对象就立即加锁了,其他线程就就无法访问了,这就好比在一般情况下,很多精子去访问卵细胞,一旦有一个精子进入到卵细胞,卵细胞立即发生透明带反应,阻止其他精子的进入卵细胞,这就保证受精的安全性。这难道不像是线程的加锁吗?精子好比很多线程,卵细胞表示唯一的资源,很多线程访问该资源(对象)时,该对象就必须立即加锁,直到该线程离开,其他线程才有机会访问,因而,该线程离开后,也可能立即再次访问该资源。这就是我们为什么要在唯一的对象中加锁,而不是在线程中加锁的原因。

既燃对象加锁了,线程如何访问该对象呢?我们可以通过组合的方式,比如:
这里写图片描述

我们可以通过线程的构造器,初始化该线程对象时,就把具有资源的类对象的实例加载进来,这和普通类与类调用的组合关系,没有什么区别的。

说了这么多,我们来观察一个例子,就应该没什么问题了:

package mythread;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyThreadWithSynchronized {

    public MyThreadWithSynchronized() {

    }

    public static void main(String[] args) throws InterruptedException {
        // 创建没有synchronized对象锁唯一的资源
        System.out.println("创建没有synchronized对象锁唯一的资源");

        MyThreadWithSynchronized myThread = new MyThreadWithSynchronized(); // 创建父对象

        // 创建子对象
        MyThreadWithSynchronized.VirtureResourceWithSynchronized resourceWithSynchronized = myThread.new VirtureResourceWithSynchronized();

        // 在主线程main创建三个线程,来查看其之顺序
        System.out.println("在主线程main创建三个线程,来查看其之顺序");

        Thread threada = new Thread(myThread.new Threada(
                resourceWithSynchronized)); // 新建线程
        Thread threadb = new Thread(myThread.new Threadb(
                resourceWithSynchronized));
        Thread threadc = new Thread(myThread.new Threadc(
                resourceWithSynchronized));

        // 启动线程,使线程达到就绪态
        threadc.start();
        Thread.sleep(1000);
        threada.start();
        Thread.sleep(1000); // 主线程挂起一秒钟
        threadb.start();


    }

    /**
     * 
     * 
     * @author baoya
     * 
     */
    class VirtureResourceWithSynchronized {

        private String name;

        public void setName(String name) {
            this.name = name;
        }

        public synchronized void addName(int i) {
            switch (i) {
            case 0:
                System.out.println(this.name + "挂起前的时间:"
                        + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                System.out.println(this.name);
                try {
                    Thread.sleep(2000);
                    System.out.println(this.name
                            + "抢占cpu挂起后的时间:"
                            + new SimpleDateFormat("HH:mm:ss")
                                    .format(new Date()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                break;
            case 1:
                System.out.println(this.name + "挂起前的时间:"
                        + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                System.out.println(this.name);
                try {
                    Thread.sleep(2000);
                    System.out.println(this.name
                            + "抢占cpu挂起后的时间:"
                            + new SimpleDateFormat("HH:mm:ss")
                                    .format(new Date()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                break;
            default:
                System.out.println(this.name + "挂起前的时间:"
                        + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                System.out.println(this.name);
                try {
                    Thread.sleep(2000);
                    System.out.println(this.name
                            + "线程 b 抢占cpu挂起后的时间:"
                            + new SimpleDateFormat("HH:mm:ss")
                                    .format(new Date()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                break;
            }
        }

    }

    /**
     * 
     * 利用组合关系去访问VirtureResourceWithSynchronized对象
     * 
     * 也就是我们常说的去访问该资源
     * 
     * @author 
     * 
     */
    class Threada implements Runnable {

        private VirtureResourceWithSynchronized resourceWithSynchronized;

        public Threada(VirtureResourceWithSynchronized resourceWithSynchronized) {

            this.resourceWithSynchronized = resourceWithSynchronized;

        }

        public void run() {
            resourceWithSynchronized.setName("线程a");
            resourceWithSynchronized.addName(0);
        }
    }

    /**
     * 同理,定义线程b
     * 
     * @author 
     * 
     */
    class Threadb implements Runnable {

        private VirtureResourceWithSynchronized resourceWithSynchronized;

        public Threadb(VirtureResourceWithSynchronized resourceWithSynchronized) {
            this.resourceWithSynchronized = resourceWithSynchronized;
        }

        public void run() {
            resourceWithSynchronized.setName("线程b");
            resourceWithSynchronized.addName(1);
        }
    }

    /**
     * 同理,定义线程c
     * 
     * @author
     * 
     */
    class Threadc implements Runnable {

        private VirtureResourceWithSynchronized resourceWithSynchronized;

        public Threadc(VirtureResourceWithSynchronized resourceWithSynchronized) {
            this.resourceWithSynchronized = resourceWithSynchronized;
        }

        public void run() {
            resourceWithSynchronized.setName("线程c");
            resourceWithSynchronized.addName(2);
        }

    }
}

通过这个例子可以看出来,具有唯一资源的类VirtureResourceWithSynchronized的实例对象resourceWithSynchronized就是作为线程的属性,这就是我们常说的软件工程中的组合关系,我们在创建线程对象时,就把对象resourceWithSynchronized放在线程中的构造器中。我们为什么使用组合关系,这样使用比较灵活。我们可以在线程体中操作对象的的属性和方法等。

由此,我们回到最初的问题,如果是在购票系统中,我们把人当做一个线程类,该线程类拥有很多属性,我们暂不关注其他属性。它可以在同一时刻,去问火车票这个对象的车票属性,然后火车票这个对象被锁定,其他人(线程对象)就不能购票,也就是处于就绪状态,当人这个线程购买成功后,人这个线程即被挂起,其他人(线程)可以去抢占CPU,然后车票这个对象被锁定,如此往复,直到没票为止。

因此,人可以定义为线程对象,而火车票可以定义为火车票对象。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网全栈开发实战

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值