java 安全锁_java-多线程安全-锁

一 同步函数

1.1 一般的方法

同步的另一种体现形式:同步函数。

同步函数使用的锁是哪个?

经过分析:大概猜的是this,因为函数必须被对象调用。

验证:

写一个同步代码块,写一个同步函数,如果同步代码块中的锁对象和同步函数中的锁对象是同一个,

就同步了,就没有错误的数据了。如果不是同一个锁对象,就不同步出现错误数据。

让两个线程,一个线程在同步代码块中执行,一个线程在同步函数中执行。

总结:同步函数使用的锁时this。

同步函数和同步代码块有什么区别吗?

同步函数使用的锁是固定的this。当线程任务只需要一个同步时完全可以使用同步函数。

同步代码块使用的锁可以是任意对象。当线程任务中需要多个同步时,必须通过锁来区分,这时必须使用同步代码块。

同步代码块较为常用。

package test;

classTicket implements Runnable {

private int tickets = 100;

private Object obj = newObject();

boolean flag = true;

public voidrun() {

while (true) {

sale();

}

}

public synchronized void sale()//同步函数,使用的锁对象 this。

{

if (tickets > 0) {

try{

Thread.sleep(10);

} catch(InterruptedException e) {

}

System.out.println(Thread.currentThread().getName() + "...sale..." + tickets--);// 打印线程名称。

}

}

}

classThreadDemo4 {

public static voidmain(String[] args) {

Ticket t = newTicket();

Thread t1 = newThread(t);

Thread t2 = newThread(t);

t1.start();

try{

Thread.sleep(10);

} catch(InterruptedException e) {

}

// 切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。

t.flag = false;

t2.start();

}

}

1.2 静态方法

static 同步函数,使用的锁不是this,而是字节码文件对象, 类名.class

classTicket implements Runnable

{

private static int tickets = 100;

private Object obj = newObject();

boolean flag = true;

public voidrun()

{

if(flag){

while(true){

synchronized(Ticket.class){

if(tickets>0){

try{Thread.sleep(10);}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+"...obj..."+tickets--);//打印线程名称。

}

}

}

}

else{

while(true){

this.sale();

}

}

}

public static synchronized void sale()//

{

if(tickets>0)

{

try{Thread.sleep(10);}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。

}

}

}

classThreadDemo5

{

public static voidmain(String[] args)

{

Ticket t = newTicket();

Thread t1 = newThread(t);

Thread t2 = newThread(t);

t1.start();

try{Thread.sleep(10);}catch(InterruptedException e){}

//切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。

t.flag = false;

t2.start();

}

}

二 单例的安全问题

//饿汉式。 多线程并发饿汉式没问题。

classSingle

{private static final Single s = newSingle();privateSingle(){}public staticSingle getInstance()

{returns;

}

}//懒汉式。

classSingle

{private static Single s = null;privateSingle(){}/*并发访问会有安全隐患,所以加入同步机制解决安全问题。

但是,同步的出现降低了效率。

可以通过双重判断的方式,解决效率问题,减少判断锁的次数。

重在分析*/

public staticSingle getInstance()

{if(s==null)

{

synchronized(Single.class)

{if(s==null)//-->0 -->1

s = newSingle();

}

}returns;

}

}classDemo implements Runnable

{public voidrun()

{

Single.getInstance();

}

}classThreadDemo6

{public static voidmain(String[] args)

{

System.out.println("Hello World!");

}

}

三 死锁

同步的另一个弊端:

情况之一:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。

这时容易引发一种现象:死锁。

这种情况能避免就避免掉。

例:一碗饭.一个人拿一个筷子,谁也不放.

//Thread-0

synchronized(obj1)

{-->thread-0obj1

synchronized(obj2)

{

}

}//Thread-1

synchronized(obj2)

{

Thread-1obj2

synchronized(obj1)

{

}

}

例如

classTicket implements Runnable

{private int tickets = 100;private Object obj = newObject();

boolean flag= true;public voidrun()

{if(flag){while(true){

synchronized(obj){

sale();//this lock;

}

}

}else{while(true){this.sale();

}

}

}public synchronized void sale()//this lock

{

synchronized(obj)//obj lock

{if(tickets>0)

{try{Thread.sleep(10);}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+"...sale..."+tickets--);//打印线程名称。

}

}

}

}classThreadDemo7

{public static voidmain(String[] args)

{

Ticket t= newTicket();

Thread t1= newThread(t);

Thread t2= newThread(t);

t1.start();try{Thread.sleep(10);}catch(InterruptedException e){}//切换标记,之前,让主线程停一会,这时就只有一个t1线程在,它就会执行同步代码块。

t.flag = false;

t2.start();

}

}

