大话Android多线程(二) synchronized使用解析

版权声明:本文为博主原创文章,未经博主容许不得转载
源码:github.com/AnliaLee
你们要是看到有错误的地方或者有啥好的建议,欢迎留言评论html

前言

这是Android多线程篇的第二章,在上章咱们比较了ThreadRunnable建立线程的异同,也简单地模拟了多线程执行任务的场景。但实际上,这样执行多线程任务是不安全的,这章咱们将分析为什么会出现线程不安全的状况以及如何使用synchronized解决这样的问题java

往期回顾
大话Android多线程(一) Thread和Runnable的联系和区别android


synchronized使用解析

同步方法(非静态)

上回说到小R(Runnable)由于诚信经营,生意愈来愈好了,因而小R便多招了一个业务员。某日,在售出10张门票后不久,小R就收到了顾客的投诉,说他们买到了假票。小R怀疑是本身的手下动了手脚,便展开了调查:git

当时的业务流程以下github

private class SellTask {
	private int ticket = 10;
	public void sellTicket(){
		if (ticket > 0) {
			try{
				Thread.sleep(500);
				Log.e("R公司",Thread.currentThread().getName() + "卖了一张票,编号为r" + (ticket--));
			}catch (Exception e){
				e.printStackTrace();
			}
		}
	}
}

public class TicketRunnable implements Runnable {
	SellTask sellTask;
	public TicketRunnable(SellTask sellTask){
		this.sellTask = sellTask;
	}

	public void run() {
		for (int i = 0; i < 10; i++) {
			sellTask.sellTicket();
		}
	}
}
复制代码

票交由3个业务员去卖安全

SellTask sellTask = new SellTask();
TicketRunnable runnable = new TicketRunnable(sellTask);
Thread r1 = new Thread(runnable, "1号业务员");
Thread r2 = new Thread(runnable, "2号业务员");
Thread r3 = new Thread(runnable, "3号业务员");

r1.start();
r2.start();
r3.start();
复制代码

调查发现,出现了多个业务员售出编号相同的票的状况多线程

进一步调查后得知,出现这样的状况是由于业务员答应了卖给顾客某编号的票,并收取了订金,回头拿票时才发现哥几个卖得是同一张票(多个线程前后操做共享数据形成数据错误),没办法只能本身复制一张给顾客企图蒙混过关。小R不知道该怎么约束本身的手下,遂公开招聘能解决问题的人app

这天,一位自称synchronized的男人前来应聘。小R问道:“s先生有何高见啊?”s先生淡定地喝了口茶,答道:ide

“你如今的业务流程不太可靠(线程不安全),让我来统一管理整个售票业务,每一张票的出售都需通过个人审批,一张票卖完后业务员才能来我这再次申请拿票出售,这样每张票都只能由一个业务员进行出售,问题也天然解决了(在Java中每个对象都有一个内部锁,当使用synchronized关键字声明某个方法时,该方法将受到对象锁的保护,这样一次就只能有一个线程能够进入该方法得到该对象的锁,其余线程要想调用该方法,只能排队等待。当得到锁的线程执行完该方法并释放对象锁后,别的线程才可拿到锁进入该方法)。”post

小R听后以为这方法不错,便让s先生来试试。此次依然是要出售10张票,业务流程通过s先生改进后以下

private class SellTask {
	private int ticket = 10;
	public synchronized void sellTicket(){//使用synchronized声明sellTicket方法
		if (ticket > 0) {
			try{
				Thread.sleep(500);
				Log.e("R公司",Thread.currentThread().getName() + "卖了一张票,编号为r" + (ticket--));
			}catch (Exception e){
				e.printStackTrace();
			}
		}
	}
}
复制代码

问题果真解决了,小R悬着的心也终于放了下来


同一个对象内多个同步方法

某日,小R又开始向s先生抱怨起来:“我那帮二愣子手下啊,每次进我办公室汇报工做都是乱糟糟的,让他们按顺序一个个来就是不听,s先生以为该如何管管他们啊?”s先生依然淡定地抿了口茶,说道:“不急,容我先看看他们是怎么汇报的。”小R便依着s先生的意思安排了两个手下过来汇报工做

private class ReportTask {
	public void report1(){
		Log.e("R公司","1号业务员" + "进办公室");
		try{
			Log.e("R公司","1号业务员" + "开始汇报");
			Thread.sleep(1000);

		}catch (Exception e){
			e.printStackTrace();
		}
		Log.e("R公司","1号业务员" + "汇报完毕");
		Log.e("R公司","1号业务员" + "出办公室");
	}

