黑马程序员-多线程(线程的安全问题与锁的理解)

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------

 

多线程的安全问题

多线程出现安全问题的根源:

多个线程共享同一个资源,并且同时在操作该资源,操作该资源的代码有多句

卖票的代码中

run()

{

         System.out.println(“卖出了第:”+tick+”张票”);

tick--;

}//上述同步代码有两句,不加锁就会产生安全问题

但是有没有方法,不加锁也能避免安全问题?

修改同步代码为一句可避免

run()

{

         System.out.println(“卖出了第:”+tick-- +”张票”);

}//此时同步代码只有一句,不存在安全问题

 

多线程对设计模式的影响与选择

 

单例设计模式。

饿汉式。

 

class Single {
	private static final Single s = new Single();// 内部创建对象

	private Single() {
	}// 把构造方法私有,外界不能创建对象

	public static Single getInstance() {
		return s;
	}
}


懒汉式

class Single {
	private static Single s = null;

	private Single() {
	}

	public static Single getInstance() {
		if (s == null)
			s = new Single();// 如果没创建,才创建
		return s;
	}
}


 

多线程的并发操作对饿汉式没有影响,但是对懒汉式存在安全问题

问题出现在getInstance()中的if(s==null)判断上,如果A线程执行到这个地方,挂起了,B线程此时也进来判断了,那么会导致创建多个对象,从而导致安全问题。

那么解决安全问题可以加同步锁来解决如下:

class Single {
	private static Single s = null;

	private Single() {
	}

	public static Single getInstance() {
		synchronized (Single.class) {
			if (s == null)
				s = new Single();
		}
		return s;
	}
}


 

此时可以解决安全问题,但是由此又导致了此段代码的执行效率低下,因为每次线程进来前都需要判断锁有没有开着,所以导致效率问题,为解决这个问题,又改进如下

class Single {
	private static Single s = null;

	private Single() {
	}

	public static Single getInstance() {
		if (s == null) {
			synchronized (Single.class) {
				if (s == null)
					s = new Single();
			}
		}
		return s;
	}
}

判断之前增加一个if判断语句,如果为空,才进行锁判断,否则不进行锁判断,直接取走对象,问题还是一样,如果此时取走对象,对象还没建立会出现什么问题?取走的对象指向为空,抛空指向异常
问题二:为什么这个能够解决效率问题?增加IF判断之后,线程每次进去之前要判断synchronized(Single.class)情况变为了判断IF语句而已,还是要判断,还是这两个语句的执行本身存在效率问题,例如执行IF语句需要1ms,而synchronized需要5ms这种?

 

我的理解:其实这个效率的问题是针对大并发量来说的,如果是小并发,则并不会提高多少效率。

我们可以分析一下,两者的区别就在于:前者无论如何都要判断锁或者判断锁+判断if

 后者只需要在第一次创建完毕是要判断if+判断锁+判断if,以后都只需要判断if

当有大并发量的时候,前者的叠加效应会加大这个负担,而后者不存在叠加效应或者可以忽略不计

在者把一个方法声明为synchronized也会大大降低效率

 

ps:还有一地方需要注意的是synchronized(Single.class)里面的对象,静态方法是先于对象存在于内存当中的,所以,加载进内存的时候,这个锁的对象只有是类的编译后的字节码文件对象Single.class

 

Java解决多线程的安全问题有专业的解决方法

Synchronized(对象)

{

       需要同步的代码块;

}

问题一:这里就有一个疑问,毕老师说的是,这个synchronized需要传入一个对象,只要是对象就可以了,也就理解为传入一个任意对象,这里不能理解,为什么不是特定的对象而是任意对象,例子中是传入Object对象,但是此对象和这个买票程序到底存在什么关系?

我的理解:其实这个传入的对象函数,只须把它看成是一个标记,因为一个程序中,可能有不止一段的同步代码,那么为了让线程识别不同的同步代码块

即假设在run()中有三个不同功能的代码块,分别操作不同的资源

run()

{

    1Synchronized(对象A)

        {    

      需要同步的代码块;

      操作“天字1号”仓库

        }

    2Synchronized(对象B)

       {

         需要同步的代码块;

         操作“地字1号”仓库

       }

3Synchronized(对象A)

    {  

         需要同步的代码块;

   操作“天字1号”仓库

    }

}

那么现在三个不同功能的同步代码块,13操作的是同一个资源,那么需要找一个人去看守这个仓库(现在阿把锁看成是一个保安),2操作的是另一个仓库,也就需要空一个保安去看守,现在对象A就是一个保安,对象B是另一个保安

 

例子:

