多线程访问临界资源
多线程访问临街资源时的数据安全问题
临界资源 :多个线程同时访问的资源。
产生原因:有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了
例:卖票(可能会多次卖出同一张票)
public static void main(String[] args) {
Runnable Tickets = new Runnable(){
int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets < 1) {
break;
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + tickets + "张票");
tickets--;
}
}
};
new Thread(Tickets, "美团").start();
new Thread(Tickets, "糯米").start();
new Thread(Tickets, "猫眼").start();
new Thread(Tickets, "淘票票").start();
}
解决方案
一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源,就得在“锁”外面等待
同步代码块
同步:Synchronized:有等待
异步:Asynchronized:没有等待,各执行各的
语法:
synchronized(锁) {
//需要访问临界资源的代码段
}
说明:
a.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其他线程不能执行代码段中的代码,只能在锁外边等待
b.执行完代码段中的这段代码,会自动解锁。然后剩下的其他线程开始争抢cpu时间片
c.一定要保证不同的线程看到的是同一把锁,否则同步代码块没有意义
理解:相当于用锁把牵涉到临界资源(共有资源)的代码块锁起来,线程去抢钥匙,抢到钥匙的线程进去执行被锁代码,当执行完被锁代码的时候,把钥匙交出来,线程继续抢
public class Ticket implements Runnable{
// 需求:100张
// 临界资源
private int ticket = 100;
@Override
public void run() {
while (true) {
//上锁
synchronized(this){ //此时的锁代表了当前对象
if (ticket < 1) {
break;
}
System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
ticket--;
}
}
}
}
同步方法
写一个卖票的方法,把同步代码块中的代码挪到方法中,在方法的返回值前面加上synchronized
同步非静态方法 锁对象:this
public class Ticket implements Runnable{
// 需求:100张
// 临界资源
private int ticket = 100;
@Override
public void run() {
while (true) {
if(!sale()){
break;
}
}
}
public synchronized boolean sale(){//锁是this,当前对象的引用
if (ticket < 1) {
return false;
}
System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
ticket--;
return true;
}
}
同步静态方法 锁对象:类.class
public class Ticket implements Runnable{
// 需求:100张
// 临界资源
private static int ticket = 100;
@Override
public void run() {
while (true) {
if(!sale()){
break;
}
}
}
public synchronized static boolean sale(){ //锁是 类.class
if (ticket < 1) {
return false;
}
System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
ticket--;
return true;
}
}
ReentrantLock类(可重入锁)jdk1.5
从jdk1.5之后加入新的接口 Lock,ReentrantLock是Lock接口的实现类。
通过显式定义同步锁对象来实现同步,同步锁提供了比synchronized代码块更广泛的锁定操
注意:最好将 unlock的操作放到finally块中
通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentrantLock可以显式地加锁、释放锁
格式:
lock.lock(); //上锁
try{
需要上锁的代码块
} finally{
lock.oncock(); //解锁
}
public class Ticket implements Runnable {
private static int ticket = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (ticket < 1) {
System.out.println(Thread.currentThread().getName() + "售罄!");
break;
}
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "张票");
ticket--;
} finally {
lock.unlock();
}
}
}
}
死锁
每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源。
当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁。
死锁的条件:
1.两个以上的线程
2.至少两个锁以上
3.同步中嵌套同步
public class Lockab {
public static Object lcoka = new Object();
public static Object lcokb = new Object();
}
public class Boy implements Runnable {
@Override
public void run() {
synchronized (Lockab.lcokb) {
System.out.println("男孩拿到了B筷子");
synchronized (Lockab.lcoka) {
System.out.println("男孩拿到了A筷子,开始吃饭");
}
}
}
}
public class Girl implements Runnable {
@Override
public void run() {
synchronized (Lockab.lcoka) {
System.out.println("女孩拿到了A筷子");
synchronized (Lockab.lcokb) {
System.out.println("女孩拿到了B筷子,开始吃饭");
}
}
}
}
public class Demo {
public static void main(String[] args) {
Boy boy = new Boy();
Girl girl = new Girl();
new Thread(boy).start();
new Thread(girl).start();
}
}
线程的通信【生产者与消费者设计模式】
需求:你和你女朋友公用一张银行卡,你向卡中存钱,你女朋友取钱,保证你存一笔,然后取一笔,再存一笔,再取一笔。
实现功能:使用线程通信。
在jdk1.5之前有三个方法实现线程通信:
wait(): 等待,线程执行这个方法进入等待队列(和锁有关,一个锁对应一个等待队列), 需要被唤醒
notify(): 通知唤醒,从等待队列中随机唤醒一个线程
notifyAll():全部唤醒,把等待队列中所有的线程都唤醒
public class BankCard {
private double money;
private boolean flag;// 标记 true 表示有钱, false没钱
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
/***
* 存取
*/
public synchronized void save() { //this
while(flag) {//true 有钱
try {
this.wait();//等待,释放了cpu,释放了锁 ,调用wait的对象是锁
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//等待
}
money=money+1000;
System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+money);
flag=true;//修改标记
this.notifyAll();//唤醒取钱线程取取钱
}
/**
* 取
*/
public synchronized void qu() {//this
while(flag==false) {
try {
this.wait();//等待 释放cpu和锁
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
money=money-1000;
System.out.println(Thread.currentThread().getName()+"取了1000,余额是:"+money);
//修改标记
flag=false;
//唤醒
this.notifyAll();
}
}
package com.qf.day20_8;
/**
* 存取类
* @author wgy
*
*/
public class AddMoney implements Runnable{
private BankCard card;
public AddMoney(BankCard card) {
this.card = card;
}
@Override
public void run() {
for(int i=0;i<10;i++) {
card.save();
}
}
}
package com.qf.day20_8;
public class SubMoney implements Runnable{
private BankCard card;
public SubMoney(BankCard card) {
this.card = card;
}
@Override
public void run() {
for(int i=0;i<10;i++) {
card.qu();
}
}
}
package com.qf.day20_8;
public class Test {
public static void main(String[] args) {
//1创建卡
BankCard card=new BankCard();
//2创建存钱和取钱功能
AddMoney add=new AddMoney(card);
SubMoney sub=new SubMoney(card);
//3创建线程对象
Thread zhengshuai=new Thread(add,"帅帅");
Thread dalang=new Thread(add, "大郎");
Thread benwei=new Thread(sub,"本伟");
Thread xiaolian=new Thread(sub,"金莲");
//4启动
zhengshuai.start();
benwei.start();
dalang.start();
xiaolian.start();
}
}
生产者与消费者模式
它描述的是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者可以从仓库中取走产品,解决生产者/消费者问题,我们需要采用某种机制保护生产者和消费者之间的同步
同步问题核心在于:如何保证同一资源被多个线程并发访问时的完整性,常用的方法就是加锁,保证资源在任意时刻只被一个线程访问
wait():当缓冲区满或者空时,生产者 / 消费者线程停止自己的执行,放弃锁,是自己处于等待状态,让其他线程执行
1. 是Object方法
2. 使用方式:对象.wait();
3. 表示释放 对象 这个锁,然后在锁外面等待(对比sleep(),sleep是抱着锁休眠的)
4. 等待,必须放在同步代码段中执行
notify():当生产者 / 消费者向缓冲区放入 / 取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态
1. 是Object的方法
2.调用方式 :对象.notify();
3.表示唤醒 对象 所标记外面等待的一个线程
notifyAll():全部唤醒
1.是Object的方法
2.调用方式:对象.notifyAll()
3.表示唤醒 对象 所标记外边等待的所有线程
代码实现:
优化!
使用了lock锁,使用了condition分别生成了生产者和消费者两个队列。唤醒的时候去所在的队列去唤醒,提高效率
面包类
public class Bread {
private int id;
private String producerName;
public Bread() {
}
public Bread(int id, String producerName) {
this.id = id;
this.producerName = producerName;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getProducerName() {
return producerName;
}
public void setProducerName(String producerName) {
this.producerName = producerName;
}
@Override
public String toString() {
return "Bread{" +
"id=" + id +
", producerName='" + producerName + '\'' +
'}';
}
}
面包工厂类
//面包工厂
public class BreadContainer {
private Bread[] breads = new Bread[6];
private int index = -1;
//创建锁
private Lock lock = new ReentrantLock();
//创建两个条件队列
Condition pro_condition = lock.newCondition();
Condition con_condition = lock.newCondition();
//做面包
public void input(Bread b) {
lock.lock();
try {
//表示有面包
while (index >= 5) {
try {
pro_condition.await(); //进存的队列
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//放入面包
index++;
breads[index] = b;
System.out.println(Thread.currentThread().getName() + "生产了" + b.getId() + "号面包");
con_condition.signal(); //唤醒取的队列中的线程
} finally {
lock.unlock();
}
}
public void output() {
lock.lock();
try {
while (index < 0) {
try {
con_condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//取出面包
Bread b = breads[index];
breads[index] = null;
System.out.println(Thread.currentThread().getName() + "取出了" + b.getId() + "号面包");
index--;
pro_condition.signal();
} finally {
lock.unlock();
}
}
}
生产者类
//生产者
public class Produce implements Runnable {
private BreadContainer breadContainer = new BreadContainer();
public Produce(BreadContainer breadContainer) {
this.breadContainer = breadContainer;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
breadContainer.input(new Bread(i, Thread.currentThread().getName()));
}
}
}
消费者类
public class Consumer implements Runnable {
BreadContainer breadContainer = new BreadContainer();
public Consumer(BreadContainer breadContainer) {
this.breadContainer = breadContainer;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
breadContainer.output();
}
}
}
测试类
public class Demo {
public static void main(String[] args) {
BreadContainer breadContainer = new BreadContainer();
//只是有生产消费的功能,还不是线程
Produce p = new Produce(breadContainer);
Consumer c = new Consumer(breadContainer);
new Thread(p, "张三").start();
new Thread(c, "张三的女朋友").start();
}
}