Java_从入门到JavaEE_21
一、线程安全
1.线程安全
问题原因:多个线程操作同一个资源,容易出现脏数据
解决方案:多个线程操作同一个资源,必须加锁,让多个线程互斥住
经验:要想多个线程互相互斥,就必须使用同一个锁对象
synchronized加锁方式
同步代码块
synchronized(锁对象){//自动上锁
…想要互斥住的代码…
}//自动解锁同步方法
//同步的成员方法 - 锁对象:this
public synchronized void method(){//自动上锁
…想要互斥住的代码…
}//自动解锁
//同步的静态方法 - 锁对象:该类的.class
public synchronized static void method(){//自动上锁
…想要互斥住的代码…
}//自动解锁
Lock锁
//锁对象
Lock lock = new ReentrantLock();
lock.lock();//手动上锁
…想要互斥住的代码…
lock.unlock();//手动解锁
2.买票案例
需求:铁道部发布了一个售票任务,要求销售1000张票,要求有3个窗口来进行销售,请编写多线程程序来模拟这个效果:
窗口001正在销售第1张票
窗口001正在销售第2张票
窗口002正在销售第3张票
。。。
窗口002正在销售第1000张票
窗口002票已售完
窗口001票已售完
窗口003票已售完 分析可能出现的问题:
问题一:每个窗口各买了1000张票,一共卖了3000张
出现原因:3个线程抢到CPU资源后,调用了3次run(),allTicket和curTicket为局部变量,也自然有3份
解决方案:allTicket和curTicket设置为静态变量,让多个线程的对象共享数据
问题二:有些票没卖,有些票卖了重票
出现原因:线程抢到CPU资源后,做了curTicket++,还没来得及做票得输出,就退出CPU资源,然后被其他线程抢到资源了
解决方案:当前线程做curTicket++和输出执行完毕后,才能让其他线程抢到CPU资源并且运行 – 加锁
问题三:多卖了票
出现原因:curTicket为999时,当前线程进入到循环里,还没来得及加锁,就退出CPU资源,然后被其他线程抢到资源了
解决方案:在锁中加判断
案例一:同步代码块
public class Test { public static void main(String[] args) { MyThread t1 = new MyThread("001"); MyThread t2 = new MyThread("002"); MyThread t3 = new MyThread("003"); t1.start(); t2.start(); t3.start(); } } public class MyThread extends Thread{ private static int allTicket = 1000; private static int curTicket = 0; private static Object obj = new Object(); public MyThread(String name) { super(name); } @Override public void run() { while(curTicket < allTicket){ //synchronized ("abc") { //synchronized (Integer.class) { synchronized (obj) { if(curTicket < allTicket){ curTicket++; System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + curTicket + "张票"); } } } System.out.println("窗口" + Thread.currentThread().getName() + "票已售完"); } }
案例二:同步方法
public class Test { public static void main(String[] args) { MyThread t1 = new MyThread("001"); MyThread t2 = new MyThread("002"); MyThread t3 = new MyThread("003"); t1.start(); t2.start(); t3.start(); } } public class MyThread extends Thread{ private static int allTicket = 1000; private static int curTicket = 0; public MyThread(String name) { super(name); } @Override public void run() { while(curTicket < allTicket){ method(); } System.out.println("窗口" + Thread.currentThread().getName() + "票已售完"); } public static synchronized void method(){ if(curTicket < allTicket){ curTicket++; System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + curTicket + "张票"); } } }
案例三:Lock锁
public class Test { public static void main(String[] args) { MyThread t1 = new MyThread("001"); MyThread t2 = new MyThread("002"); MyThread t3 = new MyThread("003"); t1.start(); t2.start(); t3.start(); } } import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyThread extends Thread{ private static int allTicket = 1000; private static int curTicket = 0; private static Lock lock = new ReentrantLock(); public MyThread(String name) { super(name); } @Override public void run() { while(curTicket < allTicket){ try { lock.lock(); if(curTicket < allTicket){ curTicket++; System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + curTicket + "张票"); } } catch (Exception e) { } finally { lock.unlock(); } } System.out.println("窗口" + Thread.currentThread().getName() + "票已售完"); } }
3.package com.qf.thread01_type03;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread{
private static int allTicket = 1000;
private static int curTicket = 0;
private static Lock lock = new ReentrantLock();
public MyThread(String name) {
super(name);
}
@Override
public void run() {
while(curTicket < allTicket){
try {
lock.lock();
if(curTicket < allTicket){
curTicket++;
System.out.println("窗口" + Thread.currentThread().getName() + "正在销售第" + curTicket + "张票");
}
} catch (Exception e) {
} finally {
lock.unlock();
}
}
System.out.println("窗口" + Thread.currentThread().getName() + "票已售完");
}
3.单例模式
懒汉式
概念:
实例化:new对象的过程
单例模式:该类只有1个对象
优点:节约空间
缺点:线程不安全
public class Test { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { A a = A.getInstance(); System.out.println(a); } }).start(); new Thread(new Runnable() { @Override public void run() { A a = A.getInstance(); System.out.println(a); } }).start(); } } public class A { private static A a; private A() { } public static A getInstance(){ if(a == null){ a = new A(); } return a; } }
饿汉式
优点:线程安全
缺点:浪费空间
public class Test { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { A a = A.getInstance(); System.out.println(a); } }).start(); new Thread(new Runnable() { @Override public void run() { A a = A.getInstance(); System.out.println(a); } }).start(); } } public class A { private static A a = new A(); private A() { } public static A getInstance(){ return a; } }
枚举单例模式(饿汉式)
public class Test { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { A a = A.a; System.out.println(a); } }).start(); new Thread(new Runnable() { @Override public void run() { A a = A.a; System.out.println(a); } }).start(); } } public enum A { //public static final A a = new A(); a; }
双重检验锁的单例模式
优点:线程安全、节约空间
public class Test { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { A a = A.getInstance(); System.out.println(a); } }).start(); new Thread(new Runnable() { @Override public void run() { A a = A.getInstance(); System.out.println(a); } }).start(); } } public class A { private volatile static A a; private A() { } public static A getInstance(){ if(a == null){ synchronized (A.class) { if(a == null){ a = new A(); } } } return a; } }
volatile – 防止指令重排
创建对象的过程:
a.开辟空间 ----- new 对象() -- 0x001
b.调用构造方法 -- 初始化数据
c.将空间赋值给引用 -- 类型 引用 = 0x001
创建对象的步骤:a/b/c 或 a/c/b
- 注意:如果创建对象的步骤是a/c/b,多线程的情况下可能会导致获取的属性为null
- 解决方案:使用volatile,防止指令重排,创建的步骤必须按照a/b/c
4.死锁
根源:如果出现同步嵌套(同步里面包含同步),就有可能会产生死锁问题
最常见的死锁形式是当线程1 持有对象A 上的锁,而且正在等待对象B 上的锁;而线程2 持有对象B 上的锁,却正在等待对象A 上的锁。这两个线程永远都不会获得第二个锁,或是释放第一个锁,所以它们只会永远等待下去。-----死锁(只有对方都拥有了彼此的资源且不愿意释放时,才会出现死锁问题)
模拟死锁
public class Test01 { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { synchronized (KuaiZi.a) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (KuaiZi.b) { System.out.println("哲学家1吃饭了"); } } } }).start(); new Thread(new Runnable() { @Override public void run() { synchronized (KuaiZi.b) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (KuaiZi.a) { System.out.println("哲学家2吃饭了"); } } } }).start(); } } class KuaiZi{ public static Object a = new Object(); public static Object b = new Object(); }
二、生产者消费者模型
1.生产一个,消费一个
场景:一个生产者,一个消费者
案例:生产手机
分析:
- 产品类 – Phone
- 生产者线程类 – Producer
- 消费者线程类 – Consumer
步骤:
生产者线程和消费者线程操作同一个资源
脏数据:
null – 0.0
hw – 0.0
两个产品之间来回切换 – 目的:让步骤1的问题更加明显
脏数据:
null – 0.0
hw – 0.0
xm – 3999.0
hw – 1999.0
解决方法:加锁 -> 生产者线程在生产的过程中(setBrand()和setPrice()),消费者线程不能输出
脏数据:
null – 0.0
生产一个,消费一个 -> 生产者线程和消费者线程来回切换
public class Test{ public static void main(String[] args) { Phone phone = new Phone();//null -- 0.0 Producer p = new Producer(phone); Consumer c = new Consumer(phone); p.start(); c.start(); } } public class Phone { private String brand; private double price; private boolean isStore; public Phone() { } public Phone(String brand, double price) { this.brand = brand; this.price = price; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public boolean isStore() { return isStore; } public void setStore(boolean isStore) { this.isStore = isStore; } } //生产者线程 public class Producer extends Thread{ private Phone phone; public Producer(Phone phone) { this.phone = phone; } @Override public void run() { boolean flag = true; while(true){ synchronized (phone) { if(phone.isStore()){//有库存 try { phone.wait();//等待:1.将当前线程记录到对象监视器中 2.释放锁资源 3.当前线程进入到阻塞状态 } catch (InterruptedException e) { e.printStackTrace(); } } if(flag){ phone.setBrand("hw"); phone.setPrice(3999); }else{ phone.setBrand("xm"); phone.setPrice(1999); } flag = !flag; phone.setStore(true); phone.notify();//唤醒 } } } } //消费者线程 public class Consumer extends Thread{ private Phone phone; public Consumer(Phone phone) { this.phone = phone; } @Override public void run() { while(true){ synchronized (phone) { if(!phone.isStore()){//没有库存 try { phone.wait();//等待:1.将当前线程记录到对象监视器中 2.释放锁资源 3.当前线程进入到阻塞状态 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(phone.getBrand() + " -- " + phone.getPrice());phone.setStore(false); phone.notify();//唤醒:唤醒对象监视器中随机的线程 } } } }
ps:多个生产者,多个消费者
只需要将唤醒改为notifyAll(),将if判断改为while循环。
原文地址:https://blog.csdn.net/lv785/article/details/139101422
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!