关于java中的并发你真的懂吗?

并发的理解

聊关于并发首先得知道,为什么会有并发这个概念?

在操作系统中有两个核心的概念:进程与线程,进程是操作系统资源分配和调度的单位,有着独立的内存空间并且进程间的数据相互隔离,进程的崩溃不会影响另一个进程;线程是进程中的执行单元(简而言之假设一个进程中的多个功能,功能就是线程),线程共享进程的内存,并且他们可以相互通信;

当操作系统中并发(concurrent)指的是多个程序可以同时运行的现象,更细化的是多进程可以同时运行或者多指令可以同时运行;具体表现为同一时刻多个线程在访问同一个资源,多个线程对一个点。

在java中,可以使用synchronized关键字和Lock来解决并发带来的问题线程不安全问题;举例一个经典案例卖票问题

public class Ticket {
    private int count; // 票的总数

    public Ticket(int count) {
        this.count = count;
    }

    // 卖票方法
    public void sellTicket() {
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() + " 卖出一张票,剩余票数: " + --count);
        } else {
            System.out.println("票已售罄");
        }
    }

    public int getCount() {
        return count;
    }
}
 

当我们创建三个线程时会发现,小飞机1和三卖的都是第9张票这就不对,出现的原因就是因为,在卖第9张票的时候,1,3飞机一起进了卖票的方法,此时都是9编号,那么怎么解决呢,加锁,让所有的人卖的票必须要有有钥匙才能把卖票盒子打开,将这个数据保护起来。

1 Synchronized 关键字使用(面试问)

synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{} 括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
小技巧:虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法定义的一部分,因此,synchronized 关键字不能被继承。如果在父类中的某个方 法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这 个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上
synchronized 关键字才可以。当然,还可以在子类方法中调用父类中相应的方 法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,
子类的方法也就相当于同步了。
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用范围对象是这个类的所有对象;
4. 修改一个类,其作用的范围是 synchronized 后面括号括起来的部分,作用范围对象是这个类的所有对象。 

2.Lock接口

当然你还可以使用java中的类(Lock)
lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他 线程获取,则进行等待。
采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一 般来说,使用 Lock 必须在 try{}catch{}块中进行,并且将释放锁的操作放在 finally 块中进行,以保证锁一定被被放,防止死锁的发生。使用为这样:
public class LIticket {
    private int number = 30;

    //可重入
    private final ReentrantLock lock = new ReentrantLock(false);

    //非公平锁 线程饿死 效率高
    //公平锁 线程不饿死 效率低
    public void sale() {
        lock.lock();
        try {
            if (number <= 0) {
                return;
            }
            System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余:" + number);
        } finally {
            lock.unlock();
        }
    }
}
在说并发理解的时候有说到,进程间数据是相互独立的,线程他们之间可以相互通信(那通信不就解决了我刚刚卖两张9号票的问题了吗);有的同学非常聪明;使用的关键字和接口都是实现线程间通信的方法;

3.线程间的通信

Synchronized 通信

了解通信前我们首先得知道线程的五个状态
新建状态(New) :线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

简单理解假设我现在有两个线程进行加减操作使他们A+完后B,那么根据线程的生命周期可以知道,第一步先创两个线,判断这个基础是否为操作前后的,根据操作前后进行对应的操作,如代码
package com.example.juc.sync;

/**
 * ClassName:Thred1
 * Package:com.example.juc.sync
 * Description:
 *
 * @Author:小飞机
 * @Create:2025/5/23 - 15:38
 * @Version: v1.0
 */
class  Share{
    private int number = 0;
    //+1
    public synchronized void   add() throws InterruptedException {
        //1.判断
        if (number != 0) {
            this.wait();
        }
        //2. 干活
        number++;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        //3.通知
        this.notifyAll();
    }
    public synchronized void   dec() throws InterruptedException {
        //1.判断 虚假唤醒 在哪里睡在哪里醒
        if (number == 0) {
            this.wait();
        }
        //2. 干活
        number--;
        System.out.println(Thread.currentThread().getName() + "\t" + number);
        //3.通知
        this.notifyAll();
    }
}
public class Thred1 {
    public static void main(String[] args) {
        Share share = new Share();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    share.add();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    share.dec();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();

    }
}

注意使用wait方法时,千万不要放在一个if中,线程是在哪里休眠就在哪里苏醒,他如果抢到了锁,他并不会去看是否是操作前后的,而是直接进行操作(也就是虚假唤醒)

