synchronized锁方法块

http://www.cnblogs.com/xrq730/p/4851530.html

synchronized同步代码块

用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间。这种情况下可以尝试使用synchronized同步语句块来解决问题。看一下例子:

public class ThreadDomain18
{
    public void doLongTimeTask() throws Exception
    {
        for (int i = 0; i < 100; i++)
        {
            System.out.println("nosynchronized threadName = " +
                    Thread.currentThread().getName() + ", i = " + (i + 1));
        }
        System.out.println();
        synchronized (this)
        {
            for (int i = 0; i < 100; i++)
            {
                System.out.println("synchronized threadName = " +
                        Thread.currentThread().getName() + ", i = " + (i + 1));
            }
        }
    }
}
public class MyThread18 extends Thread
{
    private ThreadDomain18 td;

    public MyThread18(ThreadDomain18 td)
    {
        this.td = td;
    }

    public void run()
    {
        try
        {
            td.doLongTimeTask();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}
public static void main(String[] args)
{
    ThreadDomain18 td = new ThreadDomain18();
    MyThread18 mt0 = new MyThread18(td);
    MyThread18 mt1 = new MyThread18(td);
    mt0.start();
    mt1.start();
}

运行结果,分两部分来看:

synchronized threadName = Thread-1, i = 1
synchronized threadName = Thread-1, i = 2
nosynchronized threadName = Thread-0, i = 95
synchronized threadName = Thread-1, i = 3
nosynchronized threadName = Thread-0, i = 96
synchronized threadName = Thread-1, i = 4
nosynchronized threadName = Thread-0, i = 97
synchronized threadName = Thread-1, i = 5
nosynchronized threadName = Thread-0, i = 98
synchronized threadName = Thread-1, i = 6
nosynchronized threadName = Thread-0, i = 99
synchronized threadName = Thread-1, i = 7
nosynchronized threadName = Thread-0, i = 100
...
synchronized threadName = Thread-1, i = 98
synchronized threadName = Thread-1, i = 99
synchronized threadName = Thread-1, i = 100
synchronized threadName = Thread-0, i = 1
synchronized threadName = Thread-0, i = 2
synchronized threadName = Thread-0, i = 3
...

这个实验可以得出以下两个结论:

1、当A线程访问对象的synchronized代码块的时候,B线程依然可以访问对象方法中其余非synchronized块的部分,第一部分的执行结果证明了这一点

2、当A线程进入对象的synchronized代码块的时候,B线程如果要访问这段synchronized块,那么访问将会被阻塞,第二部分的执行结果证明了这一点

所以,从执行效率的角度考虑,有时候我们未必要把整个方法都加上synchronized,而是可以采取synchronized块的方式,对会引起线程安全问题的那一部分代码进行synchronized就可以了。

两个synchronized块之间具有互斥性

如果线程1访问了一个对象A方法的synchronized块,那么线程B对同一对象B方法的synchronized块的访问将被阻塞,写个例子来证明一下:

public class ThreadDomain19
{
    public void serviceMethodA()
    {
        synchronized (this)
        {
            try
            {
                System.out.println("A begin time = " + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("A end time = " + System.currentTimeMillis());
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

        }
    }

    public void serviceMethodB()
    {
        synchronized (this)
        {
            System.out.println("B begin time = " + System.currentTimeMillis());
            System.out.println("B end time = " + System.currentTimeMillis());
        }
    }
}

写两个线程分别调用这两个方法:

public class MyThread19_0 extends Thread
{
    private ThreadDomain19 td;

    public MyThread19_0(ThreadDomain19 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.serviceMethodA();
    }
}
public class MyThread19_1 extends Thread
{
    private ThreadDomain19 td;

    public MyThread19_1(ThreadDomain19 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.serviceMethodB();
    }
}

写个main函数:

public static void main(String[] args)
{
    ThreadDomain19 td = new ThreadDomain19();
    MyThread19_0 mt0 = new MyThread19_0(td);
    MyThread19_1 mt1 = new MyThread19_1(td);
    mt0.start();
    mt1.start();
}

看一下运行结果:

A begin time = 1443843271982
A end time = 1443843273983
B begin time = 1443843273983
B end time = 1443843273983

看到对于serviceMethodB()方法synchronized块的访问必须等到对于serviceMethodA()方法synchronized块的访问结束之后。那其实这个例子,我们也可以得出一个结论:synchronized块获得的是一个对象锁,换句话说,synchronized块锁定的是整个对象

synchronized块和synchronized方法

既然上面得到了一个结论synchronized块获得的是对象锁,那么如果线程1访问了一个对象方法A的synchronized块,线程2对于同一对象同步方法B的访问应该是会被阻塞的,因为线程2访问同一对象的同步方法B的时候将会尝试去获取这个对象的对象锁,但这个锁却在线程1这里。写一个例子证明一下这个结论:

public class ThreadDomain20
{
    public synchronized void otherMethod()
    {
        System.out.println("----------run--otherMethod");
    }

    public void doLongTask()
    {
        synchronized (this)
        {
            for (int i = 0; i < 1000; i++)
            {
                System.out.println("synchronized threadName = " +
                        Thread.currentThread().getName() + ", i = " + (i + 1));
                try
                {
                    Thread.sleep(5);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }
}

写两个线程分别调用这两个方法:

public class MyThread20_0 extends Thread
{
    private ThreadDomain20 td;

    public MyThread20_0(ThreadDomain20 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.doLongTask();
    }
}
public class MyThread20_1 extends Thread
{
    private ThreadDomain20 td;

    public MyThread20_1(ThreadDomain20 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.otherMethod();
    }
}

写个main函数调用一下,这里"mt0.start()"后sleep(100)以下是为了确保mt0线程先启动:

public static void main(String[] args) throws Exception
    {
        ThreadDomain20 td = new ThreadDomain20();
        MyThread20_0 mt0 = new MyThread20_0(td);
        MyThread20_1 mt1 = new MyThread20_1(td);
        mt0.start();
        Thread.sleep(100);
        mt1.start();
    }

看一下运行结果:

...
synchronized threadName = Thread-0, i = 995
synchronized threadName = Thread-0, i = 996
synchronized threadName = Thread-0, i = 997
synchronized threadName = Thread-0, i = 998
synchronized threadName = Thread-0, i = 999
synchronized threadName = Thread-0, i = 1000
----------run--otherMethod

证明了我们的结论。为了进一步完善这个结论,把"otherMethod()"方法的synchronized去掉再看一下运行结果:

...
synchronized threadName = Thread-0, i = 16
synchronized threadName = Thread-0, i = 17
synchronized threadName = Thread-0, i = 18
synchronized threadName = Thread-0, i = 19
synchronized threadName = Thread-0, i = 20
----------run--otherMethod
synchronized threadName = Thread-0, i = 21
synchronized threadName = Thread-0, i = 22
synchronized threadName = Thread-0, i = 23
...

"otherMethod()"方法和"doLongTask()"方法中的synchronized块异步执行了

将任意对象作为对象监视器

总结一下前面的内容:

1、synchronized同步方法

(1)对其他synchronized同步方法或synchronized(this)同步代码块呈阻塞状态

(2)同一时间只有一个线程可以执行synchronized同步方法中的代码

2、synchronized同步代码块

(1)对其他synchronized同步方法或synchronized(this)同步代码块呈阻塞状态

(2)同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码

前面都使用synchronized(this)的格式来同步代码块,其实Java还支持对"任意对象"作为对象监视器来实现同步的功能。这个"任意对象"大多数是实例变量方法的参数,使用格式为synchronized(非this对象)。看一下将任意对象作为对象监视器的使用例子:

public class ThreadDomain21
{
    private String userNameParam;
    private String passwordParam;
    private String anyString = new String();

    public void setUserNamePassword(String userName, String password)
    {
        try
        {
            synchronized (anyString)
            {
                System.out.println("线程名称为:" + Thread.currentThread().getName() +
                        "在 " + System.currentTimeMillis() + " 进入同步代码块");
                userNameParam = userName;
                Thread.sleep(3000);
                passwordParam = password;
                System.out.println("线程名称为:" + Thread.currentThread().getName() +
                        "在 " + System.currentTimeMillis() + " 离开同步代码块");
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

写两个线程分别调用一下:

public class MyThread21_0 extends Thread
{
    private ThreadDomain21 td;

    public MyThread21_0(ThreadDomain21 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.setUserNamePassword("A", "AA");
    }
}
public class MyThread21_1 extends Thread
{
    private ThreadDomain21 td;

    public MyThread21_1(ThreadDomain21 td)
    {
        this.td = td;
    }

    public void run()
    {
        td.setUserNamePassword("B", "B");
    }
}

写一个main函数调用一下:

public static void main(String[] args)
{
    ThreadDomain21 td = new ThreadDomain21();
    MyThread21_0 mt0 = new MyThread21_0(td);
    MyThread21_1 mt1 = new MyThread21_1(td);
    mt0.start();
    mt1.start();
}

看一下运行结果:

线程名称为:Thread-0在 1443855101706 进入同步代码块
线程名称为:Thread-0在 1443855104708 离开同步代码块
线程名称为:Thread-1在 1443855104708 进入同步代码块
线程名称为:Thread-1在 1443855107708 离开同步代码块

这个例子证明了:多个线程持有"对象监视器"为同一个对象的前提下,同一时间只能有一个线程可以执行synchronized(非this对象x)代码块中的代码

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

注意一下"private String anyString = new String();"这句话,现在它是一个全局对象,因此监视的是同一个对象。如果移到try里面,那么对象的监视器就不是同一个了,调用的时候自然是异步调用,可以自己试一下。

最后提一点,synchronized(非this对象x),这个对象如果是实例变量的话,指的是对象的引用,只要对象的引用不变,即使改变了对象的属性,运行结果依然是同步的

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

synchronized(非this对象x)格式的写法是将x对象本身作为对象监视器,有三个结论得出:

1、当多个线程同时执行synchronized(x){}同步代码块时呈同步效果

2、当其他对象执行x对象中的synchronized同步方法时呈同步效果

3、当前他线程执行x对象方法中的synchronized(this)代码块时也呈同步效果

第一点很明显,第二点和第三点意思类似,无非一个是同步方法,一个是同步代码块罢了,举个例子验证一下第二点:

public class MyObject
{
    public synchronized void speedPrintString()
    {
        System.out.println("speedPrintString__getLock time = " +
                System.currentTimeMillis() + ", run ThreadName = " +
                Thread.currentThread().getName());
        System.out.println("----------");
        System.out.println("speedPrintString__releaseLock time = " +
                System.currentTimeMillis() + ", run ThreadName = " +
                Thread.currentThread().getName());
    }
}

ThreadDomain24中持有MyObject的引用:

public class ThreadDomain24
{
    public void testMethod1(MyObject mo)
    {
        try
        {
            synchronized (mo)
            {
                System.out.println("testMethod1__getLock time = " +
                        System.currentTimeMillis() + ", run ThreadName = " +
                        Thread.currentThread().getName());
                Thread.sleep(5000);
                System.out.println("testMethod1__releaseLock time = " +
                        System.currentTimeMillis() + ", run ThreadName = " +
                        Thread.currentThread().getName());
            }
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

写两个线程分别调用"speedPrintString()"方法和"testMethod1(MyObject mo)"方法:

public class MyThread24_0 extends Thread
{
    private ThreadDomain24 td;
    private MyObject mo;

    public MyThread24_0(ThreadDomain24 td, MyObject mo)
    {
        this.td = td;
        this.mo = mo;
    }

    public void run()
    {
        td.testMethod1(mo);
    }
}
public class MyThread24_1 extends Thread
{
    private MyObject mo;

    public MyThread24_1(MyObject mo)
    {
        this.mo = mo;
    }

    public void run()
    {
        mo.speedPrintString();
    }
}

写一个main函数启动这两个线程:

public static void main(String[] args)
{
    ThreadDomain24 td = new ThreadDomain24();
    MyObject mo = new MyObject();
    MyThread24_0 mt0 = new MyThread24_0(td, mo);
    MyThread24_1 mt1 = new MyThread24_1(mo);
    mt0.start();
    mt1.start();
}

看一下运行结果:

testMethod1__getLock time = 1443855939811, run ThreadName = Thread-0
testMethod1__releaseLock time = 1443855944812, run ThreadName = Thread-0
speedPrintString__getLock time = 1443855944812, run ThreadName = Thread-1
----------
speedPrintString__releaseLock time = 1443855944812, run ThreadName = Thread-1

看到"speedPrintString()"方法必须等待"testMethod1(MyObject mo)"方法执行完毕才可以执行,没有办法异步执行,证明了第二点的结论。第三点的验证方法类似,就不写代码证明了。

时间: 2024-12-29 11:15:18

synchronized锁方法块的相关文章

Java多线程5:synchronized锁方法块

synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用synchronized同步语句块来解决问题.看一下例子: 这个实验可以得出以下两个结论: 1.当A线程访问对象的synchronized代码块的时候,B线程依然可以访问对象方法中其余非synchronized块的部分,第一部分的执行结果证明了这一点 2.当A线程进入对象的synchronized代

记一次synchronized锁字符串引发的坑兼再谈Java字符串

问题描述 业务有一个需求,我把问题描述一下: 通过代理IP访问国外某网站N,每个IP对应一个固定的网站N的COOKIE,COOKIE有失效时间. 并发下,取IP是有一定策略的,取到IP之后拿IP对应的COOKIE,发现COOKIE超过失效时间,则调用脚本访问网站N获取一次数据. 为了防止多线程取到同一个IP,同时发现该IP对应的COOKIE失效,同时去调用脚本更新COOKIE,针对IP加了锁.为了保证锁的全局唯一性,在锁前面加了标识业务的前缀,使用synchronized(lock){}的方式,

java 多线程9 : synchronized锁机制 之 代码块锁

synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用synchronized同步语句块来解决问题.看一下例子: 下面例子是优化后的例子 使用代码块锁,原先例子是方法锁,就是同步 必须要执行2个for  public class ThreadDomain18 { public void doLongTimeTask() throws Exception

java 多线程 Synchronized方法和方法块 synchronized(this)和synchronized(object)的理解

synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块. 1. synchronized 方法:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法.如:public synchronized void accessVal(int newVal);synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否

java多线程(三)——锁机制synchronized(同步语句块)

用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法之行一个长时间的任务,那么B线程必须等待比较长的时间,在这样的情况下可以使用synchronized同步语句快来解决. 一.用同步代码块解决同步方法的弊端 Task类 1 package com.weishiyao.learn.day4.testSynchorized.ep2; 2 3 public class Task { 4 5 private String getData1; 6 private Stri

synchronized锁机制 之 代码块锁(转)

synchronized同步代码块 用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个较长时间的任务,那么B线程必须等待比较长的时间.这种情况下可以尝试使用synchronized同步语句块来解决问题.看一下例子: 下面例子是优化后的例子使用代码块锁,原先例子是方法锁,就是同步必须要执行2个for. public class ThreadDomain18 { public void doLongTimeTask() throws Exception {

java中多线程模拟(多生产,多消费,Lock实现同步锁,替代synchronized同步代码块)

import java.util.concurrent.locks.*; class DuckMsg{ int size;//烤鸭的大小 String id;//烤鸭的厂家和标号 DuckMsg(){ } DuckMsg(int size, String id){ this.size=size; this.id=id; } public String toString(){ return id + " 大小为:" + size; } } class Duck{ private int

Java synchronized 锁住的是整个对象还是只有方法

先上结论: 类方法中,synchronized锁住的是对象this,只有调用同一个对象的方法才需要获取锁.同时,同一个对象中所有加了synchronize的方法只能一次调用一个 静态方法中,synchronized锁的是整个类对象,类似于(X.class),该类中所有加了synchronized的静态方法,一次只能调用一个 1 class Sync { 2 3 public synchronized void test() { 4 System.out.println("test开始..&quo

java 多线程8 : synchronized锁机制 之 方法锁

脏读 一个常见的概念.在多线程中,难免会出现在多个线程中对同一个对象的实例变量或者全局静态变量进行并发访问的情况,如果不做正确的同步处理,那么产生的后果就是"脏读",也就是取到的数据其实是被更改过的.注意这里 局部变量是不存在脏读的情况 多线程线程实例变量非线程安全 看一段代码: public class ThreadDomain13 { private int num = 0; public void addNum(String userName) { try { if ("