(二)对象以及变量的并发访问--synchronized的使用细节,用法

具体的记录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 {
    private int num=0;
    synchronized public void addNum(){
        try{
            if(Thread.currentThread().getName().equals("a")){
                num=100;
                System.out.println("a线程设置num的值");
                Thread.sleep(2000);
            }else{
                num=200;
                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 {
    private int num=0;
    synchronized public void addNum(){
        try{
            if(Thread.currentThread().getName().equals("a")){
                num=100;
                System.out.println("a线程设置num的值");
                Thread.sleep(2000);
            }else{
                num=200;
                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(1000);
        }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(3000);
            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(6000);
        }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(3000);
                System.out.println("线程"+Thread.currentThread().getName()+"结束");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    synchronized public void addNum2(){
        try{
            System.out.println("线程"+Thread.currentThread().getName()+"开始");
            Thread.sleep(3000);
            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,这块简单理解一下就行。



原文地址:https://www.cnblogs.com/eenio/p/11348861.html

时间: 2024-10-12 06:59:30

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

java多线程(对象和变量的并发访问)

在现实开发中,我们写的线程肯定会有不同的实例在执行,此时就可能会出现"非线程安全问题",非线程安全就是:多个线程对同一个对象中的实例变量进行并发访问时候,有可能A和B线程同时读取到数据,先后进行更改,此时,该变量就不是我们期望的数据,也就是通常所说的"脏数据" 实例变量非线程安全 需要注意的是,方法中的变量是不存在非线程安全问题的,这是因为方法内部的变量都是私有的. 如果多个线程共同访问了一个对象中的实例变量,则可能会出现线程安全问题.看下面代码: public c

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

1.什么是“线程安全”与“非线程安全”? “非线程安全”会在多个线程对同一对象总的实例变量进行并发访问时发生,产生的后果是“脏读”,也就是取到的数据其实是被更改过的. “线程安全”是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象. 2.非线程安全例子?怎么解决? 非线程安全 package com.jvm.thread; public class HasSelfPrivateNum { private int num =  0; public void add(String usern

对象及变量的并发访问一

一.多个线程操作一个对象实例 当两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则有可能会出现“非线程安全问题”. 1 package concurrent; 2 /** 3 * 测试不同线程操作同一个实例变量线程安全问题 4 * @author foolishbird_lmy 5 * 6 */ 7 class ThePrivateNumber{ 8 private int num = 0; 9 10 public synchronized void addI(St

第二章 对象以及变量的并发访问

synchronied 对象监视器为Object时的使用,或者监视器为Class时的使用. 方法中的变量不存在非线程安全问题,永远都是线程安全的,这是方法内部的变量是私有的特性造成的. 1 synchronized的使用 在方法前加关键字synchronized即可. 1)A线程先持有object对象的Lock锁,B线程可以异步的方式调用object对象中的非synchrionized类型的方法. 2)A线程先持有object对象的Lock锁,B线程如果在这时调用了object对象中的synch

Java多线程编程(二)对象及变量的并发访问

一.synchronized同步方法 1.方法内的变量为线程安全 2.实例变量非线程安全 3.多个对象多个锁 4.synchronized方法与锁对象 5.脏读 6.synchronized锁冲入 7.出现异常,锁自动释放 8.同步不具有继承性 二.synchronized同步语句块 1.synchronized方法的弊端 2.synchronized同步代码块的使用 3.用同步代码块解决同步的弊端 4.一半异步,一半同步 5.synchronized代码块间的同步性 6.验证同步synchro

第二章:对象及变量的并发访问

为什么要使用多线程编程?什么时候会出现线程安全问题? 在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的的资源:一个变量.一个对象.一个文件.一个数据库表等,而当多个线程同时访问同一个资源的时候,就会存在一个问题: 由于每个线程执行的过程是不可控的,所以很可能导致最终的结果与实际上的愿望相违背或者直接导致程序出错. 举个简单的例子: 现在有两个线程分别从网络上读取数据,然后插入一张数据库表中,要求不能插入重复的数据. 那么必然在插入数

多线程--对象及变量的并发访问

1 . 多个线程访问多个对象JVM会创建多个锁.2 . 静态方法是以类为单位进行同步的--对于同一个类中的所有静态方法,在同一时间内,只允许有一个线程执行其中的一个静态方法,其余想要进入这些方法的线程都必须挂起等待.非静态方法是以对象为单位进行同步的.3 .假设现有两个线程A和B,一个object对象,当线程A调用object对象的一个同步方法M1时,线程A就获得了M1方法所在对象的锁,所以其他线程必须等待线程A执行完毕之后才能调用方法M1,如果线程B调用object的同步方法M2,必须等待线程

第二章:对象及变量的并发序言

本章主要介绍了java多线程中的同步,也就是如何在java语言中写出线程安全的程序. 如何在java语言中解决非线程安全的相关问题. 设计知识点: synchronized对象监视器为Object时的使用. synchronized对象监视器为Class时的使用. 非线程安全是如何出现的. 关键字volatile的主要作用. 关键字volatile与synchronized的区别及使用情况.

[转]高并发访问下避免对象缓存失效引发Dogpile效应

避免Redis/Memcached缓存失效引发Dogpile效应 Redis/Memcached高并发访问下的缓存失效时可能产生Dogpile效应(Cache Stampede效应). 推荐阅读:高并发下的 Nginx 优化方案 http://www.linuxidc.com/Linux/2013-01/78791.htm 避免Memcached缓存的Dogpile效应 Memcached的read-through cache流程:客户端读取缓存,没有的话就由客户端生成缓存.Memcached缓