	public void report2(){
		Log.e("R公司","2号业务员" + "进办公室");
		try{
			Log.e("R公司","2号业务员" + "开始汇报");
			Thread.sleep(1000);

		}catch (Exception e){
			e.printStackTrace();
		}
		Log.e("R公司","2号业务员" + "汇报完毕");
		Log.e("R公司","2号业务员" + "出办公室");
	}
}

public class ReportRunnable1 implements Runnable {
	ReportTask task;
	public ReportRunnable1(ReportTask task){
		this.task = task;
	}

	public void run() {
		task.report1();
	}
}

public class ReportRunnable2 implements Runnable {
	ReportTask task;
	public ReportRunnable2(ReportTask task){
		this.task = task;
	}

	public void run() {
		task.report2();
	}
}
复制代码

不一下子,两个手下先后脚进了办公室

ReportTask reportTask = new ReportTask();
ReportRunnable1 runnable1 = new ReportRunnable1(reportTask);
ReportRunnable2 runnable2 = new ReportRunnable2(reportTask);

Thread r1 = new Thread(runnable1);
Thread r2 = new Thread(runnable2);
r1.start();
r2.start();
复制代码

小R揉了揉脑壳,叹气道:“唉,他们就是这样汇报的,每次他们一块儿讲的时候我都不知该听谁的。”s先生哈哈一笑,道:

“这个不难解决,下次他们再来汇报,进来第一我的我就把门锁了,让下一个在门外等,等第一个讲完了我再放第二个进来就好了(当一个线程访问对象的某个synchronized同步方法时,其余线程对对象中全部其它synchronized同步方法的访问将被阻塞)”

小R听后深觉得然,便又安排刚刚那两个业务员过来从新汇报一次,此次由s先生亲自守门(别问我为啥他们傻傻的,计算机就是这么工做的 ╮(╯▽╰)╭)

private class ReportTask {
	public synchronized void report1(){
		Log.e("R公司","1号业务员" + "进办公室");
		try{
			Log.e("R公司","1号业务员" + "开始汇报");
			Thread.sleep(1000);

		}catch (Exception e){
			e.printStackTrace();
		}
		Log.e("R公司","1号业务员" + "汇报完毕");
		Log.e("R公司","1号业务员" + "出办公室");
	}

	public synchronized void report2(){
		Log.e("R公司","2号业务员" + "进办公室");
		try{
			Log.e("R公司","2号业务员" + "开始汇报");
			Thread.sleep(1000);

		}catch (Exception e){
			e.printStackTrace();
		}
		Log.e("R公司","2号业务员" + "汇报完毕");
		Log.e("R公司","2号业务员" + "出办公室");
	}
}
复制代码

不一下子,两个业务员又来了,此次的结果令小R很是满意

看着小R这么开心,s先生不由泼起了冷水:

“你别高兴得太早,你窗户可没锁呢,说不定你那帮二愣子手下进不了门就从窗户爬进来了(当一个线程访问对象的某个synchronized同步方法时,另外一个线程仍然能够访问该对象中的非synchronized同步方法)。”

果真,以后的某次工做汇报中,这样的事就发生了

private class ReportTask {
	public synchronized void report1(){
		Log.e("R公司","1号业务员" + "进办公室");
		try{
			Log.e("R公司","1号业务员" + "开始汇报");
			Thread.sleep(1000);

		}catch (Exception e){
			e.printStackTrace();
		}
		Log.e("R公司","1号业务员" + "汇报完毕");
		Log.e("R公司","1号业务员" + "出办公室");
	}

	public synchronized void report2(){
		Log.e("R公司","2号业务员" + "进办公室");
		try{
			Log.e("R公司","2号业务员" + "开始汇报");
			Thread.sleep(1000);

		}catch (Exception e){
			e.printStackTrace();
		}
		Log.e("R公司","2号业务员" + "汇报完毕");
		Log.e("R公司","2号业务员" + "出办公室");
	}

	public void report3(){
		Log.e("R公司","3号业务员" + "进办公室");
		try{
			Log.e("R公司","3号业务员" + "开始汇报");
			Thread.sleep(1000);

		}catch (Exception e){
			e.printStackTrace();
		}
		Log.e("R公司","3号业务员" + "汇报完毕");
		Log.e("R公司","3号业务员" + "出办公室");
	}
}

