一 多线程访问临界资源
1.1 多线程访问临界资源时的数据安全问题
临界资源 :多个线程同时访问的资源。
产生原因:有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了
1.2 解决临界资源问题
解决方案:一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源,就得在“锁”外面等待。
1.3 锁
锁:任意对象都可以被当作锁来使用
1.4 同步代码块
同步: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--;
}
}
}
}
1.5 同步方法
同步非静态方法
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;
}
}
同步静态方法
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;
}
}
同步非静态方法的锁是this
同步静态方法的锁是类.class
用synchronized存钱取钱银行卡案例
银行卡:
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;
}
}
存钱方法:
public class SaveMoney implements Runnable {
private BankCard card;
public SaveMoney(BankCard card) {
this.card = card;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized(card){
card.setMoney(card.getMoney()+1000);
System.out.println(Thread.currentThread().getName()+"存了1000元,余额是:"+card.getMoney());
}
}
}
}
取钱方法:
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++) {
synchronized (card){
if(card.getMoney()>=1000){
card.setMoney(card.getMoney()-1000);
System.out.println(Thread.currentThread().getName()+"取了一千元,余额是:"+card.getMoney());
}else{
System.out.println("余额不足");
i--;
}
}
}
}
}
测试代码
public class Demo1 {
public static void main(String[] args) {
BankCard card = new BankCard();
new Thread(new SaveMoney(card),"张三").start();
new Thread(new SubMoney(card),"李四").start();
}
}
注意:存钱和取钱必须是同一个锁,这样才能保证先存钱再取钱。
1.6 ReentranLock 类(可重入锁)在JDK1.5之后加入的新街口
从jdk1.5之后加入新的接口 Lock,ReentrantLock是Lock接口的实现类。
通过显式定义同步锁对象来实现同步,同步锁提供了比synchronized代码块更广泛的锁定操
注意:最好将 unlock的操作放到finally块中
通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentrantLock可以显式地加锁、释放锁
四个售票员卖一百张票:
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){
break;
}
System.out.println("售票员" + Thread.currentThread().getName() + "正在卖出第" + ticket + "张票");
ticket--;
} finally {
lock.unlock();
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"窗口1").start();
new Thread(ticket,"窗口2").start();
new Thread(ticket,"窗口3").start();
new Thread(ticket,"窗口4").start();
}
}
银行卡案例,当有多个取和存的用户的时候。
public class BankCard {
private double money;
private boolean flag;//true 表示有钱可以取,false表示没钱可以存
//存钱。
public synchronized void saveMoney() {//锁是this
while (flag) {
try {
this.wait();//等待状态 释放CPU,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money = money + 1000;
System.out.println(Thread.currentThread().getName() + "存了1000,余额是" + money);
flag = true;
this.notifyAll();
}
//取钱
public synchronized void SubMoney() {
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money = money - 1000;
System.out.println(Thread.currentThread().getName() + "取了1000,余额是" + money);
flag = false;
this.notifyAll();
}
}
二 死锁
每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源。
当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁
死锁的条件:
1两个以上的线程
2至少两个锁以上
3同步中嵌套同步
男孩和女孩吃饭,各拿一直筷子
public class Lock {
public static Object locka=new Object();//第一个锁
public static Object lockb=new Object();//第二个锁
}
public class Boy extends Thread{
@Override
public void run() {
while (true) {
synchronized (Lock.locka) {
System.out.println("男孩拿着locka");
synchronized (Lock.lockb) {
System.out.println("男孩拿到lockb");
System.out.println("男孩可以吃了....");
}
}
}
}
}
public class Girl extends Thread{
@Override
public void run() {
while (true) {
synchronized (Lock.lockb) {
System.out.println("女孩拿着lockb");
synchronized (Lock.locka) {
System.out.println("女孩拿到了locka");
System.out.println("女孩可以吃了...");
}
}
}
}
}
public static void main(String[] args) {
Boy shaqiang=new Boy();
Girl xiaofeng=new Girl();
shaqiang.start();
//Thread.sleep(3000);解决这个问题可以让其中一个锁包锁休眠(不释放锁,但是休眠)
xiaofeng.start();
}