又例如

classTest implements Runnable

{privateboolean flag;

Test(boolean flag)

{this.flag =flag;

}public voidrun()

{if(flag)

{while(true)

{

synchronized(MyLock.LOCKA)

{

System.out.println(Thread.currentThread().getName()+"...if......locka");

synchronized(MyLock.LOCKB)

{

System.out.println(Thread.currentThread().getName()+"...if......lockb");

}

}

}

}else{while(true)

{

synchronized(MyLock.LOCKB)

{

System.out.println(Thread.currentThread().getName()+"...else......lockb");

synchronized(MyLock.LOCKA)

{

System.out.println(Thread.currentThread().getName()+"...else......locka");

}

}

}

}

}

}//定义一个用于存储锁对象类。

classMyLock

{public static final Object LOCKA = newObject();public static final Object LOCKB = newObject();

}classDeadLockTest

{public static voidmain(String[] args)

{//创建两个线程任务。

Test t1 = new Test(true);

Test t2= new Test(false);

Thread t11= newThread(t1);

Thread t22= newThread(t2);

t11.start();

t22.start();

}

}

四 生产者和消费者

多线程中最为常见的应用案例:

生产者消费者问题。

生产和消费同时执行,需要多线程。

但是执行的任务却不相同,处理的资源确实相同的:线程间的通信。

1,描述一下资源。

2,描述生产者,因为具备着自己的任务。

3,描述消费者,因为具备着自己的任务。

问题1:数据错误:已经被生产很早期的商品,才被消费到。

出现线程安全问题,加入了同步解决。使用同步函数。

问题已解决:不会在消费到之前很早期的商品。

问题2:发现了连续生产却没有消费,同时对同一个商品进行多次消费。

希望的结果应该是生产一个商品,就被消费掉。生产下一个商品。

搞清楚几个问题?

生产者什么时候生产呢?消费者什么时候应该消费呢?

当盘子中没有面包时,就生产,如果有了面包,就不要生产。

当盘子中已有面包时,就消费,如果没有面包,就不要消费。

21c8f0befcdef6e5dcc892c304fe6a98.png

//1,描述资源。属性:商品名称和编号, 行为:对商品名称赋值,获取商品。

classResource

{privateString name;private int count = 1;//1,提供设置的方法。

publicvoid set(String name)

{//给成员变量赋值并加上编号。

this.name = name +count;//编号自增。

count++;//打印生产了哪个商品。

System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);

}publicvoid out()

{

System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);

}

}//2,描述生产者。

classProducer implements Runnable

{privateResource r ;//生产者一初始化就要有资源,需要将资源传递到构造函数中。

Producer(Resource r)

{this.r =r;

}public voidrun()

{while(true)

{

r.set("面包");

}

}

}//3,描述消费者。

classConsumer implements Runnable

{privateResource r ;//消费者一初始化就要有资源,需要将资源传递到构造函数中。

Consumer(Resource r)

{this.r =r;

}public voidrun()

{while(true)

{

r.out();

}

}

}classThreadDemo8

{public static voidmain(String[] args)

{//1,创建资源对象。

Resource r = newResource();//2,创建线程任务。

Producer pro = newProducer(r);

Consumer con= newConsumer(r);//3,创建线程。

Thread t1 = newThread(pro);

Thread t2= newThread(con);

t1.start();

t2.start();

}

}

31d0878b50f865209707ed514c546156.png

问题1: 已经被生产很早期的商品,才被消费到。

出现线程安全问题,加入了同步解决。使用同步函数。

问题已解决:不会在消费到之前很早期的商品。

classResource

{privateString name;private int count = 1;//1,提供设置的方法。

public synchronized void set(String name)

{//给成员变量赋值并加上编号。

this.name = name +count;//编号自增。

count++;//打印生产了哪个商品。

System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);

}public synchronized void out()

{

System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);

}

}

b4b8539c2dbaced99e403873fc7ba648.png

问题2:发现了连续生产却没有消费,同时对同一个商品进行多次消费。

希望的结果应该是生产一个商品,就被消费掉。生产下一个商品。

搞清楚几个问题?

生产者什么时候生产呢?消费者什么时候应该消费呢?

当盘子中没有面包时,就生产,如果有了面包,就不要生产。

当盘子中已有面包时,就消费,如果没有面包,就不要消费。

生产者生产了商品后应该告诉消费者来消费。这时的生产者应该处于等待状态。

