多线程安全问题
- 多线程访问了共享数据,会产生安全问题
- 线程不安全的例子如下,最经典的车站卖票实例
//自定义线程类
public class MyRunnable implements Runnable {
private int ticket = 100;
@Override
public void run() {
try {
//睡眠10毫秒,提高线程安全的概率
Thread.sleep(10);
while (true){
if(ticket>0){
System.out.println("当前线程"+Thread.currentThread().getName()+"正在买第"+ticket+"张票");
ticket--;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//测试类
public class MyTest {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
t1.start();
Thread t2 = new Thread(mr);
t2.start();
Thread t3 = new Thread(mr);
t3.start();
}
}
以上代码运行结果截图如下:
线程同步来解决线程安全问题:
- 同步代码块
- 同步方法
- 锁机制
同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全的代码(访问了共享数据的代码)
}
注意:
通过代码中的锁对象,可以使用任意的对象
但是必须保证多个线程使用的锁对象是同一个
锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
public class MyRunnable implements Runnable {
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
synchronized(obj){
try {
//睡眠10毫秒,提高线程安全的概率
Thread.sleep(1000);
while (true){
if(ticket>0){
System.out.println("当前线程"+Thread.currentThread().getName()+"正在买第"+ticket+"张票");
ticket--;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//测试类
public class MyTest {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
t1.start();
Thread t2 = new Thread(mr);
t2.start();
Thread t3 = new Thread(mr);
t3.start();
}
}
同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步
同步方法
使用步骤:
- 把共享数据的数据抽取出来放在一个方法里,
- 在方法上添加synchronized修饰符
格式:
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全的问题的代码(访问了共享数据的代码)
}
public class MyRunnable2 implements Runnable {
private int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while (true){
payTicket();
}
}
/*
同步方法也会把内部的代码锁住,只让一个线程执行,同步方法的锁对象是 new MyRunnable2()
也就是this
*/
public synchronized void payTicket(){
try {
//睡眠10毫秒,提高线程安全的概率
Thread.sleep(1000);
if(ticket>0){
System.out.println("当前线程"+Thread.currentThread().getName()+"正在买第"+ticket+"张票");
ticket--;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//測試代碼
public class MyTest {
public static void main(String[] args) {
MyRunnable2 mr = new MyRunnable2();
Thread t1 = new Thread(mr);
t1.start();
Thread t2 = new Thread(mr);
t2.start();
Thread t3 = new Thread(mr);
t3.start();
}
}
死锁的实现代码:
public class MyTest {
public static void main(String[] args) {
Object objA = new Object();
Object objB = new Object();
new Thread(()->{
while(true){
synchronized (objA){
// 线程一
synchronized (objB){
System.out.println("张三正在走路。。。。");
}
}
}
}).start();
new Thread(()->{
while (true){
synchronized (objB){
// 线程二
synchronized (objA){
System.out.println("李四正在走路。。。。");
}
}
}
}).start();
}
}
生产者和消费者
需要执行的步骤如图:
代码实现如下:
//标记类 辅助作用
public class Desk {
/**
桌子上是否有面包
*/
public static boolean FLAG = false;
/**
* 汉堡的总数
*/
public static int count = 10;
/**
* 锁对象
*/
public static final Object lock = new Object();
}
/**
* @author Joshua
* @since 2021/8/11
* 消费者步骤:
* 1、判断桌子上是否有汉堡
* 2、如果没有就等待
* 3、如果有就开吃
* 4、吃完之后,桌子上的汉堡就没有了,叫醒等待的生产者继续生产,汉堡的总数量减1
*/
public class Foodie extends Thread{
@Override
public void run() {
while(true){
synchronized (Desk.lock){
//如果吃货已经吃了10个,就直接退出
if(Desk.count<=0){
break;
}else{
//判断桌子上是否有汉堡
if(Desk.FLAG){
//如果有就开吃
System.out.println("吃货开始吃汉堡。。。。。真好吃");
Desk.FLAG = false;
Desk.lock.notifyAll();
Desk.count--;
}else{
//如果没有就等待
//使用什么对象当作锁,就使用什么对象去调用等待和唤醒的方法
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
/**
* @author joshua
* @since 2021/8/11
* 生成者步骤:
* 1、判断桌子上是否有汉堡
* 2、有就等待,没有就生产
* 3、把汉堡放在桌子上
* 4、叫醒等待的消费者开吃
*/
public class Cooker extends Thread {
@Override
public void run() {
synchronized (Desk.lock){
while (true){
if(Desk.count<=0){
break;
}else{
//判断桌子上是否有汉堡
if(Desk.FLAG){
//有就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//没有就生产
System.out.println("大厨正在生产汉堡.....完工。");
//把汉堡放在桌子上
Desk.FLAG=true;
//叫醒等待的消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
//测试类
public class MyTest {
public static void main(String[] args) {
Foodie foodie = new Foodie();
foodie.start();
Cooker cooker = new Cooker();
cooker.start();
}
}
上面代码的Desk类并不符合面向对象,下面进行代码改写
public class Desk {
/**
桌子上是否有面包
*/
public boolean flag;
/**
* 汉堡的总数
*/
public int count ;
/**
* 锁对象
*/
public final Object lock = new Object();
public Desk() {
//初始化 flag和count的值
this(false,10);
}
public Desk(boolean flag, int count) {
this.flag = flag;
this.count = count;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Object getLock() {
return lock;
}
}
/**
* @author Joshua
* @since 2021/8/11
* 消费者步骤:
* 1、判断桌子上是否有汉堡
* 2、如果没有就等待
* 3、如果有就开吃
* 4、吃完之后,桌子上的汉堡就没有了,叫醒等待的生产者继续生产,汉堡的总数量减1
*/
public class Foodie extends Thread{
private Desk desk;
public Foodie(Desk desk){
this.desk = desk;
}
@Override
public void run() {
while(true){
synchronized (desk.getLock()){
//如果吃货已经吃了10个,就直接退出
if(desk.getCount()<=0){
break;
}else{
//判断桌子上是否有汉堡
if(desk.isFlag()){
//如果有就开吃
System.out.println("吃货开始吃汉堡。。。。。真好吃");
desk.setFlag(false);
desk.getLock().notifyAll();
desk.setCount(desk.getCount()-1);
}else{
//如果没有就等待
//使用什么对象当作锁,就使用什么对象去调用等待和唤醒的方法
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
/**
* @author joshua
* @since 2021/8/11
* 生成者步骤:
* 1、判断桌子上是否有汉堡
* 2、有就等待,没有就生产
* 3、把汉堡放在桌子上
* 4、叫醒等待的消费者开吃
*/
public class Cooker extends Thread {
private Desk desk;
public Cooker(Desk desk){
this.desk = desk;
}
@Override
public void run() {
synchronized (desk.getLock()){
while (true){
if(desk.getCount()<=0){
break;
}else{
//判断桌子上是否有汉堡
if(desk.isFlag()){
//有就等待
try {
desk.getLock().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//没有就生产
System.out.println("大厨正在生产汉堡.....完工。");
//把汉堡放在桌子上
desk.setFlag(true);
//叫醒等待的消费者开吃
desk.getLock().notifyAll();
}
}
}
}
}
}
public class MyTest {
/*
消费者步骤:
1、判断桌子上是否有汉堡
2、如果没有就等待
3、如果有就开吃
4、吃完之后,桌子上的汉堡就没有了,叫醒等待的生产者继续生产,汉堡的总数量减1
生成者步骤:
1、判断桌子上是否有汉堡
2、有就等待,没有就生产
3、把汉堡放在桌子上
4、叫醒等待的消费者开吃
*/
public static void main(String[] args) {
Desk desk = new Desk();
Foodie foodie = new Foodie(desk);
foodie.start();
Cooker cooker = new Cooker(desk);
cooker.start();
}
}
使用队列实现生产者消费者的案例
//生产者类
public class Cooker extends Thread {
private ArrayBlockingQueue<String> list;
public Cooker(ArrayBlockingQueue<String> list) {
this.list = list;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
list.put("食物");
System.out.println("厨师向队列中放入食物");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者类
public class Foodie extends Thread {
private ArrayBlockingQueue<String> list;
public Foodie(ArrayBlockingQueue<String> list) {
this.list = list;
}
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
System.out.println("吃货从队列中取走"+list.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//测试类
public class MyTest {
public static void main(String[] args) {
//创建一个队列 默认容量为1
ArrayBlockingQueue<String> list = new ArrayBlockingQueue<>(1);
Foodie foodie = new Foodie(list);
foodie.start();
Cooker cooker = new Cooker(list);
cooker.start();
}
}