//线程启动代码略...
复制代码

小R:


同步代码块

是日,好友小T前来拜访小R,却看见小R的办公室门窗紧闭,几个业务员在门外排着队。小T十分疑惑,遂敲门招呼小R让他开门,然而却没有获得任何回应。没办法,小T只能跟着业务员在办公室外面等了

private class ReportTask {
	public void report1(){
		synchronized(this){
			Log.e("R公司","1号业务员" + "进办公室");
			try{
				Log.e("R公司","1号业务员" + "开始汇报");
				Thread.sleep(1000);

			}catch (Exception e){
				e.printStackTrace();
			}
			Log.e("R公司","1号业务员" + "汇报完毕");
			Log.e("R公司","1号业务员" + "出办公室");
		}
	}

	public void report2(){
		synchronized(this){
			Log.e("R公司","2号业务员" + "进办公室");
			try{
				Log.e("R公司","2号业务员" + "开始汇报");
				Thread.sleep(1000);

			}catch (Exception e){
				e.printStackTrace();
			}
			Log.e("R公司","2号业务员" + "汇报完毕");
			Log.e("R公司","2号业务员" + "出办公室");
		}
	}

	public void report3(){
		synchronized(this){
			Log.e("R公司","3号业务员" + "进办公室");
			try{
				Log.e("R公司","3号业务员" + "开始汇报");
				Thread.sleep(1000);

			}catch (Exception e){
				e.printStackTrace();
			}
			Log.e("R公司","3号业务员" + "汇报完毕");
			Log.e("R公司","3号业务员" + "出办公室");
		}
	}

	public void report4(){
		synchronized (this){
			Log.e("R公司","小T" + "进办公室");
		}

	}
}
复制代码
ReportTask reportTask = new ReportTask();
ReportRunnable1 runnable1 = new ReportRunnable1(reportTask);
ReportRunnable2 runnable2 = new ReportRunnable2(reportTask);
ReportRunnable3 runnable3 = new ReportRunnable3(reportTask);
ReportRunnable4 runnable4 = new ReportRunnable4(reportTask);

Thread s1 = new Thread(runnable1);
Thread s2 = new Thread(runnable2);
Thread s3 = new Thread(runnable3);
Thread s4 = new Thread(runnable4);
s1.start();
s2.start();
s3.start();
s4.start();
复制代码

好不容易等到门开了,一个业务员走了出来,小T便一闪身溜了进去,门马上被小R锁上了。“你干啥呢,差点就夹到我了!”小T抱怨道。小R很差意思笑笑,说道:“原来是小T啊,你坐你坐,待会再向你解释,我先放下个业务员进来...”

好不容易应付完全部业务员,小R向小T解释了前因后果,并拿出了一把钥匙交给小T

private class ReportTask {
	public void report1(){
		synchronized(this){
		//省略部分代码...
		}
	}

	public void report2(){
		synchronized(this){
		//省略部分代码...
		}
	}

	public void report3(){
		synchronized(this){
		//省略部分代码...
		}
	}

	private String window = "window";
	public void report4(){
		synchronized (window){
			Log.e("R公司","小T" + "进办公室");
		}
	}
}
复制代码

"s先生天然有应对这种状况的妙计,这把钥匙能够打开窗户(持有window对象的内置锁),你之后能够从窗户直接爬进来,不用在门外排队(synchronized (obj){}同步代码块用synchronized声明方法的做用基本一致,都是对synchronized做用范围内的代码进行加锁保护,其区别在于synchronized同步代码块使用更加灵活、轻巧synchronized (obj){}括号内的对象参数即为该代码块持有锁的对象。例如上述例子中,前面三个report方法中的同步代码块持有锁的对象为ReportTask的实例对象,而report4方法中的同步代码块持有锁的对象则为window。由于对象都有本身的对象锁,只能保护属于本身的同步代码块或同步方法,因此即便其余线程进入前三个方法的同步代码块中并得到相应对象的锁,也不会阻塞进入report4方法的线程执行其中的同步代码)。"

拿到钥匙后,小T不再用和业务员一块儿在门外排队了


静态同步方法

了解了上述知识后,咱们回过头再来理解同步方法和静态同步方法的区别。从持有锁的对象的不一样咱们能够将synchronized同步代码的方式分为两大派系:

  • synchronized声明非静态方法同步代码块的synchronized (this){}和synchronized (非this对象){}这三者持有锁的对象实例对象(类的实例对象能够有不少个),线程想要执行该synchronized做用范围内的同步代码,需得到对象锁
