多线程
多线程访问临界资源问题
1.1数据安全问题
有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了。
解决办法:加“锁”。
1.2 同步代码块
-
同步:Synchronized:有等待
-
异步:Asynchronized:没有等待,各执行各的
语法:
synchronized(锁) {
//需要访问临界资源的代码段
}
说明:
a.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其他线程不能执行代码段中的代码,只能在锁外边等待
b.执行完代码段中的这段代码,会自动解锁。然后剩下的其他线程开始争抢cpu时间片
c.一定要保证不同的线程看到的是同一把锁,否则同步代码块没有意义
1.2.1同步代码块的使用
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.3 同步方法
1.3.1 同步非静态方法
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;
}
}
1.3.2 同步静态方法
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;
}
}
1.4 ReentrantLock类(可重入锁)
从jdk1.5之后加入新的接口 Lock,ReentrantLock是Lock接口的实现类。
通过显式定义同步锁对象来实现同步,同步锁提供了比synchronized代码块更广泛的锁定操
注意:最好将 unlock的操作放到finally块中
通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentrantLock可以显式地加锁、释放锁
案例一:模拟售票
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo01 {
public static void main(String[] args) {
Ticket res=new Ticket();
Thread t0 = new Thread(res, "小于");
Thread t1 = new Thread(res, "小洪");
Thread t2 = new Thread(res, "小慧");
Thread t3 = new Thread(res, "小敏");
t0.start();
t1.start();
t2.start();
t3.start();
}
}
//票类
public class Ticket implements Runnable{
// 需求:100张
// 临界资源
private int ticket = 100;
// 定义一个ReentrantLock类的对象
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//上锁
lock.lock();
if (count < 1) {
break;
}
System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
ticket--;
//解锁
// unlock()
lock.unlock();
//注意:lock()和unlock()都是成对出现的
}
}
}
案例二:模拟银行卡存取钱
package com.qf.day20_4;
public class BankCard {
private double money;
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
package com.qf.day20_4;
/**
* 存取类
* @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++) {
synchronized (card) {
card.setMoney(card.getMoney()+1000);//钱加上
System.out.println(Thread.currentThread().getName()+"存了1000,余额是:"+card.getMoney());
}
}
}
}
package com.qf.day20_4;
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()+"取了1000,余额是:"+card.getMoney());
}else {
System.out.println("余额不足");
i--;
}
}
}
}
}
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 benwei=new Thread(sub,"小慧");
//4启动
zhengshuai.start();
benwei.start();
}
}
死锁
概念: 每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源。
当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁。
死锁的条件:
- 至少拥有两个线程以上
- 至少两个锁
- 同步嵌套同步
案例一:一个男孩和一个女孩吃饭,但每人只有一只筷子(这里每只筷子相当一把锁,当同时拿到则可以吃饭),例子可能不恰当。
/*
*锁
*/
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();
xiaoyin.start();
xiaohui.start();
}
读写锁
ReentrantLock接口(lock接口子类):可以实现多个线程同时读取数据,写线程需要互斥执行。
特点:
- 读|写、写|写、需要互斥
- 读|读不需要互斥
实现代码:
package com.qf.day13;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteDemo {
private int number=0;
private ReadWriteLock lock=new ReentrantReadWriteLock();
public void read() {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取了:"+number);
} finally {
lock.readLock().unlock();
}
}
public void write(int number) {
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入了"+number);
this.number=number;
} finally {
lock.writeLock().unlock();
}
}
}
测试类:
package com.qf.day13;
import java.util.Random;
public class Test {
public static void main(String[] args) {
ReadWriteDemo rw=new ReadWriteDemo();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
rw.write(new Random().nextInt(100));
}
}).start();
Runnable r=new Runnable() {
@Override
public void run() {
rw.read();
}
};
for(int i=0;i<100;i++) {
new Thread(r).start();
}
}
}
通过测试可以看到,读读之间不互斥、读写、写写之间互斥。
lock锁
从jdk1.5之后加入新的接口 Lock,ReentrantLock是Lock接口的实现类。
通过显式定义同步锁对象来实现同步,同步锁提供了比synchronized代码块更广泛的锁定操
注意:最好将 unlock的操作放到finally块中
通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentrantLock可以显式地加锁、释放锁.
synchronized的缺陷:
- 获取锁的线程如果由于某个原因,不能及时释放锁(除非发生异常),其它线程只能等待。
- 使用同一个锁会进入同一个等待队列,所以需要唤醒所有线程
- 无法实现读写操作
案例:使用Condition与Lock配合完成等待通知机制,实现3个线程交替输出20遍A、B、C。
package www.qf.thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest_02 {
static int state = 1;
static int count = 1;
static Lock lock = new ReentrantLock();
static Condition conditionA = lock.newCondition();
static Condition conditionB = lock.newCondition();
static Condition conditionC = lock.newCondition();
static class printA extends Thread {
@Override
public void run() {
lock.lock();
try {
for (int i = 0; i < 20; i++) {
try {
while (state != 1)
conditionA.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("A");
state = 2;
conditionB.signal();
}
} finally {
lock.unlock();
}
}
}
static class printB extends Thread {
@Override
public void run() {
lock.lock();
try {
for (int i = 0; i < 20; i++) {
try {
while (state != 2)
conditionB.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("B");
state = 3;
conditionC.signal();
}
} finally {
lock.unlock();
}
}
}
static class printC extends Thread {
@Override
public void run() {
lock.lock();
try {
for (int i = 0; i < 20; i++) {
try {
while (state != 3)
conditionC.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("C" );
count++;
if (count > 20) {
System.exit(0);
}
state = 1;
conditionA.signal();
}
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
new printA().start();
new printB().start();
new printC().start();
}
}
从这个案例可以看出,lock锁在实现同步时比syncroinized要简单一些。