具体的记录synchronized关键的各种使用方式,注意事项。感觉一步一步跟我来都可以看懂滴

大致是按照以下思路进行书写的。黑体字可以理解为结论,

1.synchronized锁的是什么?

2.synchronized能够锁住所有方法吗?

3.synchronized能够用来锁住一个方法之中的部分代码吗?

4.synchronized能够锁住除了this以外的其他对象吗?有什么用?有什么需要注意的?

------------------------------------------------------------------------------------------正文------------------------------------------------------------------------------------------

1.synchronized锁的是什么?

首先,要明白非线程安全存在于实例变量之中,即大家都可以更改的变量,私有变量不存在线程安全问题。那么解决非线程安全问题,我们需要用用到 synchronized 来给某一个方法或者对象上锁,避免交叉访问的现象出现。那么synchronized到底锁的是什么呢?

先说结论,锁的是一个对象,一个类的实例,而不是将一个方法锁起来,如果想要在加上synchronized关键字之后同步运行,那多个线程访问的必须是同一个对象,这是锁的前提。也可以理解为加上synchronized关键字之后同步访问的前提是多个线程访问的是同一个资源,相当于他们是资源共享的。

用一个例子来说明:

twoNum.java是我们的测试类,里面有带锁的addNum方法,根据目前的线程名字来赋予num不同的值,a线程为100,b线程为200

MyThread.java:是自定义线程类,用于,run方法运行twoNum对象的addNum()方法

test.java:main函数

twoNum.java:

package 第二章;