消费者消费了商品后,应该告诉生产者,这时消费者处于等待状态。

等待:wait();

告诉:notify();//唤醒

问题解决:实现生产一个消费一个。

4.1 等待/唤醒机制

wait(): 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。

notify():会唤醒线程池中任意一个等待的线程。

notifyAll():会唤醒线程池中所有的等待线程。

记住:这些方法必须使用在同步中,因为必须要标识wait,notify等方法所属的锁。

同一个锁上的notify,只能唤醒该锁上的被wait的线程。

例:等待吃饭

还有银行

为什么这些方法定义在Object类中呢?

因为这些方法必须标识所属的锁,而锁可以是任意对象,任意对象可以调用的方法必然时Object类中的方法。

举例:小朋友抓人游戏

/1,描述资源。属性:商品名称和编号, 行为:对商品名称赋值,获取商品。classResource

{privateString name;private int count = 1;//定义标记。

private boolean flag = false;//1,提供设置的方法。

public synchronized void set(String name)

{if(flag)try{this.wait();}catch(InterruptedException e){}//给成员变量赋值并加上编号。

this.name = name +count;//编号自增。

count++;//打印生产了哪个商品。

System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);//将标记改为true。

flag = true;//唤醒消费者。

this.notify();

}public synchronized void out()

{if(!flag)try{this.wait();}catch(InterruptedException e){}

System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);//将标记该为false。

flag = false;//唤醒生产者。

this.notify();

}

}//2,描述生产者。

classProducer implements Runnable

{privateResource r ;//生产者一初始化就要有资源,需要将资源传递到构造函数中。

Producer(Resource r)

{this.r =r;

}public voidrun()

{while(true)

{

r.set("面包");

}

}

}//3,描述消费者。

classConsumer implements Runnable

{privateResource r ;//消费者一初始化就要有资源,需要将资源传递到构造函数中。

Consumer(Resource r)

{this.r =r;

}public voidrun()

{while(true)

{

r.out();

}

}

}classThreadDemo9

{public static voidmain(String[] args)

{//1,创建资源对象。

Resource r = newResource();//2,创建线程任务。

Producer pro = newProducer(r);

Consumer con= newConsumer(r);//3,创建线程。

Thread t1 = newThread(pro);

Thread t2= newThread(con);

t1.start();

t2.start();

}

}

五 多生产和多消费

fc80191f7979b262295fcc2ea6ed3750.png

加入多生产多消费

public static voidmain(String[] args)

{//1,创建资源对象。

Resource r = newResource();//2,创建线程任务。

Producer pro = newProducer(r);

Consumer con= newConsumer(r);//3,创建线程。

Thread t1 = newThread(pro);

Thread t2= newThread(pro);

Thread t3= newThread(con);

Thread t4= newThread(con);

t1.start();

t2.start();

t3.start();

t4.start();

}

eeb72aa7d11905e6b99da0c6bf1a8a85.png

问题1 生产了商品没有被消费,同一个商品被消费多次。

Thread-2......生产者....面包40527//没有被消费。

Thread-3......消费者....面包40528

Thread-2....消费者....面包40528

分析过程

3e970baee8c83cacba16c956f084b4aa.png

949cf17d732b30130ac523c713bc6347.png

被唤醒的线程没有判断标记,造成问题1的产生。

解决:只要让被唤醒的线程必须判断标记就可以了。将if判断标记的方式改为while判断标记。记住:多生产多消费,必须时while判断条件。

classResource

{privateString name;private int count = 1;//定义标记。

private boolean flag = false;//1,提供设置的方法。

public synchronized void set(String name)//

{while(flag) //try{this.wait();}catch(InterruptedException e){}//t1等 t2等//给成员变量赋值并加上编号。

this.name = name + count;//商品1 商品2 商品3//编号自增。

count++;//2 3 4//打印生产了哪个商品。

System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);//生产 商品1 生产商品2 生产商品3//将标记改为true。

flag = true;//唤醒消费者。

this.notify();

}public synchronized void out()//

{while(!flag)//try{this.wait();}catch(InterruptedException e){}//t3等//t4等

System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);//消费 商品1//将标记该为false。

flag = false;//唤醒生产者。

this.notify();

}

}//2,描述生产者。

classProducer implements Runnable

{privateResource r ;//生产者一初始化就要有资源,需要将资源传递到构造函数中。

Producer(Resource r)

{this.r =r;

}public voidrun()

{while(true)

{

r.set("面包");

}

}

}//3,描述消费者。

