多线程安全问题
线程安全问题的产生
线程安全问题:
- 单线程程序是不会出现线程安全问题
- 多线程程序没有访问共享数据不会产生问题
- 多线程访问了共享的数据,就会产出线程安全问题
模拟卖票:
- 创建三个线程,同时开启,对共享的票进行出售
- 会发现当我们多个线访问同一个资源时,且多个线程中对资源有写的操作,就容易出现线程安全问题。(重复票与不存在票)
- 要解决线程并发访问同一资源的安全性问题,也就是解决重复票与不存在票问题,Java提供了同步机制来解决
注意:
- 线程安全问题是不能产生的,我们可以让一个线程在访问共享数据时,无论是否失去了cpu的执行权;让其他线程只能等待,等待当前线程卖完票,其他线程再进行卖票
- 保证:只使用一个线程在卖票
public class Demo01Ticket {
public static void main(String[] args) {
//创建Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
//调用start方法开启多线程
t0.start();
t1.start();
t2.start();
}
}
//实现卖票案例
public class RunnableImpl implements Runnable {
//定义一个多线程共享的票源
private int ticket =100;
//设置线程任务:卖票
@Override
public void run() {
while (true){
if (ticket>0){
//提高安全问题出现的概率
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
解决这种线程安全问题的方法
方法一
解决线程安全问题的第一种方案:使用同步代码块
格式:
-
synchronized(锁对象){
-
可能会出现线程安全问题的代码(访问了共享数据的代码)
- }
注意:
-
1.通过代码块中的锁对象,可以使用任意的对象
-
2.但是必须保证多个线程使用的锁对象是同一个
-
3.锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
public class RunnableImpl implements Runnable {
//创建一个锁对象
Object obj = new Object();
//定义一个多线程共享的票源
private int ticket =100;
//设置线程任务:卖票
@Override
public void run() {
while (true){
synchronized (obj){
if (ticket>0){
//提高安全问题出现的概率
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
方法二
解决线程安全问题的第二种方案:使用同步方法
使用步骤:
-
1.把访问了共享数据的代码抽取出来,放在一个方法中去
-
2.在方法上添加synchronized修饰符
- 格式:定义方法的格式
-
修饰符 synchronized 返回值类型 方法名(参数列表){
-
可能会出现线程安全问题的代码(访问了共享数据的代码)
-
}
public class RunnableImpl implements Runnable {
//定义一个多线程共享的票源
private int ticket =100;
//设置线程任务:卖票
@Override
public void run() {
while (true){
payTicket();
}
}
//定义一个同步方法
/*同步方法也会把方法的内部的代码锁住
* 只让一个线程执行
* 同步方法的锁对象是谁
* 就是实现类对象 new RunnableImpl()
* 也就是this*/
public synchronized void payTicket(){
if (ticket>0){
//提高安全问题出现的概率
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
方法三
解决线程安全问题的第三种方案:使用lock锁
- java.util.concurrent.locks.Lock接口
- Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作
- Lock接口中的方法:
-
void lock()获取锁
-
void unlock()释放锁
java.util.concurrent.locks.ReentrantLock implement Lock 接口
使用步骤:
-
1.在成员位置创建了一个ReentrantLock对象
-
2.在可能会出现安全问题的代码前调用Lock接口中的lock()方法获取锁
-
2.在可能会出现安全问题的代码后调用Lock接口中的unlock()方法释放锁
//实现卖票案例
public class RunnableImpl implements Runnable {
//定义一个多线程共享的票源
private int ticket =100;
//创建ReentrantLock对象
Lock l = new ReentrantLock();
//设置线程任务:卖票
@Override
public void run() {
while (true){
l.lock();
if (ticket>0){
//提高安全问题出现的概率
try {
Thread.sleep(10);
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
l.unlock();//无论是否发生异常,都会将锁释放,增加程序的效率
}
}
}
}
/* while (true){
l.lock();
if (ticket>0){
//提高安全问题出现的概率
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
l.unlock();
}
}*/
}
线程的状态
线程有六种状态,其中Waiting状态需要使用Object类中的wait方法进入等待状态,notify方法唤醒等待的线程。