---------------------- 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()
{
1:Synchronized(对象A)
{
需要同步的代码块;
操作“天字1号”仓库
}
2:Synchronized(对象B)
{
需要同步的代码块;
操作“地字1号”仓库
}
3:Synchronized(对象A)
{
需要同步的代码块;
操作“天字1号”仓库
}
}
那么现在三个不同功能的同步代码块,1和3操作的是同一个资源,那么需要找一个人去看守这个仓库(现在阿把锁看成是一个保安),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呢?以前都是这样加的
那么其实经过实验,分别在Input和Output类中创建了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();
t1和t2所绑定的锁对象是不一样,明确这一点
---------------------- ASP.Net+Android+IOS开发、.Net培训、期待与您交流! ----------------------
详细请查看:http://edu.csdn.net