java进阶打卡17

匿名内部类方式实现线程的创建

匿名:没有名字
内部类:写在其他类内部的类

匿名内部类作用:简化代码
    把子类继承父类,重写父类的方法,创建子类对象合成一步完成
    把实现类实现接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字

格式:
    new 父类/接口(){
        重写父类/接口中的方法;
    };

例如:
//线程的父类是Thread
//new MyThread().start();
new Thread(){
	 @Override
     public void run() {
     	...
     }
}.start();

//线程的接口Runnable
//Runnable r = new RunnableImpl();//多态
Runnable r = new Runnable() {
	@Override
    public void run() {
		...
    }
};
new Thread(r).start();

//简化接口的方式
new Thread(new Runnable() {
	@Override
    public void run() {
    	...
    }
}).start();

线程安全问题

售票窗口1-100号
一个窗口卖100张票,没有问题。单线程程序是不会出现线程安全问题的。

售票窗口1-33号	售票窗口34-67号	售票窗口68-100号
3个窗口一起卖票,卖的票不同,也不会出现问题。多线程程序,没有访问共享数据,不会产生问题。

售票窗口1-100号	售票窗口1-100号	售票窗口1-100号
100				100				出现了重复的票
1				0				-1	出现了不存在的票
3个窗口卖的票是一样的,就会出现安全问题。多线程访问了共享的数据,会产生线程安全问题。

【模拟卖票案例:创建3个线程,同时开启,对共享的票进行出售】
开启了3个线程t0,t1,t2 3个线程一起抢夺cpu的执行权,谁抢到谁执行
【出现了线程安全问题,卖票出现了重复的票和不存在的票】
(1)不存在的票:
t2线程先抢到了cpu的执行权,进入到run方法中执行,执行到if语句,就失去了cpu的执行权。
t2先睡醒了,抢到了cpu的执行权,在if条件判断语句后,继续执行,进行卖票。
Thread-2-->正在卖第1张票 ticket--
ticket = 0 继续判断 0!>0 不执行

t1线程在t2线程失去了cpu执行权时抢到了cpu的执行权,进入到run方法中执行,执行到if语句,就失去了cpu的执行权。
t1在t2睡醒卖完票后睡醒了,抢到了cpu的执行权,在if条件判断语句后,继续执行,进行卖票。
Thread-1-->正在卖第0张票 ticket--
ticket = -1 继续判断 -1!>0 不执行

t0线程在t1线程失去了cpu执行权时抢到了cpu的执行权,进入到run方法中执行,执行到if语句,就失去了cpu的执行权。
t0在t1睡醒卖完票后睡醒了,抢到了cpu的执行权,在if条件判断语句后,继续执行,进行卖票。
Thread-0-->正在卖第-1张票 ticket--
ticket = -2 继续判断 -2!>0 不执行

(2)重复的票
t0 t2 t1 同时执行到了 "正在卖第"+ticket+"张票"
这时候ticket还没有执行到--

注意:
线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了cpu的执行权;
让其他的线程只能等待,等待当前线程卖完票,其他线程再进行卖票
保证:始终一个线程在卖票

解决线程安全问题的第一种方案:使用同步代码块

格式:
    synchronized(锁对象){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
    }

注意:
1. 同步代码块中的锁对象,可以是任意的对象
例如:	Object obj = new Object();
2. 但是必须保证多个线程使用的锁对象是同一个
例如:	在重写的run方法之外创建对象
3. 锁对象作用:
	把同步代码块锁住,只让一个线程在同步代码块中执行
	
synchronized (obj){
	...
}	

解决线程安全问题的第二种方案:使用同步方法

使用步骤:
1. 把访问了共享数据的代码抽取出来,放到一个方法中
2. 在方法上添加synchronized修饰符

格式:定义方法的格式
修饰符 synchronized 返回值类型 方法名(参数列表){
    可能会出现线程安全问题的代码(访问了共享数据的代码)
}

例如:
public synchronized void payTicket() {
	...
}

定义一个同步方法,同步方法也会把方法内部的代码锁住,只让一个线程执行。
同步方法的锁对象是谁?就是实现类对象 new RunnableImpl(),也就是this。
验证:
public /*synchronized*/ void payTicket() {
        synchronized (this){
        	...
        }
}

静态的同步方法,锁对象是谁?不能是this,this是创建对象之后产生的,静态方法优先于对象。
静态方法的锁对象是本类的class属性-->class文件对象(反射)
验证:
public static /*synchronized*/ void payTicketStatic() {
        synchronized (RunnableImpl.class){   
        ...
        }
}   
注意:使用静态同步方法时,成员变量也应该是静态的  

解决线程安全问题的第三种方案:使用Lock锁

java.util.concurrent.Locks.Lock接口
Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法:
    void Lock() 获取锁
    void unLock() 释放锁
java.util.concurrent.Locks.ReentrantLock implements Lock接口

使用步骤:
1. 在成员位置创建一个ReentrantLock对象
例如:	Lock l = new ReentrantLock();   
2. 在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
例如:	l.lock();
3. 在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
例如:	l.unlock();

注意:try..catch...finally{ l.unlock(); }//无论程序是否执行,都会把锁释放
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值