class Ticket implements Runnable
{
	private  int tick = 100;
	static Object obj = new Object();//必须是用static修饰Object对象,否则,每创建一个线程对象,创建一个Object对象
	public void run()
	{
		while(true)
		{
			synchronized(obj)//为什么传入任意对象就可以锁住?
			{
				if(tick>0)
				{
					try{Thread.sleep(100);}catch(Exception e){}
		System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
				}
			}
		}
	}
}
class  TickDemo2
{
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}


 

对象如同锁,持有锁的线程可以再同步代码块中执行

没有持有锁的线程,即使获取CPU的执行权也无法执行,因为没有锁

如何理解上面两句话?

 

同步的前提

(1)必须是多个线程使用

(2)必须是多个线程使用同一个锁

满足以上两个条件才是同步

------------------------------------------------------------------------

同步锁synchronized可修饰代码块,也可以修饰方法,但是不推荐使用修饰方法,synchronized修饰方法带来两弊端

1:修饰方法无法控制锁对象

2:修饰方法就把方法里的代码全部都变成同步,也就变成了单线程

 

class Cus implements Runnable
{
	private Bank b = new Bank();
	public void run()//不能把synchronized锁放在run方法这里,放在这里的话,就变成单一一个线程在里面执行,其他线程不能进入
	{
		for(int i=0;i<3;i++)
		{
			b.add(100);
		}
	}
}
class Bank
{
	int num;
	public synchronized void add(int n)
	{
		num += n;
		System.out.println(num);
	}
}
class  BankDemo
{
	public static void main(String[] args) 
	{
		Cus d = new Cus();
		Thread d1 = new Thread(d);
		Thread d2 = new Thread(d);
		d1.start();
		d2.start();
	}
}


 

同步代码被静态修饰之后,所用的锁是什么?

这里说的同步代码被修饰,说的是需要被操作的资源被静态修饰,也可以说某个方法里的某部分代码被静态修饰,因为可以把某个代码看成是资源

静态的锁不可能是this,因为静态在进内存的时候还没有类对象,但是,有该类对应的字节码文件对象,类名.Class,所以此时的锁是字节码文件对象

class Ticket implements Runnable
{
	private static  int tick = 100;//同步函数被静态修饰后
	//static Object obj = new Object();//Object必须要用static修饰
	boolean flag = true;
	public  void run()
	{
		if(flag)
		{
			while(true)
			{
				synchronized(Ticket.class)//此时的锁是字节码文件对象
				{
					if(tick>0)
					{
						try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....code:"+tick--);
					}
				}
			}
		}
}

死锁的形成是不同的形成锁嵌套,互相等待造成的

线程间通信,解决安全问题和锁对象的理解(重点理解)

观察代码,重点是红色部分

class Res 
{
	String name;
	String sex;
}
class Input implements Runnable
{
	private Res r;
	Object obj = new Object();
	Input(Res r)
	{
		this.r = r;
	}
	public void run()
	{
		int x = 0;
		while(true)
		{
			synchronized(Output.class)// synchronized(obj)
				{
					if(x==0)
					{
						r.name = "张锡林";
						r.sex = "男";
					}
					else
					{
						r.name = "刘木敬";
						r.sex = "女";
					}
					x = (x+1)%2;
				}
		}
	}
}
class Output implements Runnable
{
	private Res r;
	Object obj = new Object();
	//建立Res类型的引用,不必开辟对象,在外部使用的时候在创建对象,这里只需要接收对象的引用即可
	Output(Res r)
	{
		this.r = r;
	}
	public void run()
	{
				while(true)
				{
					synchronized(Output.class)// synchronized(obj)
						{
						System.out.println(r.name +"..." + r.sex);
						}
				}
	}
}
class InputOotputDemo 
{
	public static void main(String[] args) 
	{
		Res r = new Res();
		Input in = new Input(r);
		Output out = new Output(r);

		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);

		t1.start();
		t2.start();
	}
}


 

输出结果为

张锡林...

刘木敬...

张锡林...

刘木敬...

刘木敬...

……

这个代码恰好可以帮助理解synchronized(锁对象)中的锁对象

首先,如果上述代码,没有加synchronized(Input.class),则会产生安全问题

输出的结果会产生所谓妖的问题,

张锡林...

张锡林...

刘木敬...

刘木敬...

那是因为,没有同步操作Res这个资源,两个线程一边在读,一边在写,有可能写到一半的时候,读取进来了,把写好的一半拿走

所以,需要加锁给同步一下,那么可以首先讨论一下为什么不是在synchronized()中加入上帝类的对象Object obj呢?以前都是这样加的

