匿名内部类方式实现线程的创建
匿名:没有名字
内部类:写在其他类内部的类
匿名内部类作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合成一步完成
把实现类实现接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
格式:
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( ) ; } //无论程序是否执行,都会把锁释放