classConsumer implements Runnable

{privateResource r ;//消费者一初始化就要有资源,需要将资源传递到构造函数中。

Consumer(Resource r)

{this.r =r;

}public voidrun()

{while(true)

{

r.out();

}

}

}classThreadDemo10

{public static voidmain(String[] args)

{//1,创建资源对象。

Resource r = newResource();//2,创建线程任务。

Producer pro = newProducer(r);

Consumer con= newConsumer(r);//3,创建线程。

Thread t1 = newThread(pro);

Thread t2= newThread(pro);

Thread t3= newThread(con);

Thread t4= newThread(con);

t1.start();

t2.start();

t3.start();

t4.start();

}

}

f0336fd3271ec7cb2bf9b9f482edb326.png

问题2:发现while判断后,死锁了。

原因:生产方唤醒了线程池中生产方的线程。本方唤醒了本方。

解决:希望本方要唤醒对方,没有对应的方法,所以只能唤醒所有。

defd6631f09a9924afef791009175c7a.png

六 Condition

Condition/*

jdk1.5以后提供多生产多消费的解决方案。

在java.util.concurrent.locks 软件包中提供相应的解决方案

Lock接口:比同步更厉害,有更多操作。lock():获取锁 unlock():释放锁;

提供了一个更加面对对象的锁,在该锁中提供了更多的显示的锁操作。

替代同步。

升级到JDK1.5,先把同步改成 Lock。

已经将旧锁替换成新锁,那么锁上的监视器方法(wait,notify,notifyAll)也应该替换成新锁的监视器方法。

而jdk1.5中将这些原有的监视器方法封装到了一个Condition对象中。

想要获取监视器方法,需要先获取Condition对象。

Condition对象的出现其实就是替代了Object中的监视器方法。

await();

signal();

signalAll();

将所有的监视器方法替换成了Condition。

功能和ThreadDemo10.java老程序的功能一样,仅仅是用新的对象。改了写法而已。

但是问题依旧;效率还是低。

希望本方可以唤醒对方中的一个。

老程序中可以通过两个锁嵌套完成,但是容易引发死锁。

新程序中,就可以解决这个问题,只用一个锁,

可以在一个锁上加上多个监视器对象。

*/

a6dc7bb93094e6c28b03935c11850f24.png

import java.util.concurrent.locks.*;classResource

{privateString name;private int count = 1;//定义一个锁对象。

private final Lock lock = newReentrantLock();//获取锁上的Condition对象。为了解决本方唤醒对方的问题。可以一个锁创建两个监视器对象。

private Condition produce = lock.newCondition();//负责生产。

private Condition consume = lock.newCondition();//负责消费。//定义标记。

private boolean flag = false;//1,提供设置的方法。

public void set(String name)//

{//获取锁。

lock.lock();try{while(flag)try{produce.await();}catch(InterruptedException e){}//t1等 t2等

this.name = name + count;//商品1 商品2 商品3

count++;//2 3 4

System.out.println(Thread.currentThread().getName()+"......生产者...."+this.name);//生产 商品1 生产商品2 生产商品3//将标记改为true。

flag = true;//执行的消费者的唤醒。唤醒一个消费者就哦了。

consume.signal();

}finally{lock.unlock();//一定要执行。

}

}public void out()//

{lock.lock();try{while(!flag)try{consume.await();}catch(InterruptedException e){}//t3等//t4等

System.out.println(Thread.currentThread().getName()+"....消费者...."+this.name);//消费 商品1//将标记该为false。

flag = false;//produce.signal();

}finally{lock.unlock();

}

}

}//2,描述生产者。

classProducer implements Runnable

{privateResource r ;//生产者一初始化就要有资源,需要将资源传递到构造函数中。

Producer(Resource r)

{this.r =r;

}public voidrun()

{while(true)

{

r.set("面包");

}

}

}//3,描述消费者。

classConsumer implements Runnable

{privateResource r ;//消费者一初始化就要有资源,需要将资源传递到构造函数中。

Consumer(Resource r)

{this.r =r;

}public voidrun()

{while(true)

{

r.out();

}

}

}classThreadDemo11

{public static voidmain(String[] args)

{//1,创建资源对象。

Resource r = newResource();//2,创建线程任务。

Producer pro = newProducer(r);

Consumer con= newConsumer(r);//3,创建线程。

Thread t1 = newThread(pro);

Thread t2= newThread(pro);

Thread t3= newThread(con);

Thread t4= newThread(con);

t1.start();

t2.start();

t3.start();

t4.start();

}

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值