一、什么是线程安全
当多个线程访问共享资源时,每个线程都会各自对共享资源进程操作,导致数据不一致,造成程序不能正确的得到结果,此时需要让多个线程排队访问共享资源,让线程安全,才能保证数据安全的被访问。
二、实现线程安全
让线程同步(线程排队访问共享资源),保证线程安全,其中的细节都在注释里。
(1)使用synchronized关键字,把共享资源上锁,让每个线程访问之前排队。
1.同步方法
案例为:模拟买票
package com.synchronizeddd;
public class Ticket01 implements Runnable {
// 总票数
private int count = 100;
// 购买票数
private int nums = 0;
// 重写run()方法
@Override
public void run() {
while (true) {
// 为了只让黄牛抢一张票 抢到返回true
// 执行return结束run()方法 也就结束了黄牛线程
if (rob())
return;
}
}
// 同步方法
private synchronized boolean rob() {
if (count > 0) {
count--;
nums++;
if (Thread.currentThread().getName().equals("黄牛党")) {
System.out.println(Thread.currentThread().getName() + "抢了第" + nums + "张票,还剩" + count + "张。");
// 为了结束黄牛线程
return true;
}
System.out.println(Thread.currentThread().getName() + "抢了第" + nums + "张票,还剩" + count + "张。");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return false;
}
}
测试类:
package com.synchronizeddd;
public class Test01 {
public static void main(String[] args) {
// 创建线程任务
Ticket01 ticket = new Ticket01();
// 创建线程
Thread threadHN = new Thread(ticket, "黄牛党");
Thread thread1 = new Thread(ticket, "小白");
Thread thread2 = new Thread(ticket, "Ikun");
// 线程就绪
threadHN.start();
thread1.start();
thread2.start();
}
}
运行结果:
2.同步代码块
案例与上相同
package com.synchronizeddd;
public class Ticket implements Runnable {
// 总票数
private int count = 100;
// 购买票数
private int nums = 0;
// 重写run()方法
@Override
public void run() {
while (true) {
/* 保证线程安全 使用synchronized 实现同步代码块
synchronized(obj){} obj为一个锁对象 锁对象要唯一
保证每个线程都是用同一个锁 若多个锁不能保证同步
this在对象中指向自己 在此处指向线程任务对象
在测试类创建线程时 每个线程的线程任务对象都是同一个 故满足锁对象唯一 可参考测试类
*/
synchronized (this) {
if (count > 0) {
count--;
nums++;
if (Thread.currentThread().getName().equals("黄牛党")){
System.out.println(Thread.currentThread().getName() + "抢了第" + nums + "张票,还剩" + count + "张。");
// 结束run()方法 即黄牛的线程
return;
}
System.out.println(Thread.currentThread().getName() + "抢了第" + nums + "张票,还剩" + count + "张。");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
测试类:
package com.synchronizeddd;
public class Test {
public static void main(String[] args) {
// 创建线程任务 下面创建的线程都是使用此线程任务 保证锁对象唯一
Ticket ticket = new Ticket();
// 创建线程
Thread threadHN = new Thread(ticket, "黄牛党");
Thread thread1 = new Thread(ticket, "小白");
Thread thread2 = new Thread(ticket, "Ikun");
// 线程就绪
threadHN.start();
thread1.start();
thread2.start();
}
}
运行结果:
(2)使用Lock锁
案例同上
package com.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyLock01 implements Runnable {
// 总票数
private int count = 100;
// 购买票数
private int nums = 0;
//使用静态引用 确保锁对象唯一
static Lock lock = new ReentrantLock();
// 重写run()方法
@Override
public void run() {
while (true) {
// 调用lock()方法 进行上锁
// 让若把锁放在循环外 则开不了锁 因为死循环没结束
lock.lock();
if (count > 0) {
// 修改票数
count--;
nums++;
// 黄牛党只能给一张票 不能让他们太嚣张
if (Thread.currentThread().getName().equals("黄牛党")) {
System.out.println(Thread.currentThread().getName() + "抢了第" + nums + "张票,还剩" + count + "张。");
// 结束run()方法 即黄牛的线程
// 但注意 return结束方法后 并没有开锁 会造成死锁
// 结束之前需要开锁
lock.unlock();
return;
}
// 普通人
System.out.println(Thread.currentThread().getName() + "抢了第" + nums + "张票,还剩" + count + "张。");
//模拟网络延迟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// 开锁 当一个线程访问结束后 一个开锁让其他线程进来访问
lock.unlock();
}
}
}
测试类:
package com.lock;
import com.synchronizeddd.Ticket;
public class Test01 {
public static void main(String[] args) {
// 创建线程任务
MyLock01 ticket = new MyLock01();
// 创建线程
Thread threadHN = new Thread(ticket, "黄牛党");
Thread thread1 = new Thread(ticket, "小白");
Thread thread2 = new Thread(ticket, "Ikun");
// 线程就绪
threadHN.start();
thread1.start();
thread2.start();
}
}
运行结果: