不安全的买票
sleep会放大问题的发生性噢!
package syn;
/**
* 不安全的买票
*/
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"你").start();
new Thread(buyTicket,"我").start();
new Thread(buyTicket,"他").start();
}
}
class BuyTicket implements Runnable{
//票
private int ticketNums= 10;
boolean flag=true;//外部停止方式
@Override
public void run() {
//买票
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
//判断是否有票
if(ticketNums<=0){
flag=false;
return;
}
Thread.sleep(1000);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
运行代码我们会发现,7这张票被拿到了两次,这很明显的是不被允许的吧
所以为了解决这个问题,我们需要用到线程锁这个概念,即使用synchronized关键词
在并发编程中存在线程安全问题,主要原因有:
1.存在共享数据
2.多线程共同操作共享数据。关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性
synchronized的三种应用方式
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
那么我们就可以开始解决上面的问题啦:
只需要在买票方法buy前面加上锁就可以解决啦
不安全的取钱
package syn;
/**
* 不安全的取钱
* 两个人去银行取钱,账户
*/
public class UnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(100, "结婚基金");
Drawing you = new Drawing(account, 50, "你");
Drawing girl = new Drawing(account, 100, "girl");
you.start();
girl.start();
}
}
//账户
class Account {
int money;//余额
public Account(int money, String name) {
this.money = money;
this.name = name;
}
String name;//卡命
}
//银行,模拟取款
class Drawing extends Thread {
Account account;
//取了多少钱
int drawingMoney;
//现在手里有多少钱
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
super(name);
// this.setName(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
synchronized (account) {
//判断现在有没有钱
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "钱不够");
return;
}
//sleep可以方法问题的发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额
account.money = account.money - drawingMoney;
//你手里的前
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name + "余额为" + account.money);
System.out.println(this.getName() + "手里的前" + nowMoney);
}
}
}
这里采用的加锁就是代码块的加锁方式
不安全的集合
这里我使用了lambda表达式,前面的文章我有专门细说的使用方法的噢!
package syn;
import java.util.ArrayList;
/**
* 线程不安全的集合
*/
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
}
代码看似没毛病对吧,但是如果去掉锁运行就会发现结果有问题
这里如果按照理想来说,应该输出是10000的
因为CPU的执行是很快的,在某一时刻内,可能就有两条线程同时操作了同一个list,所以才会造成这种结果
进程死锁问题
package syn;
/**
* 进程死锁
*/
public class DeadLock {
public static void main(String[] args) {
Makeup makeup1=new Makeup(0,"白雪公主");
Makeup makeup2=new Makeup(1,"灰姑凉");
makeup1.start();
makeup2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
class Makeup extends Thread {
//需要的资源只有一份,用static来保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choose;//选择 这里千万不能加上static
//因为如果他是一个静态变量的话,进程是不会发生死锁的
String girlname;//选择化妆品的人
public Makeup(int choose, String girlname) {
this.choose = choose;
this.girlname = girlname;
}
@Override
public void run() {
try {
makeup(choose);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void makeup(int choose) throws InterruptedException {
if (choose == 0) {
synchronized (lipstick) {
System.out.println(girlname + "在用口红");
Thread.sleep(1000);
synchronized (mirror) {
System.out.println(girlname + "在用镜子");
}
}
} else {
synchronized (mirror) {
System.out.println(girlname + "在用镜子");
Thread.sleep(1000);
synchronized (lipstick) {
System.out.println(girlname + "在用口红");
}
}
}
}
}
运行后发现进程不会停止,这是因为我们的两个线程都在等待对方线程执行完毕,但是每一条线程里面都被我们给上了锁(嵌套锁)
所以解决方案当然就是不要拆解掉嵌套锁啦
嵌套锁的方式是非常不可取的,使用时一定要摸清情况,避免发生死锁