博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java多线程编程笔记3:synchronized同步语句块
阅读量:6622 次
发布时间:2019-06-25

本文共 11234 字,大约阅读时间需要 37 分钟。

使用synchronized关键字声明方法有些时候是有很大的弊端的,比如我们有两个线程一个线程A调用同步方法后获得锁,那么另一个线程B就需要等待A执行完,但是如果说A执行的是一个很费时间的任务的话这样就会很耗时。

synchronized(this)同步代码块

当两个并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块才能执行该代码块。

一个线程访问object的一个同步代码块时,另外一个线程仍然可以访问该对象的非同步代码块。 以下代码可以证明:不在synchronized块中就是异步执行,在synchronized块中就是同步执行。

class Task{    public void doLongTimeTask(){        for (int i=0;i<5;i++){            System.out.println("nonsync threadName:"+Thread.currentThread().getName()+" i: "+i);        }        System.out.println();        synchronized (this){            for (int i=0;i<5;i++){                System.out.println("sync threadName:"+Thread.currentThread().getName()+" i: "+i);            }        }    }}class MyThread extends Thread{    private Task task;    public MyThread(Task task){        this.task=task;    }    @Override    public void run() {        task.doLongTimeTask();    }}public class Run {    public static void main(String[] args) {        Task task=new Task();        MyThread t1=new MyThread(task);        MyThread t2=new MyThread(task);        t1.start();        t2.start();    }}复制代码

运行结果为:

nonsync threadName:Thread-0 i: 0nonsync threadName:Thread-1 i: 0nonsync threadName:Thread-0 i: 1nonsync threadName:Thread-1 i: 1nonsync threadName:Thread-1 i: 2nonsync threadName:Thread-1 i: 3nonsync threadName:Thread-0 i: 2nonsync threadName:Thread-1 i: 4nonsync threadName:Thread-0 i: 3nonsync threadName:Thread-0 i: 4sync threadName:Thread-1 i: 0sync threadName:Thread-1 i: 1sync threadName:Thread-1 i: 2sync threadName:Thread-1 i: 3sync threadName:Thread-1 i: 4sync threadName:Thread-0 i: 0sync threadName:Thread-0 i: 1sync threadName:Thread-0 i: 2sync threadName:Thread-0 i: 3sync threadName:Thread-0 i: 4复制代码

可以看到,非同步代码块是异步执行的,同步代码块则是排队执行的。

同步代码块之间的同步性:在使用synchronized(this)代码块时,当一个线程访问object的一个同步代码块时,其他线程对同一个对象中所有其他synchronized(this)代码块的访问会被阻塞,说明synchronized使用的“对象监视器是一个”。

synchronized(this)代码块是锁定当前对象的。也就是说,多个线程调用同一个对象的不同名称的synchronized同步方法或者synchronized(this)同步代码块时,调用的效果就是按顺序执行,就是同步的,阻塞的。也就是说,无论是同步方法或者synchronized(this)代码块,都可以对其他同步方法或代码块调用呈阻塞状态。同一时间只有一个线程可以执行同步方法或同步代码块中的代码。

synchronized(非this对象)代码块

除了synchronized(this)同步代码块,Java还支持对任意对象作为“对象监视器”来实现同步的功能。任意对象大多数是实例变量及方法的参数。同样的,在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this)同步代码块中的代码。

锁非this对象有一定的优点:如果在一个类中有很多synchronized方法,这时虽然能实现同步,但会收到阻塞,影响运行效率。但是如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,可以提高运行效率。

在使用synchronized(非this对象)同步代码块时,对象监视器必须是同一个对象,如果不是同一个对象监视器,运行的结果就是异步调用了。示例代码:

