文章目录 1.线程安全问题 1.1多线程安全问题: 2.线程安全 2.1同步机制:同步方式1 2.1.1同步代码块: 2.1.2线程阻塞状态 2.2同步机
文章目录
- 1.线程安全问题
- 1.1多线程安全问题:
- 2.线程安全
- 2.1同步机制:同步方式1
- 2.1.1同步代码块:
- 2.1.2线程阻塞状态
- 2.2同步机制:同步方式2
- 2.2.1同步方法
- 2.3同步规则
- 3.经典问题
- 3.1死锁
- 4.线程通信
- 4.1常用方法
- 4.2还是存取钱的例子(存一下,取一下):
- 4.3多存多取问题与全部等待问题
- 5.生产者消费者问题
- 6.小结
1.线程安全问题
- 需求: A线程将“Hello"存入数组; B线程将“World"存入数组。
- 出现的问题:A线程查找下标0,此时这个位置是空的,但是还没有存入数据,时间片到期,然后B线程开始执行,查找到下标0,发现里面没有数据,就把World存入到下标为0的位置,之后轮到A线程执行时,因为前面A执行时,发现下标0这个位置还没有数据,就把Hello存入,导致World被覆盖
import java.util.Arrays;
/**
*多线程安全问题:
* 两个线程同时向一个数组存入数据
*/
public class ThreadSafe {
private static int index = 0;
public static void main(String[] args) throws Exception{
//创建数组,共享资源
String[] s = new String[5];
//创建两个操作
Runnable Arunnabe = new Runnable() {
@Override
public void run() {
s[index] = "Hello";
index++;
}
};
Runnable Brunnabe = new Runnable() {
@Override
public void run() {
s[index] = "World";
index++;
}
};
//创建线程
Thread a = new Thread(Arunnabe,"A");
Thread b = new Thread(Brunnabe,"B");
//启动线程
a.start();
b.start();
//加入线程,确保最后a,b线程执行完
a.join();
b.join();
System.out.println(Arrays.toString(s));
}
}
/*
多运行几次,你会发现结果并不都是我们设想的 -》[Hello, World, null, null, null]
,还会有-》[Hello, null, null, null, null] ,[World, null, null, null, null]
等.........
*/
1.1多线程安全问题:
- 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一 致。
- 临界资源:共享资源(同一对象),一次仅允许-一个线程使用,才可保证其正确性。
- 原子操作:不可分割的多步操作,被视作- -个整体,其顺序和步骤不可打乱或缺省。
2.线程安全
- 思考:在程序应用中,如何保证线程安全?
同步机制:一个线程A在访问共享资源时,别的线程等待,只有A线程执行完毕,别的线程才能执行
2.1同步机制:同步方式1
2.1.1同步代码块:
synchronized(临界资源对象/共享资源对象){ //对临界资源对象加锁//代码(原子操作)
}
注意:
上面数组问题的优化,同步机制:
package com.wlw.thread;import java.util.Arrays;
/**
*多线程安全问题:
* 两个线程同时向一个数组存入数据
*/
public class ThreadSafe {
private static int index = 0;
public static void main(String[] args) throws Exception{
//创建数组,共享资源
String[] s = new String[5];
//创建两个操作
Runnable Arunnabe = new Runnable() {
@Override
public void run() {
//同步代码块
synchronized (s){
s[index] = "Hello";
index++;
}
}
};
Runnable Brunnabe = new Runnable() {
@Override
public void run() {
//同步代码块
synchronized (s) {
s[index] = "World";
index++;
}
}
};
//创建线程
Thread a = new Thread(Arunnabe,"A");
Thread b = new Thread(Brunnabe,"B");
//启动线程
a.start();
b.start();
//加入线程,确保最后a,b线程执行完
a.join();
b.join();
System.out.println(Arrays.toString(s));
}
}
/*
执行结果只有这两种:
[Hello, World, null, null, null]
[World, Hello, null, null, null]
*/
优化之前的四个窗口共卖100张票的问题:
package com.wlw.thread.caseDemo;/**
* 票类(共享资源)
*/
public class Ticket implements Runnable{
int ticket = 100; //票总共100张
//创建锁,引用对象都行
//Object obj = new Object();
//售票功能
@Override
public void run() {
while (true){
synchronized (this){ //this 代表当前对象 Ticket
if(ticket <= 0){
break;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
}
}
}
}package com.wlw.thread.caseDemo;
public class TestTicket {
public static void main(String[] args) {
// 创建实现类对象
Ticket ticket = new Ticket();
//创建线程对象
Thread thread1 = new Thread(ticket,"窗口1");
Thread thread2 = new Thread(ticket,"窗口2");
Thread thread3 = new Thread(ticket,"窗口3");
Thread thread4 = new Thread(ticket,"窗口4");
//调用start()方法,启动线程
thread1.start();
thread2.start();
thread3.start();
thread4.start();
/*
这时就不会出现多个窗口卖了同一张票,这是一个线程安全问题,已用同步机制解决
*/
}
}
优化之前存钱取钱的问题:
package com.wlw.thread.caseDemo;/**
* 银行卡类
*/
public class BankCard {
private Double money;
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
}package com.wlw.thread.caseDemo;
public class TestBankCard2 {
public static void main(String[] args) {
//1.创建银行卡
BankCard bankCard = new BankCard();
bankCard.setMoney(0.0);
//2.创建两个操作,存取功能
Runnable add = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//同步机制
synchronized (bankCard){
bankCard.setMoney(bankCard.getMoney() + 1000);
System.out.println(Thread.currentThread().getName()+ "存了1000块钱,余额是:" +bankCard.getMoney());
}
}
}
};
Runnable sub = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//同步机制
synchronized (bankCard) {
if (bankCard.getMoney() >= 1000) {
bankCard.setMoney(bankCard.getMoney() - 1000);
System.out.println(Thread.currentThread().getName() + "取了1000块钱,余额是:" + bankCard.getMoney());
} else {
System.out.println("余额不足,请充值");
i--;
}
}
}
}
};
//3.创建线程对象,并启动
new Thread(add,"小李").start();
new Thread(sub,"小月").start();
}
}
2.1.2线程阻塞状态
2.2同步机制:同步方式2
2.2.1同步方法
synchronized 返回值类型 方法名称(形参列表o){//对当前对象(this)加锁//代码(原子操作)
}
注意:
例子:四个窗口共卖100张票
package com.wlw.thread.caseDemo;/**
* 票类(共享资源)
*/
public class Ticket2 implements Runnable{
int ticket = 100; //票总共100张
//售票功能
@Override
public void run() {
while (true){
if(!sale()){
break;
}
}
}
public synchronized boolean sale(){ //此时锁为this,如果该方法变为static的,锁为Ticket2.Class
if(ticket <= 0){
return false;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
return true;
}
}package com.wlw.thread.caseDemo;
public class TestTicket2 {
public static void main(String[] args) {
// 创建实现类对象
Ticket2 ticket = new Ticket2();
//创建线程对象
Thread thread1 = new Thread(ticket,"窗口1");
Thread thread2 = new Thread(ticket,"窗口2");
Thread thread3 = new Thread(ticket,"窗口3");
Thread thread4 = new Thread(ticket,"窗口4");
//调用start()方法,启动线程
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}
2.3同步规则
- 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
- 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。
- StringBuffer
- Vector
- Hashtable
- 以上类中的公开方法,均为synchonized修饰的同步方法。
3.经典问题
3.1死锁
实现死锁的代码(理解死锁就好):
package com.wlw.thread.casedemo02;public class MyLock {
//定义锁,两只筷子
public static Object a = new Object();
public static Object b = new Object();
}package com.wlw.thread.casedemo02;
public class Boy extends Thread{
@Override
public void run() {
synchronized (MyLock.a){
System.out.println("男孩拿到了a");
synchronized (MyLock.b){
System.out.println("男孩拿到了b");
System.out.println("男孩可以吃饭了...........");
}
}
}
}package com.wlw.thread.casedemo02;
public class Girl extends Thread{
@Override
public void run() {
synchronized (MyLock.b){
System.out.println("女孩拿到了b");
synchronized (MyLock.a){
System.out.println("女孩拿到了a");
System.out.println("女孩可以吃饭了...........");
}
}
}
}package com.wlw.thread.casedemo02;
public class TestDeadLock {
public static void main(String[] args) {
//创建线程对象
Boy boy = new Boy();
Girl girl = new Girl();
//启动线程
boy.start();
/*try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
girl.start();
}
}
/*
运行发现,程序不会停止,男孩拿到了a,女孩拿到了b,就这样一值僵持下去
如果启动其中一个线程之后,休眠一会,再启动另一个线程,就可以正确执行了
*/
4.线程通信
4.1常用方法
- public final void wait()
- public final void wait(long timeout)
- 必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait() 时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁,进入等待队列。
- public final void notify() //唤醒等待线程
- public final void notifyAll() //唤醒全部等待线程
4.2还是存取钱的例子(存一下,取一下):
package com.wlw.thread.casedemo03;/**
* 银行卡类
*/
public class BankCard {
private double money;
private boolean flag ; //true为有钱,false为没钱
//存钱(没有钱才会存)
public synchronized void add(double m){//锁为this
if(flag){//有钱,不能存
try {
this.wait(); //锁.wait(),将线程放入等待队列,同时释放锁和cpu
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money = money + m;
System.out.println(Thread.currentThread().getName()+"存了1000块钱,余额为:"+money);
flag = true;// 存完钱了,变为有钱了
this.notify();//锁.notify(),唤醒等待队列中的线程
}
//取钱,有钱才能取
public synchronized void sub(double m){
if (!flag){ //没钱不能取
try {
this.wait();//锁.wait(),将线程放入等待队列,同时释放锁和cpu
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money = money - m;
System.out.println(Thread.currentThread().getName()+"取了1000块钱,余额为"+money);
flag = false;//取完钱了,变没钱了
this.notify();//锁.notify(), 唤醒等待队列中的线程
}
}package com.wlw.thread.casedemo03;
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.add(1000.0);
}
}
}package com.wlw.thread.casedemo03;
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.sub(1000.0);
}
}
}package com.wlw.thread.casedemo03;
public class TestBankCard {
public static void main(String[] args) {
//创建一张卡
BankCard card = new BankCard();
//创建操作
AddMoney addMoney = new AddMoney(card);
SubMoney subMoney = new SubMoney(card);
//创建线程
Thread add = new Thread(addMoney,"小李");
Thread sub = new Thread(subMoney,"小月");
//启动线程
add.start();
sub.start();
}
}
4.3多存多取问题与全部等待问题
最终代码:
package com.wlw.thread.casedemo03;/**
* 银行卡类
*/
public class BankCard {
private double money;
private boolean flag ; //true为有钱,false为没钱
//存钱(没有钱才会存)
public synchronized void add(double m){//锁为this
while (flag){//有钱,不能存
try {
this.wait(); //锁.wait(),将线程放入等待队列,同时释放锁和cpu
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money = money + m;
System.out.println(Thread.currentThread().getName()+"存了1000块钱,余额为:"+money);
flag = true;// 存完钱了,变为有钱了
this.notifyAll();//锁.notify(),唤醒等待队列中的线程
}
//取钱,有钱才能取
public synchronized void sub(double m){
while (!flag){ //没钱不能取
try {
this.wait();//锁.wait(),将线程放入等待队列,同时释放锁和cpu
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money = money - m;
System.out.println(Thread.currentThread().getName()+"取了1000块钱,余额为"+money);
flag = false;//取完钱了,变没钱了
this.notifyAll();//锁.notify(), 唤醒等待队列中的线程
}
}package com.wlw.thread.casedemo03;
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.add(1000.0);
}
}
}package com.wlw.thread.casedemo03;
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.sub(1000.0);
}
}
}package com.wlw.thread.casedemo03;
public class TestBankCard {
public static void main(String[] args) {
//创建一张卡
BankCard card = new BankCard();
//创建操作
AddMoney addMoney = new AddMoney(card);
SubMoney subMoney = new SubMoney(card);
//创建线程
Thread xiaoli = new Thread(addMoney,"小李");
Thread xiaoyue = new Thread(subMoney,"小月");
Thread mingming = new Thread(addMoney,"明明");
Thread lili = new Thread(subMoney,"丽丽");
//启动线程
xiaoli.start();
mingming.start();
xiaoyue.start();
lili.start();
}
}
5.生产者消费者问题
package com.wlw.thread.casedemo04;/**
* 面包类
*/
public class Bread {
private int id;
private String producterName;
public Bread() {}
public Bread(int id, String producterName) {
this.id = id;
this.producterName = producterName;
}
public int getId() { return id;}
public void setId(int id) {this.id = id;}
public String getProducterName() {return producterName;}
public void setProducterName(String producterName) {
this.producterName = producterName;
}
@Override
public String toString() {
return "Bread{" +
"id=" + id +
", producterName='" + producterName + '\'' +
'}';
}
}package com.wlw.thread.casedemo04;
public class BreadCon {
//装面包的容器,也是缓冲区
private Bread[] cons = new Bread[6];
//下标
private int index = 0;
//存面包
public synchronized void input(Bread bread){//锁 this
//判断容器满吗
while(index >= 6){
try {
this.wait(); //加入等待队列,释放锁和cpu
} catch (InterruptedException e) {
e.printStackTrace();
}
}
cons[index] = bread;
System.out.println(Thread.currentThread().getName()+"生产了第"+bread.getId()+"个面包");
index++;
this.notifyAll();//唤醒等待队列里的线程
}
//吃面包
public synchronized void output(){
//判断容器里还有面包吗
while(index <= 0){
try {
this.wait();//加入等待队列,释放锁和cpu
} catch (InterruptedException e) {
e.printStackTrace();
}
}
index--;
Bread bread = cons[index];
System.out.println(Thread.currentThread().getName()+"消费了第"+bread.getId()+"个面包"+",生产者:"+bread.getProducterName());
cons[index] = null;
this.notifyAll();//唤醒等待队列里的线程
}
}package com.wlw.thread.casedemo04;
public class Product implements Runnable{
private BreadCon breadCon;
public Product(BreadCon breadCon) {
this.breadCon = breadCon;
}
//生产面包
@Override
public void run() {
for (int i = 0; i < 10; i++) {
breadCon.input(new Bread(i,Thread.currentThread().getName()));
}
}
}package com.wlw.thread.casedemo04;
public class Consume implements Runnable{
private BreadCon breadCon;
public Consume(BreadCon breadCon) {
this.breadCon = breadCon;
}
//消费面包
@Override
public void run() {
for (int i = 0; i < 10; i++) {
breadCon.output();
}
}
}package com.wlw.thread.casedemo04;
public class Test {
public static void main(String[] args) {
//创建容器
BreadCon breadCon = new BreadCon();
//创建操作
Product product = new Product(breadCon);
Consume consume = new Consume(breadCon);
//创建线程
Thread xiaoli = new Thread(product, "小李");
Thread xiaoyue = new Thread(consume, "小月");
Thread mingming = new Thread(product, "明明");
Thread lili = new Thread(consume, "丽丽");
//启动线程
xiaoli.start();
xiaoyue.start();
mingming.start();
lili.start();
}
}
6.小结
- 方式1:继承Thread类
- 方式2:实现Runnable接口 (-一个任务Task),传入给Thread对象并执行。
- 同步代码块:为方法中的局部代码 (原子操作) 加锁。
- 同步方法:为方法中的所有代码 (原子操作) 加锁。
- wait() / wait(long timeout); // 等待
- notify() / notifyAll(); // 通知