public class twoNum {
    ;
    synchronized public void addNum(){
        try{
            if(Thread.currentThread().getName().equals("a")){
                num=;
                System.out.println("a线程设置num的值");
                Thread.sleep();
            }else{
                num=;
                System.out.println("b线程设置num的值");
            }
            System.out.println(Thread.currentThread().getName()+" "+num);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

MyThread.java,

package 第二章;
import 第二章.twoNum;

public class MyThread extends Thread {
        private twoNum twonum;
        public MyThread(twoNum temp){
            super();
            this.twonum=temp;
        }
        public void run(){
            super.run();
            twonum.addNum();
        }
}

test.java:

package 第二章;

public class test {
    public static void main(String[] args){
        twoNum twonum = new twoNum();
        MyThread threadA = new MyThread(twonum);
        threadA.setName("a");
        MyThread threadB = new MyThread(twonum);
        threadB.setName("b");
        threadA.start();
        threadB.start();
    }
}

运行上述代码,不出意料的,是线程安全的,并同步运行,因为我们给twonum对象的addNum方法上了锁,并且线程A,B是用一个twoNum初始化的,

更改test.java代码如下,用两个twoNum对象实例分别给A,B线程来初始化:

package 第二章;

public class test {
    public static void main(String[] args){
        twoNum twonum1 = new twoNum();
        twoNum twonum2 = new twoNum();
        MyThread threadA = new MyThread(twonum1);
        threadA.setName("a");
        MyThread threadB = new MyThread(twonum2);
        threadB.setName("b");
        threadA.start();
        threadB.start();
    }
}

运行结果如下:

可以看到现在A,B两个线程执行顺序虽然是异步的,但是数据仍然是正常的。为什么呢?很明显,因为有两个twoNum对象,所以有两个对象锁,A,B线程持有不同的锁,所以他们在访问时,访问的是不同对象,那当然能异步运行了,同时也有两个num变量,从属于不同的线程,A线程并不能够更改B线程当中的num变量,所以数据也是正常的。

上面的例子看得出,锁 关键字锁的是对象,

2.synchronized能够锁住所有方法吗?

那么synchronized锁的是整个对象里面的所有方法,还是怎么样呢?

先说结论:synchronized只能够锁住一个对象当中带锁的方法,并不是全部方法。可以理解为局部同步。

这就意味着假如A线程拿到了一个对象的锁,正在访问该对象之中的一个同步方法,这时候B线程也尝试拿到同一个对象锁,如果B线程访问的是该对象当中不带锁的方法,那么久能够拿到该锁并访问,如果访问的是该对象之中带锁的方法,那么B线程无法拿到该锁,只能等A线程释放锁之后才能拿到锁。

下面是一个例子:

修改twoNum.java如下:增加了一个没有锁的方法addNum2

package 第二章;

public class twoNum {
    ;
    synchronized public void addNum(){
        try{
            if(Thread.currentThread().getName().equals("a")){
                num=;
                System.out.println("a线程设置num的值");
                Thread.sleep();
            }else{
                num=;
                System.out.println("b线程设置num的值");
            }
            System.out.println(Thread.currentThread().getName()+" "+num);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    public void addNum2(){
        try {
            System.out.println(Thread.currentThread().getName() + "正在访问");
            Thread.sleep();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("访问结束");
    }
}

修改MyThread.java文件如下:两个线程,一个运行有锁的方法,另一个运行没有锁的

package 第二章;
import 第二章.twoNum;

class MyThread1 extends Thread {
        private twoNum twonum;
        public MyThread1(twoNum temp){
            super();
            this.twonum=temp;
        }
        public void run(){
            super.run();
            twonum.addNum();
        }
}
class MyThread2 extends Thread {
    private twoNum twonum;
    public MyThread2(twoNum temp){
        super();
        this.twonum=temp;
    }
    public void run(){
        super.run();
        twonum.addNum2();
    }
}

test.java:

public class test {
    public static void main(String[] args){
        twoNum twonum = new twoNum();
        MyThread1 threadA = new MyThread1(twonum);
        threadA.setName("a");
        MyThread2 threadB = new MyThread2(twonum);
        threadB.setName("b");
        threadA.start();
        threadB.start();
    }
}

运行结果如图:

可以看到A对象拿到了锁,但是B线程仍然在同时访问了没有锁的addNum2()方法,证明了上述结论,其他线程可以在对象已经被占用的情况下可以异步访问同一个对象的没有锁的方法,但是有锁的方法却不行。有锁的不行这里不演示了,很简单。

理解了这一个概念,就可以解决有时候会碰到的脏读现象,

脏读很好理解,比如你现在执行一个setValue()函数,该函数更改两个值,username和password,当更改完username还没有更改password的时候,调用了getValue()方法获取这两个变量的值,那获取到的username是已经更改过的,但是password是没有更改的,这就出现了脏读。

这时候,运用我们所掌握的知识,给setValue()和getValue()方法都加上锁,这样子在setValue()执行结束之前,getValue()就无法拿到对象锁获取信息,这就解决了脏读问题。

接下来记录几个结论,比较容易理解就不展示例子了,只做记录:

1.出现异常,锁自动释放

2,同步方法不具有继承性,即父类有一个同步方法,那么他的子类如果有一个多态方法,那么子类中的方法想要同步必须加上synchronized关键字,不能继承;

3.synchronized能够锁住一个方法之中的部分代码吗?

可以看到,前面说的都是给整个方法上锁,但是想一下, 如果这个方法的执行时间会很久,A线程先拿到了锁,B线程如果要执行有锁的方法只能等待它执行完再执行,那么效率会很低,比如下面的例子:

twoNum.java:模拟一个任务

package 第二章;

public class twoNum {
    private String data;
    synchronized public void addNum(){
        try{
            System.out.println("开始");
            Thread.sleep();
            data = Thread.currentThread().getName();
            System.out.println("结束");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

MyThread1.类,线程类:记录执行时间

class MyThread1 extends Thread {
    long time1;
    long time2;
    private twoNum twonum;
        public MyThread1(twoNum temp){
            super();
            this.twonum=temp;
        }
        public void run(){
            super.run();
            time1 = System.currentTimeMillis();
            twonum.addNum();
            time2=System.currentTimeMillis();
        }
}

test.java:主函数

package 第二章;

public class test {
    public static void main(String[] args){
        twoNum twonum = new twoNum();
        MyThread1 threadA = new MyThread1(twonum);
        threadA.setName("a");
        MyThread1 threadB = new MyThread1(twonum);
        threadB.setName("b");
        threadA.start();
        threadB.start();
        try{
            Thread.sleep();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("A线程花费时间:"+(threadA.time2-threadA.time1));
        System.out.println("B线程花费时间:"+(threadB.time2-threadB.time1));
    }
}

运行结果如下:

可以看到B线程等待了3秒才执行,相当于多花费了3秒的时间。

那么怎么解决呢?使用synchronized同步代码块来解决

首先synchronized同步代码块就是说现在不给整个方法上锁,只给方法之中的部分关键代码上锁,这样当A线程拿到一个对象的锁时,B线程仍然可以访问相同对象之中没有上锁的代码块,但是不能访问上锁的代码块。简单来说,代码块锁synchronized锁的是一个对象的局部代码块,其他线程仍然可以在没有锁的情况下访问非同步代码块。

改变上述twoNum.java的代码,如下:

package 第二章;

public class twoNum {    private String data;    public void addNum(){        try{            System.out.println("开始");            Thread.sleep(3000);            String temp = Thread.currentThread().getName();            synchronized(this) {                System.out.println("线程"+temp+"赋值当中");                data=temp;                System.out.println("线程"+temp+"赋值结束");            }            System.out.println("结束");        }catch (InterruptedException e){            e.printStackTrace();        }    }}

只锁住关键的data赋值代码,其他代码块并不用锁,运行如下:

可以看到,开始结束时异步执行的,但是赋值却是同步的。证明了我们上面的结论,只同步执行synchronized锁住的代码块。

4.synchronized能够锁住除了this以外的其他对象吗?有什么用?有什么需要注意的?

你可能注意到了,上面synchronized(this) 里面锁住了this,这个意思就是说取到当前对象的锁,那么这个this能不能换成其他的对象呢?可以,他可以是任何对象,这个对象我们就叫做对象监听器。那么不同的对象监听器y有什么区别呢?

首先对象监听器总体分为两类:

1.this,即自身

2.非this对象,一般是实例变量或者方法的参数

那么第二种有什么用处呢?假设这种情况,现在有一个类,里面有很多个synchronized方法,执行起来确实是同步的,但是会受到阻塞。不过如果我们使用synchronized(非this对象)同步代码块来锁住一些代码块,这些代码块和其他被锁住的方法就是异步的了,因为他们锁的是不同对象,这样就提升了效率。

简单一句话,synchronized(非this对象)可以让锁住的代码块和其他方法异步执行,下面用程序进行演示:
twoNum.java:两个方法,一个上锁,另一个是synchronized(非this对象)同步代码块

package 第二章;

public class twoNum {
    private String anything = new String();
    public void addNum(){
        try{
            synchronized(anything) {
                System.out.println("线程"+Thread.currentThread().getName()+"开始");
                Thread.sleep();
                System.out.println("线程"+Thread.currentThread().getName()+"结束");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    synchronized public void addNum2(){
        try{
            System.out.println("线程"+Thread.currentThread().getName()+"开始");
            Thread.sleep();
            System.out.println("线程"+Thread.currentThread().getName()+"结束");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

MyThread.java:定义了两个线程类,一个运行addNum()另一个运行addNum2()

package 第二章;
import 第二章.twoNum;

class MyThread1 extends Thread {
    long time1;
    long time2;
    private twoNum twonum;
    public MyThread1(twoNum temp){
        super();
        this.twonum=temp;
    }
    public void run(){
        super.run();
        twonum.addNum();
    }
}
class MyThread2 extends Thread {
    private twoNum twonum;
    public MyThread2(twoNum temp){
        super();
        this.twonum=temp;
    }
    public void run(){
        super.run();
        twonum.addNum2();
    }
}

test.java:

public class test {
    public static void main(String[] args){
        twoNum twonum = new twoNum();
        MyThread1 threadA = new MyThread1(twonum);
        threadA.setName("a");
        MyThread2 threadB = new MyThread2(twonum);
        threadB.setName("b");
        threadA.start();
        threadB.start();
    }
}

运行结果如下:

可以看到他们是异步执行的,就是因为他们锁的是不同的对象。

 不过要注意两点

1.java有字符串常量池,也就是如果有两个String对象,但是他们的值是相同的,那么当他们作为对象监听器时,他们是被看做同一个锁的。

 2.synchronized如果加到一个静态方法上,那么它锁的就不是一个对象,而是整个类了。这时候可以理解为只锁了静态方法,该类的实例对象的锁还是可以正常拿到的。

下面看看java多线程死锁:

简单理解就是多个线程都在互相等待对方释放锁然后执行,双方互相持有对方的锁,这一般是程序bug,这块简单理解一下就行。

												





											

(二)对象以及变量的并发访问--synchronized的使用细节,用法的更多相关文章

  1. Java多线程编程核心技术(二)对象及变量的并发访问

    本文主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题.阅读本文应该着重掌握如下技术点: synchronized对象监视器为O ...

  2. 对象和变量的并发访问synchronized解析以及死锁分析排查

    一.synchronized java并发编程中存在“非线程安全"问题.“非线程安全"是指发生在多个线程对同一个对象中的实例变量并发访问时,产生的”脏读“现象,使用synchron ...

  3. Java多线程编程核心技术---对象及变量的并发访问(一)

    synchronized同步方法 "非线程安全"其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是"脏读",也就是渠道的数据其实是被更改 ...

  4. 对象及变量的并发访问(同步方法、同步代码块、对class进行加锁、线程死锁)&内部类的基本用法

    主要学习多线程的并发访问,也就是使得线程安全. 同步的单词为synchronized,异步的单词为asynchronized 同步主要就是通过锁的方式实现,一种就是隐式锁,另一种是显示锁Lock,本节 ...

  5. Java多线程编程核心 - 对象及变量的并发访问

    1.什么是“线程安全”与“非线程安全”? “非线程安全”会在多个线程对同一对象总的实例变量进行并发访问时发生,产生的后果是“脏读”,也就是取到的数据其实是被更改过的. “线程安全”是以获得的实例变量的 ...

  6. 《JAVA多线程编程核心技术》 笔记:第二章:对象及变量的并发访问

    一.基本概念1.安全的变量和不安全的变量2.脏读的理解3.锁重入:4.锁释放5.死循环:二.synchronized 的理解:三.synchronized 同步方法3.1 同步方法不具有继承性.3.2 ...

  7. Java多线程编程核心技术---对象及变量的并发访问(二)

    数据类型String的常量池特性 在JVM中具有String常量池缓存的功能. public class Service { public static void print(String str){ ...

  8. java多线程编程核心技术(二)--对象及变量的并发访问

    1.方法内部的私有变量是线程安全的,实例变量是线程不安全的. 2.A线程先持有object对象的lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法. 3.A ...

  9. Java中,对多线程访问同一变量(并发访问)的认识

    在Java中,如果启动多个线程对同一个对象或者变量时候,在没有安全保护前提下有可能会抛出并异常 java.util.ConcurrentModificationException 当方法检测到对象的并 ...

  10. Java-对象及变量的并发访问小结

    1)多线程环境下,方法内的变量是线程安全的 2)多个线程同时处理一个实例,这个实例内的变量是不安全的 3)不同线程中注入同一个类的不同实例,实例中的变量是安全的 4)Synchronized获取到的锁 ...

随机推荐

  1. react native 之 react-native-image-picke的详细使用图解

    最近需要在react native项目中集成相机和相册的功能,于是在网上找了一个好用的第三方插件:react-native-image-picke. 该插件可以同时给iOS和Android两个平台下使 ...

  2. OPENGL之定时器

    定时器一般用于控制隔一段时间调用某函数 函数原型glutTimerFunc(unsigned int msecs,void(*func)(int value),int value) 说明:msecs为 ...

  3. “FAIL - Deployed application at context path but context failed to start”错误的解决

    Netbeans调试错误,出现以下信息,无法启动浏览器调试. Attached JPDA debugger to localhost:tomcat_shared_memory_id 正在取消部署... ...

  4. [D3] 4. d3.max

    how to use d3.max to normalize your dataset visually within the specific bounds of a variable domain ...

  5. Linux Increase The Maximum Number Of Open Files / File Descriptors (FD)

    How do I increase the maximum number of open files under CentOS Linux? How do I open more file descr ...

  6. 【ASP.NET MVC路由测试+性能调试工具】

    http://getglimpse.com https://github.com/Glimpse/Glimpse 百度网盘: http://pan.baidu.com/s/1jHuTtKa

  7. winform最小化到托盘

    1.拖取NotifyIcon控件.将该控件的visible设成false. 2.指定NotifyIcon的Icon(很重要,否则最小化后找不到窗口). 3.找到window的SizeChanged事件 ...

  8. 如何验证所做的AIX系统备份是否可用

    --如何验证所做的AIX系统备份是否可用 ----------------------------------2013/11/15 系统备份(mksysb)的介质可以是磁带,也可以是CD和DVD.想要 ...

  9. awk数组结合+=统计题

    awk增加统计列值为增加列数或进行运行结果统计,使用符号 + =.增加的结果赋给符号左边变量值,增加到变量的域在符号右边.例如将 $ 1加入变量total,表达式为toatl+=$1.列值增加很有用. ...

  10. python实现 双向循环链表

    最近身边的朋友在研究用python来实现数据结构.遇到一个问题就是双向循环链表的实现,改指向的时候总是发蒙. 我自己尝实现了一个python的双向循环链表.附上代码,希望对大家有帮助. 如果不懂什么是 ...