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)"方法执行完毕才可以执行,没有办法异步执行,证明了第二点的结论。第三点的验证方法类似,就不写代码证明了。

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信