public class SynchronizedTest {
    public synchronized void test1(){
        //持有锁的对象为SynchronizedTest的实例对象
    }

    public void test2(){
        synchronized (this){
            //持有锁的对象为SynchronizedTest的实例对象
        }
    }

    private String obj = "obj";
    public void test3(){
        synchronized (obj){
            //持有锁的对象为obj
        }
    }
}
复制代码
  • synchronized声明静态方法以及同步代码块的synchronized (类.class){}这二者持有锁的对象Class对象(每一个类只有一个Class对象,而Class对象是Java类编译后生成的.class文件,它包含了与类有关的信息),线程想要执行该synchronized做用范围内的同步代码,需得到类锁
public class SynchronizedTest {
    public static synchronized void test4(){
        //持有锁的对象为SynchronizedTest的Class对象(SynchronizedTest.class)
    }

    public void test5(){
        synchronized (SynchronizedTest.class){
            //持有锁的对象为SynchronizedTest的Class对象(SynchronizedTest.class)
        }
    }
}
复制代码

有关实例对象Class对象的详细知识你们能够继续在网上查找资料进行深挖,这里就不赘述了,总之咱们要记住的一点是

synchronized同步方法(代码块)持有锁的对象不一样,则多线程执行相应的同步代码时互不干扰;若相同,则得到该对象锁的线程先执行同步代码,其余访问同步代码的线程会被阻塞等待锁的释放


更新

为了更好地理解静态和非静态同步方法的区别,我这里补充一下示例吧

在某个平行宇宙中,小R穿越去了火影的世界,为了在村子里谋个一官半职,遂决定请村长鸣人(本体,即Class对象)和他的影分身(实例对象)吃拉面(影分身也能够理解为克隆体,现实中没找到啥好的例子能够代入,你们将就吧哈哈)。为了找到鸣人分布在各个地方的影分身,小R也使出了影分身

public class SynchronizedTest {
    public synchronized void test1(){
        try{
            System.out.println("请鸣人影分身 "+Thread.currentThread().getName()+" 吃拉面");
            Thread.sleep(1000);

        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("鸣人影分身 "+Thread.currentThread().getName()+" 吃完拉面了");
    }

    public static void main(String args[]) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                new SynchronizedTest().test1();
            }
        }, "1号");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                new SynchronizedTest().test1();
            }
        }, "2号");
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                new SynchronizedTest().test1();
            }
        }, "3号");
        t1.start();
        t2.start();
        t3.start();
    }
}
复制代码

鸣人的影分身各吃各的,并无出现争抢的现象(上述代码中非静态同步方法使用new SynchronizedTest().xxx的方式进行调用,因此它们持有锁的对象不一样,所以相互之间没有影响

至于鸣人本体嘛,确定要重点“贿赂”的,因此小R派了3个影分身去请他吃拉面

public class SynchronizedTest {
    public static synchronized void test2(){
        try{
            System.out.println("请鸣人 本体 吃拉面:" + Thread.currentThread().getName());
            Thread.sleep(1000);

        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("鸣人 本体 吃完拉面了:" + Thread.currentThread().getName());
    }

    public static void main(String args[]) {
        Thread t4 = new Thread(new Runnable() {
            @Override
            public void run() {
// test2();//test2方法是静态方法,能够直接调用
                new SynchronizedTest().test2();//这里的调用方式仅为了做对照
            }
        },"t4");
        Thread t5 = new Thread(new Runnable() {
            @Override
            public void run() {
                new SynchronizedTest().test2();
            }
        },"t5");
        Thread t6 = new Thread(new Runnable() {
            @Override
            public void run() {
                new SynchronizedTest().test2();
            }
        },"t6");

        t4.start();
        t5.start();
        t6.start();
    }
}
复制代码

鸣人本体只有一个,即时3我的一块儿请面也是要一碗一碗吃的(静态同步方法持有锁的对象都是Class对象,持有锁的线程正常执行任务,其余未持有锁的线程则阻塞等待

至于同步代码块synchronized (this){}、synchronized (xxx.class){}的区别和上面一致,我就不赘述了,总之理解静态和非静态同步方法的区别最重要的是理清Class对象实例对象的关系,这里推荐一篇博客给你们:JAVA中类、实例与Class对象。若是你们还有什么疑问,欢迎留言评论,我有(shang)空(ban)会(tou)来(lan)一一答复的

相关文章
相关标签/搜索