package ch02.t12;class Service {    private String anyStr = new String();    public void method() {        try {            synchronized (anyStr) {                System.out.println("Thread: " + Thread.currentThread().getName()                        + " time: " + System.currentTimeMillis() + "进入同步块");                Thread.sleep(3000);                System.out.println("Thread: " + Thread.currentThread().getName()                        + " time: " + System.currentTimeMillis() + "离开同步块");            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}class MyThread extends Thread{    private Service service;    public MyThread(Service service){        this.service=service;    }    @Override    public void run() {        service.method();    }}public class Run {    public static void main(String[] args) {        Service service=new Service();        MyThread a=new MyThread(service);        a.setName("A");        a.start();        MyThread b=new MyThread(service);        b.setName("B");        b.start();    }}复制代码

运行结果为:

Thread: A time: 1544337130842进入同步块Thread: A time: 1544337133842离开同步块Thread: B time: 1544337133842进入同步块Thread: B time: 1544337136852离开同步块复制代码

如果将Service改成:

class Service {    public void method() {        try {            String anyStr = new String();            synchronized (anyStr) {                System.out.println("Thread: " + Thread.currentThread().getName()                        + " time: " + System.currentTimeMillis() + "进入同步块");                Thread.sleep(3000);                System.out.println("Thread: " + Thread.currentThread().getName()                        + " time: " + System.currentTimeMillis() + "离开同步块");            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}复制代码

运行结果为:

Thread: B time: 1544337317933进入同步块Thread: A time: 1544337317933进入同步块Thread: A time: 1544337320933离开同步块Thread: B time: 1544337320933离开同步块复制代码

是异步进行的,因此此时两个anyStr对象不是同一个对象了。

synchronized代码块的“脏读”

同步代码块放在非同步方法中进行生命,并不能摆正调用方法的线程的执行同步、顺序性。线程调用方法的顺序是无序的,虽然在同步块中执行的顺序是同步的,这样容易出现“脏读”问题。

class MyOneList{    private ArrayList list=new ArrayList();    synchronized public void add(String data){        list.add(data);    }    synchronized public int getSize(){        return list.size();    }}class MyService{    public MyOneList add(MyOneList list,String data){        try {            System.out.println(Thread.currentThread().getName());            if(list.getSize()<1){
//保证list只有一个元素 Thread.sleep(2000); list.add(data); } } catch (InterruptedException e) { e.printStackTrace(); } return list; }}class MyThread extends Thread{ private MyOneList list; public MyThread(MyOneList list){ this.list=list; } @Override public void run() { MyService service=new MyService(); service.add(list,"A"); }}public class Run { public static void main(String[] args) throws InterruptedException { MyOneList list=new MyOneList(); MyThread t1=new MyThread(list); t1.setName("A"); t1.start(); MyThread t2=new MyThread(list); t2.setName("B"); t2.start(); Thread.sleep(6000); System.out.println(list.getSize()); }}复制代码

运行结果:

BA2复制代码

首先可以发现,两个线程打印的顺序是无序的,说明线程的执行时异步的。返回的结果中list的size为2,原因是在add()方法中,list.getSize()这一方法是异步调用的。因此,需要对add()方法进行同步化,修改如下:

class MyService {    public MyOneList add(MyOneList list, String data) {        try {            synchronized (list) {                System.out.println(Thread.currentThread().getName());                if (list.getSize() < 1) {
//保证list只有一个元素 Thread.sleep(2000); list.add(data); } } } catch (InterruptedException e) { e.printStackTrace(); } return list; }}复制代码

list对象在项目中只有一份实例,且对其进行调用,因此以list参数进行同步处理。结果如下:

AB1复制代码

synchronized(非this对象x)的三个结论

  1. 当多个线程同时执行synchronized(x)同步代码块时是同步的;
  2. 当线程A执行以x为锁的代码块时,其他线程执行x对象中的同步方法时也是同步的;
  3. 当线程A执行以x为锁的代码块时,其他线程执行x对象中的synchronized(this)代码块时也是同步的。

静态synchronized方法与synchronized(class)代码块

关键字synchronized还可以应用在static静态方法上,这样是堆当前的*.java文件对应的Class类加锁。

class Service{    synchronized public static void printA(){        try {            System.out.println("thread: "+Thread.currentThread().getName()+" time: "+System.currentTimeMillis()+" 进入printA方法");            Thread.sleep(3000);            System.out.println("thread: "+Thread.currentThread().getName()+" time: "+System.currentTimeMillis()+" 离开printA方法");        } catch (InterruptedException e) {            e.printStackTrace();        }    }    synchronized public static void printB(){            System.out.println("thread: "+Thread.currentThread().getName()+" time: "+System.currentTimeMillis()+" 进入printB方法");            System.out.println("thread: "+Thread.currentThread().getName()+" time: "+System.currentTimeMillis()+" 离开printB方法");    }    synchronized public  void printC(){        System.out.println("thread: "+Thread.currentThread().getName()+" time: "+System.currentTimeMillis()+" 进入printC方法");        System.out.println("thread: "+Thread.currentThread().getName()+" time: "+System.currentTimeMillis()+" 离开printC方法");    }}class ThreadA extends Thread{    private Service service;    public ThreadA(Service service){        this.service=service;    }    @Override    public void run() {        service.printA();    }}class ThreadB extends Thread{    private Service service;    public ThreadB(Service service){        this.service=service;    }    @Override    public void run() {        service.printB();    }}class ThreadC extends Thread{    private Service service;    public ThreadC(Service service){        this.service=service;    }    @Override    public void run() {        service.printC();    }}public class Run {    public static void main(String[] args) {        Service service=new Service();        ThreadA a=new ThreadA(service);        a.setName("A");        a.start();        ThreadB b=new ThreadB(service);        b.setName("B");        b.start();        ThreadC c=new ThreadC(service);        c.setName("C");        c.start();    }}复制代码

运行结果:

thread: A time: 1544341920925 进入printA方法thread: C time: 1544341920925 进入printC方法thread: C time: 1544341920925 离开printC方法thread: A time: 1544341923925 离开printA方法thread: B time: 1544341923925 进入printB方法thread: B time: 1544341923925 离开printB方法复制代码

可以看出,A与C并不是同步的,A与B是同步的。原因就是A和B是持有的同一个Class锁,而C持有的是对象锁。

但是,Class锁可以对类的所有对象实例起作用。修改上述的代码,只修改main方法如下:

public class Run {    public static void main(String[] args) {        Service service1=new Service();        Service service2=new Service();        ThreadA a=new ThreadA(service1);        a.setName("A");        a.start();        ThreadB b=new ThreadB(service2);        b.setName("B");        b.start();    }}复制代码

运行结果如下:

thread: A time: 1544342122668 进入printA方法thread: A time: 1544342125668 离开printA方法thread: B time: 1544342125668 进入printB方法thread: B time: 1544342125668 离开printB方法复制代码

可以看到,线程A和线程B虽然调用的是不同的对象,但是由于二者是Class锁,因此仍然是同步进行的。

synchronized(class)代码块和synchronized static方法的作用是一样的,修改上述的printA()和printB()方法如下:

class Service {    public static void printA() {        synchronized (Service.class) {            try {                System.out.println("thread: " + Thread.currentThread().getName() + " time: " + System.currentTimeMillis() + " 进入printA方法");                Thread.sleep(3000);                System.out.println("thread: " + Thread.currentThread().getName() + " time: " + System.currentTimeMillis() + " 离开printA方法");            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    synchronized public static void printB() {        synchronized (Service.class) {            System.out.println("thread: " + Thread.currentThread().getName() + " time: " + System.currentTimeMillis() + " 进入printB方法");            System.out.println("thread: " + Thread.currentThread().getName() + " time: " + System.currentTimeMillis() + " 离开printB方法");        }    }}复制代码

Run类和刚刚修改后的两个不同的Service对象一样,运行结果为:

thread: A time: 1544342263970 进入printA方法thread: A time: 1544342266970 离开printA方法thread: B time: 1544342266970 进入printB方法thread: B time: 1544342266970 离开printB方法复制代码

可以看到效果是一样的。

synchronized(string)同步代码块

在JVM中,具有String常量池缓存功能。在将synchronized(string)使用时,需要注意常量池带来的一些例外。在大多数情况下,同步代码块不推荐使用String作为锁对象,而改用其他,比如new Object(),它不放入缓存中。

多线程的死锁

死锁的场景一般是:线程 A 和线程 B 都在互相等待对方释放锁,或者是其中某个线程在释放锁的时候出现异常如死循环之类的。这时就会导致系统不可用。

常用的解决方案如下:

  • 尽量一个线程只获取一个锁。
  • 一个线程只占用一个资源。
  • 尝试使用定时锁,至少能保证锁最终会被释放。

内置类与静态内置类

判断是否同步的方法是一样的,都是判断是否是同一个对象为锁。

锁对象的改变

如果锁对象本身改变了(如String从“a”变成了“b”),则改变前和改变后,线程的锁不一样,是异步的。但是如果锁的属性改变了,但是锁对象本身没变,则仍然是同步的。

参考资料

  • 高洪岩. Java多线程编程核心技术[M]. 机械工业出版社, 2015.

转载地址:http://mtjpo.baihongyu.com/

你可能感兴趣的文章
灾难恢复-boot分区的恢复方法
查看>>
小游戏-猜数字
查看>>
深度学习到顶,AI寒冬将至!
查看>>
【投资】欧盟区块链创业公司投资超500万欧元
查看>>
优傲机器人:人机协作机器人助推电子制造业智慧升级
查看>>
PHP浮点数的精确计算BCMath
查看>>
[起重机监测系统] 1、基于无线传输的桥式起重机的安全监测方案
查看>>
2014年发展计划
查看>>
QQ协议
查看>>
[Android]一个干净的架构(翻译)
查看>>
Oracle RAC安装过程中碰到的“坑”和关键点(一)
查看>>
Jmeter关联
查看>>
java的nio之:java的nio系列教程之Scatter/Gather
查看>>
linux命令之ldconfig
查看>>
Shell之sed命令
查看>>
如何让你的传输更安全——NIO模式和BIO模式实现SSL协议通信
查看>>
【云计算的1024种玩法】使用 NAS 文件储存低价获得好磁盘性能
查看>>
Android Framework Boot Up Overview(Android系统框架启动流程概述)
查看>>
聊聊 iOS 开发
查看>>
人人都应该了解的信息简史
查看>>