那么其实经过实验,分别在InputOutput类中创建了Object obj对象,观察代码标示黄色部分,加了锁之后,结果并没有发生变化,这个原因又在哪?

此时,应该从多线程同步的两个原则思考

是否存在多个线程

是否用的是同一个锁

第一个确定无疑,是属于多线程

问题就是出在第二点,他们用的锁是不是同一个锁?

表面上看起来,黄色标识部分都是Object obj,但是,这两个obj对象并不是同一个,他们是分别在各自的类中开辟的新对象,是不同的两个对象,而不是同一个对象,即使现在把他们传入锁内,也不能起到同步的作用

现在可以来思考,synchronized(锁对象)中的锁对象到底和同步有什么关系又是怎么保证同步的

我们知道,要保证同步,就要确保锁对象的唯一性,在内存中,有什么是唯一的?上回说过字节码文件对象在类加载进内存的时候就存在,类唯一,类的字节码文件对象也就唯一,那么我们就可以用Input.class或者Output.class这两个其中的一个去作为锁对象。

到这里,基本能够明白锁对象的用处,其实,锁对象就是锁本身,这个锁必须是唯一的,但是并不是说只有一把锁,其实形象一点就是,这把锁有很多拷贝,他们的钥匙就只有一把,某一时刻只能开一个锁。

 

等待唤醒机制

Wait()sleep()的区别

1

Wait()是等待,释放CPU执行权,同时释放锁

Sleep()也可以理解为等待,释放CPU执行权,但是不释放锁

2

Wait()进入等待后,必须notify()才能够被唤醒,否则一直等待

Sleep()调用时需要同时传入一个时间参数,入Sleep(100),这个表示,等待100毫秒,时间到后,自动被唤醒,进入就绪状态

 

注意使用wait()notify()的细节

必须要标记这个wait()的所属的锁对象,就是你必须要让编译器知道,你想要哪一个锁中的哪一个线程等待或者唤醒,因为,程序中可能会同时存在多段同步锁,他们的锁对象可能不一样

以上面“线程间通信”的代码为例子,要在同步中加入wait()

notify(),则格式如下(Input.class).wait()  (Input.class) .notify

 

此时可以引出一个问题的答案

为什么要把wait()notify()notifyAll()定义在Object类中?

因为锁有可能是任意的对象,Object是上帝类,是任意类的父类

任意对象都是继承了父类的,也就继承了父类的方法,父类中定义了这两个方法,那么任何锁都能够调用,这也就是java多态的体现

 

PS: wait()必须要在同步代码块中使用,也就是说,使用wait()必须要加锁。一段多线程代码没有加锁就不是同步代码块,如果不加锁,使用了wait()编译会不会报错,但是运行会报错

 

使用d.wait()要写try…catch()语句,如果对象d调用了d.wait(),那么如果通过d.notify()属于正常唤醒,运行时不会抛异常,但是还有另外一个方法(相对粗暴)可以强制d.wait()interrupt()可以强制唤醒等待的线程,如果d.wait()被打断了,线程被唤醒,那么d.wait()处就会抛一个异常,是在运行的时候抛异常,但是编译可通过

 

JDK升级到5.0之后锁锁的替代àlock和Object监视器的方法(wait,notify,notifyAll)的替代-àcondition

 

其实主要的改进就是将锁的实现过程给明朗化,之前synchronized()方法是一个已经封装好的锁,对象执行到synchronized()方法的时候,内部自动判断锁没锁,什么时候释放锁,外部是不知道的

现在升级后的就将锁的锁功能和释放锁功能,单独拿出来作为一个方法。其实也就是方便使用

 

Conditon 替代了Object中的wait(),notify()notifyAll()方法

使用时有一点需要特别注意

前面说到wait()sleep()的区别的时候,说到执行到wait()的时候,交出CPU执行权,并且释放锁,而sleep()只是交出CPU的执行权而已

这里就又引出wait()condition.await()的区别,同样的condition.await()也只是交出CPU的执行权而已,并没有释放锁,所以这里我们必须要手动加上lock.unlock();

示例如下

Try

{

condition.await();

}

catch(Exception e)

{}

Finally

{

        lock.unlock();

}

 

Condition 能够绑定多个所对象,通过lock.newCondition()返回一个condition对象

可以通过Conditon t1 = lock.newCondition();

              Conditont2 = lock.newCondition();

绑定不同的锁对象

t1.await();

t1.signal();

t2.await();

t2.signal();

t1t2所绑定的锁对象是不一样,明确这一点



 

 

---------------------- ASP.Net+Android+IOS开发.Net培训、期待与您交流! ----------------------

详细请查看:http://edu.csdn.net 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值