Lock通信

解释一下Lock的实现类并没有实现通信的方法,也没有wait notifyall的方法,但是Condition类实现,,接下来根据文档的示例,你肯定也能模仿出来awai()==wait,notifyall=signall(文档中signal不是唤醒全部),我知道现在你只需要知道,这个类怎么样实例化,请看代码

package com.example.juc.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.prefs.Preferences;

/**
 * ClassName:Thred2
 * Package:com.example.juc.lock
 * Description:
 *
 * @Author:小飞机
 * @Create:2025/5/23 - 15:54
 * @Version: v1.0
 */
class Share{
    private int number =0;

    private static Lock lock = new ReentrantLock();
    private static Condition condition =lock.newCondition();
    public void increase(){
        lock.lock();
       try {
           while (number!=0){
               condition.await();
           }
           number++;
           System.out.println(Thread.currentThread().getName()+"==="+number);
           condition.signalAll();
       } catch (InterruptedException e) {
           throw new RuntimeException(e);
       } finally {
           lock.unlock();
       }
    }
    public void decrease(){
        lock.lock();
        try {
            //解决虚假唤醒问题
            while (number==0){
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"==="+number);
            condition.signalAll();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
}
public class Thred2 {
    public static void main(String[] args) {
        Share share = new Share();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                share.increase();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                share.decrease();
            }
        }, "B").start();
    }


}

这里Condition还有一个非常强大的功能,定制化的通信,Condition相当于一个钥匙,当我在一个线程中A中调用线程B的singal,此时B线程将从等待状态变为锁定状态,进入锁池中竞争锁,并等待获取cpu权限,例如我现在要让abc顺序分别打印,a打印完通知b,b打印完通知c,循环如此,记得要用标识位子去标识当前可以打印的线程,避免其他线程抢到锁后就开始打印

package com.example.juc.lock;

import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ClassName:Thred3
 * Package:com.example.juc.lock
 * Description:
 *
 * @Author:小飞机
 * @Create:2025/5/23 - 16:04
 * @Version: v1.0
 */
class ShareResource {
    //定义标识位
    private int flags = 1;
    //锁
    private Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();
    public void print5() throws InterruptedException {
        lock.lock();
        try {
            while (flags != 1) {
                c1.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + (i + 1));
            }
            flags = 2;
            //唤醒c2
            c2.signal();
        }finally {
            lock.unlock();
        }
    }
    public void print10() throws InterruptedException {
        lock.lock();
        try {
            while (flags != 2) {
                c2.await();
            }
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + (i + 1));
            }
            flags = 3;
            //唤醒c3
            c3.signal();
        }finally {
            lock.unlock();
        }
    }
    public void print15() throws InterruptedException {
        lock.lock();
        try {
            while (flags != 3) {
                c3.await();
            }
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + (i + 1));
            }
            flags = 1;
            //唤醒c1
            c1.signal();
        }finally {
            lock.unlock();
        }
    }
}
public class Thred3 {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(() -> {
            for (int i = 0; i < 15; i++) {
                try {
                    shareResource.print5();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },  "A").start();
        new Thread(() -> {
            for (int i = 0; i < 15; i++) {
                try {
                    shareResource.print10();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 15; i++) {
                try {
                    shareResource.print15();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },  "C").start();
    }
}

4.集合的线程安全

ArryList是线程安全的吗?

不是,从他的方法中可以并没有对他进行锁

多线程下的添加和修改可能会出现异常

怎么办,可以使用他的安全类,Vector,Collections,CopyOnWriteArrayList

 public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
//        List<String> list =new Vector<>();

//        List<String> list = Collections.synchronizedList();
//        写时拷贝 并发读,不会锁住写,不会锁住读 并发写,不会锁住写,会锁住读
//        CopyOnWriteArrayList<Object> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
                System.out.println(list);
            },  "Thread" + i).start();
        }
    }

简单总结一下:

1.线程安全与线程不安全集合

集合类型中存在线程安全与线程不安全的两种,常见例如:

ArrayList ----- Vector

HashMap -----HashTable

但是以上都是通过 synchronized 关键字实现,效率较低

2.Collections 构建的线程安全集合3.java.util.concurrent 并发包下 CopyOnWriteArrayList CopyOnWriteArraySet 类型,通过动态数组与线程安全两个方面保证线程安全

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值