1.判断程序是否会出现线程安全问题
-
是否是多线程环境
-
是否存在共享数据
-
是否多个线程操作共享数据
public class MyThread01 implements Runnable { private int ticket = 10; @Override public void run() { while (ticket>0){ System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票"); ticket--; } } } class TestMyThread01 { public static void main(String[] args) { MyThread01 r = new MyThread01(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); Thread t3 = new Thread(r); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } } 运行结果: 窗口2正在出售第10张票 窗口2正在出售第9张票 窗口2正在出售第8张票 窗口2正在出售第7张票 窗口2正在出售第6张票 窗口2正在出售第5张票 窗口2正在出售第4张票 窗口2正在出售第3张票 窗口2正在出售第2张票 窗口2正在出售第1张票 窗口1正在出售第10张票 窗口3正在出售第10张票
以上是模拟多窗口售票的代码,可以发现在多线程环境下数据产生了问题,第10张票被卖了3次,这显然是很不合理的。
2.如何解决线程安全问题
解决办法:同步机制,同步代码块,同步方法,使任意一个时刻只能有一个线程操作这个资源。
2.1 同步代码
public class MyThread02 implements Runnable {
private int ticket = 10;
@Override
public void run() {
while (ticket > 0) {
synchronized (this) {
if(ticket>0){
//System.out.println(this.getClass().getName().toString());
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket-- + "张票");
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
上述代码在操作数据的位置加上可synchronized关键字,格式:
synchronized(锁对象){
可能会出现线程安全问题的代码;
}
当某一个线程执行同步代码块时,其它线程将无法执行当前同步代码块,会发生阻塞,等当前线程执行完同步代码块后,所有的线程开始抢夺线程的执行权,抢到执行权的线程将进入同步代码块,执行其中的代码。
2.2 同步方法
public class MyThread03 implements Runnable {
private int ticket = 10;
@Override
public void run() {
while (ticket > 0) {
sell();
}
}
//在方法上加synchronized关键字
private synchronized void sell() {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket-- + "张票");
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.3 synchronized关键字总结
synchronized关键字的三种应用方式:
- 普通同步方法上加锁(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
- 在静态方法上加锁,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
- 在代码块上加锁,锁是括号里面的对象,可以是class对象,实例对象,或者是其他任意对象。
同步的作用:解决了线程安全问题。
同步的弊端:当线程相当多时,每个线程都会去判断同步上的锁,需要消耗耗费较多资源,降低程序的运行效率。
3.常见的线程安全的类
- StringBuffer
- HashTable
- Vector
- ConcurrentHashMap
4.Lock锁
4.1 lock锁介绍
JDK5以后提供了一个新的锁,Lock。Lock是一个接口,里面实现的方法如下:
//获取锁
void lock();
//获取锁过程中可以响应中断
void lockInterruptibly() throws InterruptedException;
//非阻塞式响应能够马上返回,获取锁返回true,反之false
boolean tryLock();
//超时获取锁,在超时time内或未中断情况下,可以获取锁
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//释放锁
void unlock();
//获取与lock绑定的等待通知组件,当前线程必须获取锁才能进行等待
//等待时释放锁,当再次获取锁才能从等待中返回
Condition newCondition();
lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是获取锁的方法。以下是lock()的使用方法:
public class MyThread04 implements Runnable {
private int ticket = 10;
//Lock接口的实现类
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (ticket > 0) {
//获取锁,加锁和解锁处需要通过lock()和unlock()显示指出。
//释放锁的过程必须手动执行,所以一般会在finally块中写unlock()以防死锁。
lock.lock();
try {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket-- + "张票");
}
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lock.unlock();
}
}
}
}
4.2 synchronized和lock的区别
- synchronized是java中的关键字,由jvm执行,Lock是接口,提供了操作锁的代码。
- synchronized在发生异常时,会自动释放掉锁,而Lock则不会,一般需要在finally里释放。
- Lock能够响应中断,让等待状态的线程停止等待;synchronized不行。