线程不安全案例
package ksdxc.d19;
// 线程不安全案例:线程不安全,有负数
public class d19 {
public static void main(String[] args) {
BuyTicket customer = new BuyTicket();
new Thread(customer,"a").start();
new Thread(customer,"b").start();
new Thread(customer,"c").start();
}
}
class BuyTicket implements Runnable{
private int ticketNum = 10;
private boolean flag = true;
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void buy() throws InterruptedException{
if (ticketNum <= 0){
flag = false;
return;
}
// 延时
Thread.sleep(120);
System.out.println(Thread.currentThread().getName());
ticketNum--;
}
}
package ksdxc.d19;
public class bank {
public static void main(String[] args) {
Account account = new Account(66,"pool");
Withdrawal withdrawal1 = new Withdrawal(account,55,"coding");
Withdrawal withdrawal2 = new Withdrawal(account,55,"walking");
withdrawal1.start();
withdrawal2.start();
}
}
/*
账户类
*/
class Account{
int remainMoney; // 余额
String cardName; // 银行卡类别
public Account(int remainMoney, String cardName) {
this.remainMoney = remainMoney;
this.cardName = cardName;
}
}
/*
取款类
*/
class Withdrawal extends Thread{
Account account;
int drawMone; // 取了多少钱
String threadName; // 现金
/*
构造方法
*/
public Withdrawal(Account account,int drawMone,String threadName){
super(threadName);
this.account = account;
this.drawMone = drawMone;
}
/*
取钱
*/
@Override
public void run() {
// 判断有没有前
if (account.remainMoney - drawMone < 0){
System.out.println(Thread.currentThread().getName() + "operator fail");
return;
}
try {
Thread.sleep(120);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 程序能够执行到这里说明取款成功
account.remainMoney = account.remainMoney - drawMone;
System.out.println(Thread.currentThread().getName());
System.out.println(account.remainMoney);
/*
输出结果出现了负数,说明线程不安全
*/
}
}
生产者消费者模式
package du;
import java.util.ArrayList;
import java.util.List;
public class d802 {
public static void main(String[] args) {
/*
wait()方法
notify()方法
这两种方法是Object类中自带的
不是通过线程对象调用的
wait()方法作用
Object object = new Object();
object.wait();
表示让正在object对象上活动的线程进入等待状态
并释放之前占用object对象的锁
无期限等待,直到被唤醒为止
notify()方法作用
object.notify();
可以让object上等待的线程进入活动状态
两者建立在线程同步的基础之上
因为多线程要操作一个仓库
存在线程安全问题
案例:
使用wait和notify方法实现生产者和消费者模式
这是一种特殊的业务需求
需要做到这种效果
生产一个,消费一个
*/
List list = new ArrayList<>();
Thread thread1 = new Thread(new Productor(list));
Thread thread2 = new Thread(new Consumer(list));
thread1.setName("生产者线程");
thread2.setName("消费者线程");
thread1.start();
thread2.start();
}
}
/*
生产线程
*/
class Productor implements Runnable{
/*
仓库
*/
private List list;
public Productor(List list){
this.list = list;
}
@Override
public void run() {
while (true){
/*
两个线程都操作list集合
所以给list加锁
*/
synchronized (list){
if (list.size() > 0){
/*
生产者生产完毕
线程进入等待状态
等待消费者消费
释放list集合的锁
*/
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
程序能够运行到这里说明.
仓库空了可以生产了
*/
Object o = new Object();
list.add(o);
System.out.println(Thread.currentThread().getName() + " " + o);
/*
唤醒消费者消费
进入消费者run方法
两个线程会被同时唤醒
之后两个线程开始抢占时间片
这个时候谁先执行同步代码块都有可能
但由于if语句限制
必然有一个线程会再次陷入等待然后释放锁
*/
list.notifyAll();
}
}
}
}
/*
消费线程
*/
class Consumer implements Runnable{
/*
仓库
*/
private List list;
public Consumer(List list){
this.list = list;
}
@Override
public void run() {
/*
按理说
进入while循环的第1次不会走if里面
第2次while循环会进入if条件语句当中
然后线程就会进入等待状态
*/
while (true){
synchronized (list){
if (list.size() == 0){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
程序能够执行到此处说明
仓库里面有数据了
可以开始消费
*/
Object remove = list.remove(0);
System.out.println(Thread.currentThread().getName() + " " + remove);
/*
唤醒生产者生产
进入生产者run方法
两个线程会被同时唤醒
之后两个线程会开始抢占时间片
但由于if语句限制
必然有一个线程会再次陷入等待然后释放锁
*/
list.notifyAll();
}
}
}
}
同步锁的使用
/*
Java三大变量
实例变量:存储堆中
静态变量:存储方法区
局部变量:存储栈中
以上3大变量中,局部变量永远不会出现线程安全问题
因为局部变量存储在栈中,永远不会共享
一个线程一个栈
堆和方法区是多线程共享的
所以可能出现线程安全问题
常量同样没有线程安全问题
因为常量不可修改
出现线程安全问题的3个条件
1. 多线程并发
2. 有共享数据
3. 共享数据有修改行为
synchronized同步代码块3种使用方式:
1. 包裹线程不安全的代码
2. 包裹不安全的方法,效率比第1种方式低
3. 直接修饰实例方法,锁的一定是this,这总方式不灵活
而且该方式表示整个方法都需要同步,可能导致无故扩大同步范围
程序运行效率降低,所以不常用
4. Vector类由于大量使用第3种方式,所以不常用
*/
/*
synchronized的3种写法
1. 同步代码块
灵活
synchronized(线程共享对象){
同步代码块
}
2. 实例方法上使用
共享对象一定是this
同步代码块是整个方法体
用的是对象锁
对象锁每个对象都有一把锁
3. 在静态方法上使用
表示类锁
类锁永远只有1把
此时无论你new了多少个对象
静态方法上加synchronized关键字
都需要等
保证静态变量的安全
/*
开发中如何解决死锁问题:
死锁问题很难调试
因为程序员不知道自己在哪里错了
所以synchronized在开发中不要嵌套使用
synchronized会降低用户的吞吐量,也就是并发量
电商平台的秒杀功能尤其注重高并发 也就是异步
用户不可能排队去抢商品吧
下面是方案优先级:
1. 尽量使用局部变量来代替实例变量和静态变量(首选)
2. 如果必须是实例变量,可以创建多个对象
一个线程对应一个对象
线程就不共享了
3. 如果不能使用局部变量,对象也不能创建多个
那就只能使用synchronized了
*/
数据定时备份
package du;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class d800 {
public static void main(String[] args) throws Exception {
/*
定时器概述
间隔特定的时间,执行特定的程序
例如:
每周需要对银行进行总账操作
每天需要对数据进行备份操作
可以采用多种方法实现:
1. sleep方法,最原始的方法,比较low
2. java类库中,已经写好了一个定时器 java.util.Timer
SpringTask框架的底层实现原理
*/
/*
创建定时器对象
*/
Timer timer = new Timer();
/*
param1 定时任务
param2 第1次执行时间
param3 间隔多久执行1次
*/
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/*
第一次执行时间
*/
Date firstExecTime = simpleDateFormat.parse("2021-09-27 20:05:00");
timer.schedule(new LogTimerTask(),firstExecTime,1000*5);
}
}
/*
由于TimerTask是抽象类
所以不能new对象
只能自己编写定时任务类
假设是一个记录日志的定时任务
这里可以采用匿名内部类的方式
*/
class LogTimerTask extends TimerTask {
@Override
public void run() {
/*
这里编写需要执行的任务
获取当前时间
进行数据备份
*/
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String string = simpleDateFormat.format(new Date());
System.out.println(string + "finish data archived");
}
}
合理终止线程
public class d774 {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
Thread thread = new Thread(myThread1);
thread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
终止线程
*/
myThread1.flag = false;
}
}
class MyThread1 implements Runnable{
boolean flag = true;
@Override
public void run() {
for (int i = 0; i < 11; i++) {
if (flag){
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
return;
}
}
}
}