资讯专栏INFORMATION COLUMN

java多线程(4)synchronized的作用

Pandaaa / 700人阅读

摘要:在多线程并发的情况下,有时就涉及到对于同一资源的读写,如果不进行一些处理,容易出现数据混乱,结果和实际不一致等问题。

在多线程并发的情况下,有时就涉及到对于同一资源的读写,如果不进行一些处理,容易出现数据混乱,结果和实际不一致等问题。java中可以使用synchronized关键字对资源锁定。

synchronized的用法

synchronized有2种用法:
1.修饰代码块,以某个对象为锁,锁的范围是指定的代码块。
2.修饰方法,其实也可以等价于修饰代码块,比如修饰普通方法:

synchronized void doxx(){
//.........
}

等价于

void doxx(){
    synchronized (this){
    //.........
    }
}

以当前实体为锁对象,整个方法都是在代码块中。也能修饰静态方法:

public class Test{
    static synchronized void doxx(){
    //.........
    }
}

等价于

public class Test{
    static void doxx(){
        synchronized (Test.class){
        //.........
        }
    }
}
synchronized 修饰代码块

先举一个反例demo:

import java.util.List;
import java.util.Random;

public    class Thread1 extends Thread{
    private List list;
    public Thread1(List list) {
        this.list=list;
    }
    @Override
    public void run() {
        try {
            for(int i=0;i<100;i++){
                Thread.sleep(10);//模拟处理一些业务,这样也更容易重现问题
                int randon = new Random().nextInt();
                list.add(randon);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
    public static void main(String[] args) throws InterruptedException {
        List list = new ArrayList<>(1000);
        Thread1 t1 = new Thread1(list);
        t1.start();
        Thread1 t2 = new Thread1(list);
        t2.start();
        t1.join();
        t2.join();
        System.out.println("size="+list.size());
    }

执行结果:

size=162

这个结果是不确定,每次执行都可能不一样。demo里启动了2个线程,每个执行100次,按理list的数据量应该会是200个。这个就是本文开始提到的,多个线程读写同一个对象时,发生了数据异常。那么我们再用synchronized对demo进行小小的改造。

import java.util.List;
import java.util.Random;

public    class Thread1 extends Thread{
    private List list;
    public Thread1(List list) {
        this.list=list;
    }
    @Override
    public void run() {
        try {
            for(int i=0;i<100;i++){
                Thread.sleep(10);//模拟处理一些业务,这样也更容易重现问题
                int randon = new Random().nextInt();
                synchronized (list) {//就只改这个地方
                    list.add(randon);
                }
            
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

main方法保持不变,结果如下:

size=200

可见使用synchronized关键字后,代码的执行结果恢复了正常。

synchronized修饰方法
import java.util.List;
import java.util.Random;

public    class Thread1 extends Thread{
    private List list;
    public Thread1(List list) {
        this.list=list;
    }
    public synchronized void  run() {
        try {
            for(int i=0;i<100;i++){
                Thread.sleep(10);//模拟处理一些业务,这样也更容易重现问题
                int randon = new Random().nextInt();
                list.add(randon);
            
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

main方法不变,执行结果:

size=150

这是很多人开始接触多线程时,会出现的错误,明明对run方法用了synchronized 关键字怎么出来的结果是不对的。根据上面提到的我们把代码转变一下:

import java.util.List;
import java.util.Random;

public    class Thread1 extends Thread{
    private List list;
    public Thread1(List list) {
        this.list=list;
    }
    public  void  run() {
        try {
            synchronized(this){//把synchronized改到这个地方
                for(int i=0;i<100;i++){
                    Thread.sleep(10);//模拟处理一些业务,这样也更容易重现问题
                    int randon = new Random().nextInt();
                    list.add(randon);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

synchronized用在this上,所以t1.start()锁定的是t1,t2.start()锁定的是t2,2者锁定的对象不同自然就没有相应的效果。

那是不是synchronized用在方法上就没有作用呢?当然不会,先看下面的例子:

import java.util.Random;

public    class Thread1 extends Thread{
    private SyncList list;
    public Thread1(SyncList list) {
        this.list=list;
    }
    public  void  run() {
        try {
            for(int i=0;i<100;i++){
                Thread.sleep(10);//模拟处理一些业务,这样也更容易重现问题
                int randon = new Random().nextInt();
                list.addList(randon);//注意这里
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
import java.util.ArrayList;
import java.util.List;

public class SyncList{
    
    private List list;
    
    public SyncList(List list) {
        this.list = list;
    }
    
    public void addList(E obj){
        list.add(obj);
    }
    public List getList() {
        return list;
    }
}
    public static void main(String[] args) throws InterruptedException {
        SyncList list = new SyncList(new ArrayList<>(1000));
        Thread1 t1 = new Thread1(list);
        t1.start();
        Thread1 t2 = new Thread1(list);
        t2.start();
        t1.join();
        t2.join();
        System.out.println("size="+list.getList().size());
}

执行结果:

size=161

修改一下SyncList:

import java.util.ArrayList;
import java.util.List;

public class SyncList{
    
    private List list;
    
    public SyncList(List list) {
        this.list = list;
    }
    
    public synchronized void addList(E obj){//仅在这里加上synchronized 
        list.add(obj);
    }
    public List getList() {
        return list;
    }
}

执行结果:

size=200

这个就是synchronized用在方法上的一个例子,锁定的对象都是同一个SyncList,所以最终结果是正确的。因此synchronized使用上很重要的一点,是保障多个线程锁定的对象要一致

异常释放锁
public    class Thread1 extends Thread{
    private Object lock;
    public Thread1(Object lock) {
        this.lock=lock;
    }
    public  void  run() {
        try {
            synchronized (lock) {
                for(int i=5;i>-1;i--){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                    Thread.sleep(200);
                    int j= 100/i;
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        for(int i=0;i<2;i++){
            Thread1 t1 = new Thread1(lock);
            t1.start();
            t1.join();
            Thread.sleep(10);
        }
}

执行结果:

Thread-0:5
Thread-0:4
Thread-0:3
Thread-0:2
Thread-0:1
Thread-0:0
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
    at Thread1.run(Thread1.java:12)
Thread-1:5
Thread-1:4
Thread-1:3
Thread-1:2
Thread-1:1
Thread-1:0
Exception in thread "Thread-1" java.lang.ArithmeticException: / by zero
    at Thread1.run(Thread1.java:12)

可以看到由于Thread-0先获取锁,Thread-1一直处于等待状态,Thread-0一直执行到i=0时,程序发生异常,锁被释放。Thread-1就获得了锁开始执行。

总结

1.synchronized可以修饰方法或者代码块。
2.尽量使用代码块以及减少代码块的范围,有利于提高程序运行效率。
3.要注意锁定的对象是否一致。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70702.html

相关文章

  • @Java | Thread & synchronized - [ 线程同步锁 基本使用]

    摘要:线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法对于静态同步方法,锁是针对这个类的,锁对象是该类的对象。 对实现了Runnable或者Callable接口类,可以通过多线程执行同一实例的run或call方法,那么对于同一实例中的局部变量(非方法变量)就会有多个线程进行更改或读取...

    Michael_Lin 评论0 收藏0
  • Java 线程核心技术梳理(附源码)

    摘要:本文对多线程基础知识进行梳理,主要包括多线程的基本使用,对象及变量的并发访问,线程间通信,的使用,定时器,单例模式,以及线程状态与线程组。源码采用构建,多线程这部分源码位于模块中。通知可能等待该对象的对象锁的其他线程。 本文对多线程基础知识进行梳理,主要包括多线程的基本使用,对象及变量的并发访问,线程间通信,lock的使用,定时器,单例模式,以及线程状态与线程组。 写在前面 花了一周时...

    Winer 评论0 收藏0
  • java线程synchronized

    摘要:非静态方法以及方法内部的代码块持有的是同一个对象锁,它们是同步执行的。可重入锁使用时,当一个线程请求一个对象锁时,再次请求该锁是可以立即得到的。出现异常,会自动释放锁同步方法与同步代码块作用于整个方法,可能引起方法执行效率下降。 synchronize可以在多个线程操作同一个成员变量或者方法时,实现同步(或者互斥)的效果。synchronized可以作用于方法,以及方法内部的代码块。 ...

    entner 评论0 收藏0
  • BATJ都爱问线程面试题

    摘要:今天给大家总结一下,面试中出镜率很高的几个多线程面试题,希望对大家学习和面试都能有所帮助。指令重排在单线程环境下不会出先问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。使用可以禁止的指令重排,保证在多线程环境下也能正常运行。 下面最近发的一些并发编程的文章汇总,通过阅读这些文章大家再看大厂面试中的并发编程问题就没有那么头疼了。今天给大家总结一下,面试中出镜率很高的几个多线...

    高胜山 评论0 收藏0
  • 线程中那些看不见陷阱

    摘要:多线程编程就像一个沼泽,中间遍布各种各样的陷阱。但是在多线程编程或者说是并发编程中,有非常多的陷阱被埋在底层细节当中。线程池类中用于控制线程池状态和线程数的控制变量就是一个类型的字段。 多线程编程就像一个沼泽,中间遍布各种各样的陷阱。大多数开发者绝大部分时间都是在做上层应用的开发,并不需要过多地涉入底层细节。但是在多线程编程或者说是并发编程中,有非常多的陷阱被埋在底层细节当中。如果不知...

    phodal 评论0 收藏0

发表评论

0条评论

Pandaaa

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<