Java线程安全指的是什么?如何实现
线程安全问题
1、引入案例——多窗口买票
1、提及概念:原子操作
原子操作就是不可分割的操作,例如售票的过程中的代码就是一个不可分割的操作.
2、引入案例
案例需求:
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
方式一:
方式1: 不符合现实生活中的情况
窗口1: 1 ---- 33
窗口2: 34 ---- 66
窗口3: 67 ---- 100
方式二:
三个窗口 同时操作 100张票 哪个窗口 轮到了就卖
代码实现:
// 方式2:三个窗口 同时操作 100张票 哪个窗口 轮到了就卖
public class Demo1 {
public static void main(String[] args) {
//创建 3个窗口
Thread t1 = new Thread("窗口1"){
@Override
public void run() {
for (int i = 1; i <= 33; i++) {
System.out.println(this.getName()+"卖的第"+i+"号票");
}
}
};
Thread t2 = new Thread("窗口2"){
@Override
public void run() {
for (int i = 34; i <= 66; i++) {
System.out.println(this.getName()+"卖的第"+i+"号票");
}
}
};
Thread t3 = new Thread("窗口3"){
@Override
public void run() {
for (int i = 67; i <=100; i++) {
System.out.println(this.getName()+"卖的第"+i+"号票");
}
}
};
t1.start();
t2.start();
t3.start();
}
}
案例中出现的问题
卖票出现了问题
1. 相同的票出现了多次
2. 出现了负数的票
问题产生原因: 线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题。
2、线程安全概念
百度百科:
线程安全是多线程编程时的计算机程序代码中的一个概念。
在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
维基百科:
线程安全是程式设计中的术语,指某个函数、函数库在多执行绪环境中被调用时,能够正确地处理多个执行绪之间的公用变数,使程序功能正确完成。
假设有间银行只有 1000 元,而两个人同时提领 1000 元时就可能会拿到总计 2000 元的金额。
为了避免这个问题,该间银行提款时应该使用互斥锁,即意味着针对同一个资源处理时,前一个人提领交易完成后才处理下一笔交易。
但这种手法会使得效能降低。
一般来说,线程安全的函数应该为每个调用它的线程分配专门的空间,来储存需要单独保存的状态(如果需要的话),
不依赖于“线程惯性”,把多个线程共享的变量正确对待(如,通知编译器该变数为“易失(volatile)”型,阻止其进行一些不恰当的优化),
而且,线程安全的函数一般不应该修改全局对象。
3、解决方案
1、解决方案之同步代码块
1、格式
synchronized(任意对象) {
原子操作的代码;
}
2、说明
1. 任意对象可以是任何类型的对象
2. 任意对象必须是多个线程共享的对象
3、优点和缺点
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
4、案例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo2 {
public static void main(String[] args) {
//创建任务类对象
Tickets tickets = new Tickets();
//创建三个线程 模拟三个窗口
Thread t1 = new Thread(tickets,"窗口1");
Thread t2 = new Thread(tickets,"窗口2");
Thread t3 = new Thread(tickets,"窗口3");
//同时开始卖票
t1.start();
t2.start();
t3.start();
}
}
// 票类 是一个任务类 对象交给三个线程同时操作
class Tickets implements Runnable{
private int tickets = 100;//有100张票
Object lock = new Object();
@Override
public void run() {
//不断的卖票 直到票买完为止
while (true){
//锁
synchronized (lock){
if (tickets<=0){
System.out.println("票已经售空了");
break;//结束系统
}else{
//卖票过程
System.out.println("请您出示身份证.....正在查询是否有余票....");
System.out.println("恭喜您在"+Thread.currentThread().getName()+"买到了第"+tickets+"号票,正在出票请稍等....");
try {
Thread.sleep(100);//模拟出票的过程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"出票成功 ....请收好您的票和证件");
tickets--;
}
}
}
}
}
2、解决方案之同步方法
1、格式
(1)同步成员方法:
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
(2)同步静态方法:
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
2、两种同步方法的锁对象
1. 同步成员方法的锁对象是 this
2. 同步静态方法的锁对象是 类名.class ---->类对象 字节码对象
3、案例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo3 {
public static void main(String[] args) {
//创建任务类对象
Tickets tickets = new Tickets();
//创建三个线程 模拟三个窗口
Thread t1 = new Thread(tickets, "窗口1");
Thread t2 = new Thread(tickets, "窗口2");
Thread t3 = new Thread(tickets, "窗口3");
//同时开始卖票
t1.start();
t2.start();
t3.start();
}
}
// 票类 是一个任务类 对象交给三个线程同时操作
class Tickets implements Runnable {
private static int tickets = 100;//有100张票
@Override
public void run() {
//不断的卖票 直到票买完为止
while (true) {
sellTickets();
}
}
//同步成员方法 直观 简单
private static synchronized void sellTickets() {
if (tickets <= 0) {
System.out.println("票已经售空了");
System.exit(0);
} else {
//卖票过程
System.out.println("请您出示身份证.....正在查询是否有余票....");
System.out.println("恭喜您在" + Thread.currentThread().getName() + "买到了第" + tickets + "号票,正在出票请稍等....");
try {
Thread.sleep(100);//模拟出票的过程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出票成功 ....请收好您的票和证件");
tickets--;
}
}
}
3、解决方案之 Lock锁
1、概述
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。
2、使用方法
(1)ReentrantLock构造方法
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
(2)加锁解锁方法
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
案例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo4 {
public static void main(String[] args) {
//创建任务类对象
Tickets tickets = new Tickets();
//创建三个线程 模拟三个窗口
Thread t1 = new Thread(tickets, "窗口1");
Thread t2 = new Thread(tickets, "窗口2");
Thread t3 = new Thread(tickets, "窗口3");
//同时开始卖票
t1.start();
t2.start();
t3.start();
}
}
// 票类 是一个任务类 对象交给三个线程同时操作
class Tickets implements Runnable {
private static int tickets = 100;//有100张票
//创建锁对象
Lock lock = new ReentrantLock();//接口引用指向实现类对象
@Override
public void run() {
//不断的卖票 直到票买完为止
while (true) {
lock.lock();//进来的时候 锁上了
sellTickets();
lock.unlock();// 出来的时候 解锁
}
}
//同步成员方法 直观 简单
private void sellTickets() {
if (tickets <= 0) {
System.out.println("票已经售空了");
System.exit(0);
} else {
//卖票过程
System.out.println("请您出示身份证.....正在查询是否有余票....");
System.out.println("恭喜您在" + Thread.currentThread().getName() + "买到了第" + tickets + "号票,正在出票请稍等....");
try {
Thread.sleep(100);//模拟出票的过程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出票成功 ....请收好您的票和证件");
tickets--;
}
}
}