自学内容网 自学内容网

Java_从入门到JavaEE_21

一、线程安全

1.线程安全

  1. 问题原因:多个线程操作同一个资源,容易出现脏数据

  2. 解决方案:多个线程操作同一个资源,必须加锁,让多个线程互斥住

  3. 经验:要想多个线程互相互斥,就必须使用同一个锁对象

  4. synchronized加锁方式

    1. 同步代码块
      synchronized(锁对象){//自动上锁
      …想要互斥住的代码…
      }//自动解锁

    2. 同步方法

      //同步的成员方法 - 锁对象:this

      public synchronized void method(){//自动上锁

      ​ …想要互斥住的代码…

      }//自动解锁

      //同步的静态方法 - 锁对象:该类的.class

      public synchronized static void method(){//自动上锁

      ​ …想要互斥住的代码…

      }//自动解锁

    3. Lock锁

      //锁对象

      Lock lock = new ReentrantLock();

      lock.lock();//手动上锁

      …想要互斥住的代码…

      lock.unlock();//手动解锁

2.买票案例

​ 需求:铁道部发布了一个售票任务,要求销售1000张票,要求有3个窗口来进行销售,请编写多线程程序来模拟这个效果:

​ 窗口001正在销售第1张票
​ 窗口001正在销售第2张票
​ 窗口002正在销售第3张票
​ 。。。
​ 窗口002正在销售第1000张票
​ 窗口002票已售完
​ 窗口001票已售完
​ 窗口003票已售完

​ 分析可能出现的问题:

  1. 问题一:每个窗口各买了1000张票,一共卖了3000张

    出现原因:3个线程抢到CPU资源后,调用了3次run(),allTicket和curTicket为局部变量,也自然有3份

    解决方案:allTicket和curTicket设置为静态变量,让多个线程的对象共享数据

  2. 问题二:有些票没卖,有些票卖了重票

    出现原因:线程抢到CPU资源后,做了curTicket++,还没来得及做票得输出,就退出CPU资源,然后被其他线程抢到资源了

    解决方案:当前线程做curTicket++和输出执行完毕后,才能让其他线程抢到CPU资源并且运行 – 加锁

  3. 问题三:多卖了票

    出现原因: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.单例模式

  1. 懒汉式

    概念:

    实例化: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;
    }
    }
    
  2. 饿汉式

    优点:线程安全

    缺点:浪费空间

    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;
    }
    }
    
  3. 枚举单例模式(饿汉式)

    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;
    }
    
  4. 双重检验锁的单例模式

    优点:线程安全、节约空间

    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. 根源:如果出现同步嵌套(同步里面包含同步),就有可能会产生死锁问题

  2. 最常见的死锁形式是当线程1 持有对象A 上的锁,而且正在等待对象B 上的锁;而线程2 持有对象B 上的锁,却正在等待对象A 上的锁。这两个线程永远都不会获得第二个锁,或是释放第一个锁,所以它们只会永远等待下去。-----死锁(只有对方都拥有了彼此的资源且不愿意释放时,才会出现死锁问题)

  3. 模拟死锁

    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.生产一个,消费一个

场景:一个生产者,一个消费者

案例:生产手机

分析:

  1. 产品类 – Phone
  2. 生产者线程类 – Producer
  3. 消费者线程类 – Consumer

步骤:

  1. 生产者线程和消费者线程操作同一个资源

    脏数据:

    null – 0.0

    hw – 0.0

  2. 两个产品之间来回切换 – 目的:让步骤1的问题更加明显

    脏数据:

    null – 0.0

    hw – 0.0

    xm – 3999.0

    hw – 1999.0

    解决方法:加锁 -> 生产者线程在生产的过程中(setBrand()和setPrice()),消费者线程不能输出

    脏数据:

    null – 0.0

  3. 生产一个,消费一个 -> 生产者线程和消费者线程来回切换

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)!