java内存模型(jmm)
java内存模型分为两大类型即,主内存和本地内存 1.主内存 也就是主进程所占用的内存 2.本地内存 线程中开辟的属于线程自己的内存,其中存放着全局变量的副本数据 也就是全局共享的数据在主内存中的,线程中复制一份放入自己的本地内存,线程执行结束后将其变动刷新至主内存。 这也就是为什么多线程会有线程安全的问题所在。 多个线程同时做了修改,都去刷新主内存,会造成结果和实际不一致。
volatile关键字
其功能为多线程可见。 怎么说呢?实际上就是当线程在本地内存中修改了被volatile修改的变量后,会立刻将其值刷新至主内存中,其他线程 也会马上得到这个修改的结果 实际看上去就像线程在直接修改主线程中的这个变量一样。 还有就是防止指令重新排序 代码自上而下执行,这是常识,但是,计算机执行时,可能会将,没有依赖关系的代码指令的执行顺序打乱,但一般不会 影响结果。 如:int a=1;int b=1;这时无论怎么变化这两条指令,都没有影响。 如果再来一条int c=a+b;就会相互之间有了依赖关系,计算机就不会将其重新排序 指令并不是代码行,指令是原子的,通过javap命令可以看到一行代码编译出来的指令,当然,像int i=1;这样的代码行也 是原子操作。 而对于那些比较隐晦的,指令重拍可能会引发问题得数据,volatile关键字可以指定其对应的代码不会参与重新排序
案例
package live.yanxiaohui; /** * @Description 仿真消息的生产和消费(多线程之间的通讯),即来一个消息,消费一条消息 * @ImapBox https://blog.csdn.net/yxh13521338301 * @Author: yanxh<br> * @Date 2020-05-15 11:08<br> * @Version 1.0<br> */ public class Test2 { public static void main(String[] args) { Money money = new Money(); new Task1(money).start(); new Task2(money).start(); } } class Task1 extends Thread{ private Money money; private int count; public Task1(Money money) { this.money = money; } @Override public void run() { while (true){ if(count % 2 ==0){ money.name = "马云"; money.componet = "阿里"; }else { money.name = "马化腾"; money.componet = "腾讯"; } count++; } } } class Task2 extends Thread{ private Money money; public Task2(Money money) { this.money = money; } @Override public void run() { while (true){ System.out.println(money.name + "," + money.componet); } } } class Money{ public String name; public String componet; }
首先我们运行程序,会发现数据错乱的现象
这是由于多个线程访问全局共享数据造成的线程安全问题。 说人话: 写入的线程做了修改,将本地内存改动的数据要同步至主内存,刚好同步一半,这时被挂起, 读线程从主内存读取数据就是一个错误的数据。
那么加锁如何?
对共享的money对象加锁。关键代码如下 写入操作: synchronized (money){ if(count % 2 ==0){ money.name = "马云"; money.componet = "阿里"; }else { money.name = "马化腾"; money.componet = "腾讯"; } } 读操作: synchronized (money){ System.out.println(money.name + "," + money.componet); }
一定会有读者有疑问:读为什么还要加锁?
此处对数据的修改和读取是两个变量,这两个变量需要保证其原子性,否则依旧是可能出现线程安全的 比如:如果读不加锁,那么其读到第一个属性值后,被挂起,写入的线程做了修改,读线程继续执行后 就会出现第二个属性和第一个属性不同步的现象
运行程序
数据是保证一致性了,但依然不满足我们消费的机制,所以需要再优化下
我们可以使用wait和notify配合进行多个线程之间对同一共享的全局数据进行通讯 package live.yanxiaohui; /** * @Description 仿真消息的生产和消费(多线程之间的通讯),即来一个消息,消费一条消息 * @ImapBox https://blog.csdn.net/yxh13521338301 * @Author: yanxh<br> * @Date 2020-05-15 11:08<br> * @Version 1.0<br> */ public class Test2 { public static void main(String[] args) { Money money = new Money(); new Task1(money).start(); new Task2(money).start(); } } class Task1 extends Thread{ private Money money; private int count; public Task1(Money money) { this.money = money; } @Override public void run() { try{ while (true){ synchronized (money){ // 方便查看效果,设置阻塞时间 Thread.sleep(100); if(!money.success){ // 交出对象的使用权,等待消息消费 money.wait(); } if(count % 2 ==0){ money.name = "马云"; money.componet = "阿里"; }else { money.name = "马化腾"; money.componet = "腾讯"; } money.success = false; // 唤醒此对象所有等待的线程 money.notify(); } count++; } } catch (Exception e){ e.printStackTrace(); } } } class Task2 extends Thread{ private Money money; public Task2(Money money) { this.money = money; } @Override public void run() { try { while (true){ synchronized (money){ if(money.success){ // 交出对象的使用权,等待消息生产 money.wait(); } System.out.println(money.name + "," + money.componet); money.success = true; // 唤醒此对象所有等待的线程 money.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } class Money{ public String name; public String componet; // true表示可以写,false表示可以读 public boolean success; }
由此可以看出,线程执行是抢夺CPU分配权的,和主线程代码的执行顺序没太大关系
我们可以使用lock锁,去替代sync
package live.yanxiaohui; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @Description 仿真消息的生产和消费(多线程之间的通讯),即来一个消息,消费一条消息 * @ImapBox https://blog.csdn.net/yxh13521338301 * @Author: yanxh<br> * @Date 2020-05-15 11:08<br> * @Version 1.0<br> */ public class Test3 { public static void main(String[] args) { Money money = new Money(); new Task1(money).start(); new Task2(money).start(); } } class Task1 extends Thread { private Money money; private int count; public Task1(Money money) { this.money = money; } @Override public void run() { while (true) { try { // 对象上锁 money.lock.lock(); // 方便查看效果,设置阻塞时间 Thread.sleep(100); if (!money.success) { // 交出对象的使用权,等待消息消费 money.condition.await(); } if (count % 2 == 0) { money.name = "马云"; money.componet = "阿里"; } else { money.name = "马化腾"; money.componet = "腾讯"; } money.success = false; // 唤醒此对象所有等待的线程 money.condition.signal(); count++; } catch (Exception e) { e.printStackTrace(); } finally { // 释放锁 money.lock.unlock(); } } } } class Task2 extends Thread { private Money money; public Task2(Money money) { this.money = money; } @Override public void run() { try { while (true) { // 对象上锁 money.lock.lock(); if (money.success) { // 交出对象的使用权,等待消息生产 money.condition.await(); } System.out.println(money.name + "," + money.componet); money.success = true; // 唤醒此对象所有等待的线程 money.condition.signal(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { // 释放锁 money.lock.unlock(); } } } class Money { public String name; public String componet; // true表示可以写,false表示可以读 public boolean success; public Lock lock = new ReentrantLock(); public Condition condition = lock.newCondition(); }
运行结果也是相同
总结
1.多线程之间实现通讯基于对全局共享数据的锁定,即多个线程都必须去争夺锁,获取锁之后依照业务进行对应的等待或运行。 2.wait和notify必须要配合synchronized使用, 且必须要在同步代码块中执行 3.lock锁因为是手动添加的,所以它可控,实际开发中可用到的最多 4.wait底层是将对象的锁释放掉,自身线程进入阻塞等待状态,直到被对象的notify唤醒 5.sleep只是线程的定时阻塞,不会释放锁 6.volatile指定全局共享变量在内存模型中线程间数据的可见性 7.volatile还可以防止指令重新排序
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算