
🧡💛💚💙💜🤎💗🧡💛💚💙💜🤎💗 感谢各位一直以来的支持和鼓励 , 制作不易 🙏 求点赞 👍 ? 收藏 ? ? 关注? 一键三连走起 ! ! ! 🧡💛💚💙💜🤎💗 🧡💛💚💙💜🤎💗
一、线程安全的概述
????????如果有多个线程在同时运行,并且这些线程可能会同时运行一段相同的代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,这样就是线程安全的。
二、线程同步机制
????????当我们使用多个线程访问同一个资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。 解决多钱程并发访问一个资源的安全性问题,Java中提供了同步机制(synchronized)来解决。
Java中有三种方法实现同步操作:同步代码块,同步方法,锁机制。
案例,使用多线程模拟电影院卖票,卖100张票,有三个售票窗口
public class RunnableImp implements Runnable {
private int ticket=100;
@Override
public void run() {
while (true){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"--正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
public class test {
public static void main(String[] args) {
RunnableImp runnableImp = new RunnableImp();
Thread thread = new Thread(runnableImp);
thread.start();
new Thread(runnableImp).start();
new Thread(runnableImp).start();
}
}
测试结果:
会线程安全问题1:售出相同的票 会线程安全问题2:售出不存在的票 
1、同步代码块
使用格式:
synchronize(锁对象){
//可能出现线程安全的代码块
}
注意:
1。锁对象可以是任意的数据类型
2。多个线程对象要使用同一把锁
锁对象的作用:
把同步代码块锁住,只让一个线程在同步代码块中执行
示例代码;
public class RunnableImp implements Runnable {
private int ticket=100;
Object obj=new Object();
@Override
public void run() {
while (true){
synchronized (obj){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"--正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
运行test测试类,得到测试结果:  
????????由测试结果,可以发现解决了卖出重复票和不存在的票的线程安全问题。
同步代码块实现同步技术的原理分析:
????????同步代码块中使用了一个锁对象也叫做(“同步锁”、“对象锁”、“对象监视器”)在多线程实现售票的案例中,有三个线程一起抢夺CPU的执行权,哪个线程抢夺到了CPU执行权,哪个就执行run()方法,当遇到了同步代码块synchronized时,就会检查代码块中是否有锁对象,如果有那么就会获取到该对象,并进入代码块中执行;那么剩下的线程,若是也有抢到了CPU的执行权,也会执行synchronized同步代码块并且判断是否有锁对象,若发现没有,那么此线程就会进入到阻塞状态,直到获取了锁对象的线程归还锁对象,也就是一直等到上一个线程执行完同步代码块中的代码并把锁对象归还到同步代码块中,此线程才能进入到同步中执行代码。 所以同步技术的核心就是,同步中的线程没有执行完毕不会释放锁对象;而同步外的线程没有锁对象则进不去同步
2、同步方法
解决线程安全问题的二种方案:使用同步方法
作用:
同步方法会将方法内部的代码锁住,只让一个线程使用。
使用步骤:
1.将访问了共享数据的代码也就是可能出现线程安全问题的代码抽取出来,
放到一个方法中去
2.给方法添加 synchronized 修饰狩
使用格式:
权限修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现线程安全问题的代码(访问了共享数据的代码块 )
}
示例代码:
public class RunnableImp implements Runnable {
private int ticket=100;
@Override
public void run() {
while (true){
sellticket();
}
}
public synchronized void sellticket(){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"--正在卖第"+ticket+"张票");
ticket--;
}
}
}
运行test测试代码:测试结果和同步代码块测试结果一样,没有出现重复票或不存在的票
3、锁机制
????????java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更加广泛的锁定操作, 而且同步代码块和同步方法具有的功能Lock也都会有,并且Lock更能体现出面向对象。 Lock锁也称为同步锁,它的加锁和释放锁都已经方法化了:
public void lock(): 加同步锁。
public void unlock(: 释放同步锁.
示例代码:
解决线程安全问题的二种方案:使用Lock锁
使用步骤:
1.在成员位置创建一个ReentrantLock对象
2.在可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁
3.在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
public class RunnableImp implements Runnable {
private int ticket=100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"--正在卖第"+ticket+"张票");
ticket--;
}
lock.unlock();
}
}
}
运行test测试代码:测试结果和同步代码块测试结果一样,没有出现重复票或不存在的票
三、线程的六种状态
-
New(新建状态)
- 线程刚被创建时的状态,但是线程还未启动,还没调用start方法。
-
Runnable(可运行状态)
- 线程可以在java虚拟机中运行的状态,线程可能正在运行自己代码也可能没有,取决于操作系统的处理器。
- Blocked(锁阻塞状态)
- 当一个线程试图获取一个对象锁时,但是该对象锁已经被其他的线程持有,那么该线程就会进入Blocked阻塞状态;而当该线程持有锁对象时,该线程就会转变成Runnable可运行状态。
- Waiting(无限等待状态)
- 一个钱程在等待另一个线程执行一个(唤醒)动作时,那么该线程就是进入Waiting无限等待状态。进入这个状态后的线程是不能被自动唤醒的,必须要等待另外一个线程调用notify()或者notifyAll()方法才能够被唤醍。
- Timed Waiting(计时等待状态)
- 与waiting无限等待状态很相似,当调用带有超时参数的方法时,他们就会进入Timed Waiting计时等待状态。而该状态将会一直保持着,直到超时期满或者接收到唤醒通知。带有超时参数的常用方法有
Thread.sleep() 和 Object.wait() 。
四、线程等待与唤醒机制
等待唤醒中使用的方法
????????等待唤醒机制用于解决线程间通信的问题,
????????使用的3个方法如下:
- 1.wait()方法 :调用了wait()方法候,线程就不再参与调度,进入 wait set中,不会浪费CPU资源,也不会竞争锁。这种线程状态就是WAITING(等待状态)。同时它还会等着别的线程执行特别的动作—>通知( notify) 在这个对象上等待的线程就会从walt set中释放出来,重新进入到调度队列 ( ready queue)中
- 2.notify()方法 :选择所通知对象的 wait set 中的一个线程并释放它。也就是唤醒在此监视器上等待的单个线程
- 3.notifyAll():释放所通知对象的wait set 中的全部线程。也就是唤醒在此监视器上等待的所有线程
????????注意:被通知的线程是不能立即进入到执行状态的,因为此前该线程实在同步块内中断的,此时该线程已经没有锁对象了,所以他需要再次的获取锁对象,那么此时就可能会有其他的线程与其竞争锁对象,该线程竞争成功i获得锁对象后才会在之前调用到wait()方法之处恢复执行。 ????????所以可以总结到:若线程能够获取到锁,那么线程就会从等待状态转变成运行状态;否则线程则只是从wait set中出来又进入到entry set中,线程就会又从等待状态转换到阻塞状态
调用wait()和notify()方法需要注意的细节
- 1.wait方法与notify方法必须要由同一个锁对象调用。因为对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait()方法后的线程。
- 2.wait()方法与notify()方法都是Object类的方法。因为锁对象可以是任意类型的对象,而任意类的对象所属的类都是继承了Object类的。 I
- 3.walt()方法与notlfy()方法必须要在同步代码块或者是同步方法中使用。因为要调用这两个方法,必须要通过锁对象。
案例:客户去牛肉面馆吃牛肉面,面馆老板首先是在等待客户点面,等到了一个客户点了一碗牛肉面,老板开始做面,客户则进入等待,等到老板面做好了就会告知客户面好了,客户开始吃面。
示例代码:
class BeefNoodles{
String tang;
String kouwei;
boolean flag =false;
}
public class BeefNoodlesRestaurant extends Thread {
private BeefNoodles beefNoodles;
public BeefNoodlesRestaurant(BeefNoodles beefnoodles) {
this.beefNoodles = beefnoodles;
}
@Override
public void run() {
int count = 2;
while (true){
synchronized (beefNoodles){
if (beefNoodles.flag==true){
try{
beefNoodles.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (count%2==0){
beefNoodles.tang="金汤";
beefNoodles.kouwei="不辣";
}else if (count%3==0){
beefNoodles.tang="红汤";
beefNoodles.kouwei="不辣";
}else if (count%5==0){
beefNoodles.tang="金汤";
beefNoodles.kouwei="一般辣";
}else if (count%7==0){
beefNoodles.tang="红汤";
beefNoodles.kouwei="一般辣";
}
System.out.println("正在做"+beefNoodles.tang+beefNoodles.kouwei+"牛肉面");
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
beefNoodles.flag=true;
count++;
beefNoodles.notify();
System.out.println("客观的"+beefNoodles.tang+beefNoodles.kouwei+"牛肉面已经制作好了");
}
}
}
}
public class KeHu extends Thread {
private BeefNoodles beefNoodles;
public KeHu(BeefNoodles beefnoodles) {
this.beefNoodles = beefnoodles;
}
@Override
public void run() {
int i=1;
while (true){
synchronized (beefNoodles){
if (beefNoodles.flag==false){
try {
beefNoodles.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("客户"+Thread.currentThread().getName()+"-->"+i+"正在吃"
+beefNoodles.tang+beefNoodles.kouwei+"的牛肉面");
beefNoodles.flag =false;
beefNoodles.notify();
System.out.println("客户"+Thread.currentThread().getName()+"-->"+i+"已经吃完了"
+beefNoodles.tang+beefNoodles.kouwei+"的牛肉面"+",面馆开始继续制作牛肉面");
i++;
System.out.println("----------------------------------");
}
}
}
}
public class Test {
public static void main(String[] args) {
BeefNoodles beefNoodles = new BeefNoodles();
new BeefNoodlesRestaurant(beefNoodles).start();
new KeHu(beefNoodles).start();
}
}
测试结果: 
|