asp.net开发网站和优势,展示型网站源码,网页设计实验报告html,有哪些做品牌特卖的网站文章目录 前言一、volatile关键字volatile 能保证内存可见性 二、wait 和 notify2.1 wait()方法2.2 notify()方法2.3 notifyAll()方法2.4 wait 和 sleep 的对比#xff08;面试题#xff09; 三、多线程案例单例模式 四、总结-保证线程安全的思路五、对比线程和进程总结 前言… 文章目录 前言一、volatile关键字volatile 能保证内存可见性 二、wait 和 notify2.1 wait()方法2.2 notify()方法2.3 notifyAll()方法2.4 wait 和 sleep 的对比面试题 三、多线程案例单例模式 四、总结-保证线程安全的思路五、对比线程和进程总结 前言
这篇博客博主开始讲述多线程的下部上部在博主的上一篇博客需要的柚柚们可以看看~链接在这里点击即可。 提示以下是本篇文章正文内容
一、volatile关键字
volatile 能保证内存可见性 代码在写入 volatile 修饰的变量的时候 改变线程工作内存中volatile变量副本的值将改变后的副本的值从工作内存刷新到主内存 代码在读取 volatile 修饰的变量的时候从主内存中读取volatile变量的最新值到线程的工作内存中从工作内存中读取volatile变量的副本 代码示例 在这个代码中
创建两个线程 t1 和 t2t1 中包含⼀个循环, 这个循环以 flag 0 为循环条件.t2 中从键盘读入⼀个整数, 并把这个整数赋值给 flag.预期当用户输入非 0 的值的时候, t1 线程结束
static class Counter {public int flag 0;
}
public static void main(String[] args) {Counter counter new Counter();Thread t1 new Thread(() - {while (counter.flag 0) {}System.out.println(循环结束!);});Thread t2 new Thread(() - {Scanner scanner new Scanner(System.in);System.out.println(输⼊⼀个整数:);counter.flag scanner.nextInt();});t1.start();t2.start();}t1读的是自己的工作内存中的内容当 t2 对 flag 变量进行修改, 此时 t1 感知不到 flag 的变化.
static class Counter {public volatile int flag 0;
}此时当用户输入非零值时t2将flag的值刷回内存然后t1再从内存中重新读取flag的值我们可以发现t1线程循环能够立即结束。
二、wait 和 notify
由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知. 但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序.
完成这个协调工作, 主要涉及到三个方法
wait() / wait(long timeout): 让当前线程进入等待状态notify() / notifyAll(): 唤醒在当前对象上等待的线程
注 wait, notify, notifyAll 都是 Object 类的方法.
2.1 wait()方法
wait 做的事情:
使当前执行代码的线程进行等待. (把线程放到等待队列中)释放当前的锁满足⼀定条件时被唤醒, 重新尝试获取这个锁
注wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.
wait 结束等待的条件:
其他线程调用该对象的 notify/notifyAll 方法.wait 等待时间超时 (wait 方法提供⼀个带有 timeout 参数的版本, 来指定等待时间).其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常
代码示例
public static void main(String[] args) throws InterruptedException {Object object new Object();synchronized (object) {System.out.println(等待中);object.wait();System.out.println(等待结束);}
}2.2 notify()方法
notify 方法是唤醒等待的线程
方法notify()也要在同步方法或同步块中调用该方法是用来通知那些可能等待该对象的对象锁的其它线程对其发出通知notify并使它们重新获取该对象的对象锁。如果有多个线程等待则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 “先来后到”)在notify()方法后当前线程不会马上释放该对象锁要等到执行notify()方法的线程将程序执行 完也就是退出同步代码块之后才会释放对象锁.
代码示例
创建 WaitTask 类, 对应⼀个线程, run 内部循环调用 wait.创建 NotifyTask 类, 对应另⼀个线程, 在 run 内部调用一次 notify注意, WaitTask 和 NotifyTask 内部持有同⼀个 Object locker. WaitTask 和 NotifyTask 要想配合就需要搭配同⼀个 Object.
static class WaitTask implements Runnable {private Object locker;public WaitTask(Object locker) {this.locker locker;}Overridepublic void run() {synchronized (locker) {while (true) {try {System.out.println(wait 开始);locker.wait();System.out.println(wait 结束);} catch (InterruptedException e) {e.printStackTrace();}}}}
}static class NotifyTask implements Runnable {private Object locker;public NotifyTask(Object locker) {this.locker locker;}Overridepublic void run() {synchronized (locker) {System.out.println(notify 开始);locker.notify();System.out.println(notify 结束);}}
}public static void main(String[] args) throws InterruptedException {Object locker new Object();Thread t1 new Thread(new WaitTask(locker));Thread t2 new Thread(new NotifyTask(locker));t1.start();Thread.sleep(1000);t2.start();
}2.3 notifyAll()方法
notify方法只是唤醒某⼀个等待线程. 使用notifyAll方法可以⼀次唤醒所有的等待线程(同一个对象锁的所有等待线程)唤醒之后需要重新竞争锁。
2.4 wait 和 sleep 的对比面试题
其实理论上 wait 和 sleep 完全是没有可比性的因为⼀个是用于线程之间的通信的⼀个是让线程阻塞⼀段时间唯⼀的相同点就是都可以让线程放弃执行⼀段时间. 不同点
wait 需要搭配 synchronized 使用 sleep 不需要。wait 是 Object 的方法 sleep 是 Thread 的静态方法。
三、多线程案例
单例模式
单例模式是校招中最常考的设计模式之⼀单例模式能保证某个类在程序中只存在唯⼀⼀份实例, 而不会创建出多个实例单例模式具体的实现方式有很多. 最常见的是 “饿汉” 和 “懒汉” 两种。
那我们要如何保证一个程序中对象是单例的用什么方法去保证? 1.人为口头约束大家不要new这个对象我给大家提供一个方法这个方法可以返回一个单例的对象。但是显然是不可取的人和人之间的信任不太靠谱 2.通过语言自身的语法约束限制一个类只能被实例化一个对象。把限制的过程交给程序程序写死了就按照写的逻辑执行不会改变
实现过程 1.要实现单例类只需要定义一个static修饰的变量就可以保证这个变量全局唯一单例
public class Singleton {private static Singleton instance new Singleton();public Singleton getInstance() {return instance;}
}
class Test{public static void main(String[] args){Singleton instance1 new Singleton();System.out.println(instance1.getInstance());Singleton instance2 new Singleton();System.out.println(instance2.getInstance());Singleton instance3 new Singleton();System.out.println(instance3.getInstance());}
}private防止外部对这个变量修改就会导致instance不唯一static保证全局唯一
输出 从输出可以看出我们似乎实现啦单例但是还是有不足的
2.既然是单例就不想然外部去new这个对象虽然返回的是同一个对象已经实现了单例但代码书写上有歧义所以我们要将构造方法私有化但是私有化之后外界就获取不到单例类了所以我们要给getInstance方法前面加上static我们就就可以通过类名.方法名的方式调用。修改之后的代码为
public class Singleton {private static Singleton instance new Singleton();private Singleton(){}public static Singleton getInstance() {return instance;}
}
class Test{public static void main(String[] args){Singleton instance1 Singleton.getInstance();System.out.println(instance1.getInstance());Singleton instance2 Singleton.getInstance();System.out.println(instance2.getInstance());Singleton instance3 Singleton.getInstance();System.out.println(instance3.getInstance());}
}输出 现在我们就真真正正地实现单例啦~
饿汉模式类加载的同时, 创建实例
public class Singleton {private static Singleton instance new Singleton();private Singleton(){}public static Singleton getInstance() {return instance;}
}懒汉模式-单线程版类加载的时候不创建实例. 第⼀次使用的时候才创建实例
public class Singleton {private static Singleton instance null;private Singleton(){}public static Singleton getInstance() {if(instance null) {instance new Singleton();}return instance;}
}懒汉模式-多线程版上面的懒汉模式对于多线程的实现是线程不安全的
线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致创建出多个实例⼀旦实例已经创建好了, 后面再多线程环境调用getInstance 就不再有线程安全问题了(不再修改instance 了)
class Singleton {private static Singleton instance null;private Singleton() {}public synchronized static Singleton getInstance() {if (instance null) {instance new Singleton();}return instance;}
}懒汉模式-多线程版改进以下代码在加锁的基础上, 做出了进⼀步改动 • 使用双重 if 判定, 降低锁竞争的频率. • 给 instance 加上了 volatile.
class Singleton {private static volatile Singleton instance null;private Singleton() {}public static Singleton getInstance() {if (instance null) {synchronized (Singleton.class) {if (instance null) {instance new Singleton();}}}return instance;}
}四、总结-保证线程安全的思路
使用没有共享资源的模型适用共享资源只读不写的模型 a. 不需要写共享资源的模型 b. 使用不可变对象直面线程安全重点 a. 保证原子性 b. 保证顺序性 c. 保证可见性
五、对比线程和进程
5.1 线程的优点
创建⼀个新线程的代价要比创建⼀个新进程小得多与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多线程占用的资源要比进程少很多能充分利用多处理器的可并行数量在等待慢速I/O操作结束的同时程序可执行其他的计算任务计算密集型应用为了能在多处理器系统上运行将计算分解到多个线程中实现I/O密集型应用为了提高性能将I/O操作重叠。线程可以同时等待不同的I/O操作
5.2 进程与线程的区别
进程是系统进行资源分配和调度的⼀个独立单位线程是程序执行的最小单位。进程有自己的内存地址空间线程只独享指令流执行的必要资源如寄存器和栈。由于同⼀进程的各线程间共享内存和文件资源可以不通过内核进行直接通信。线程的创建、切换及终止效率更高。
总结
这篇博客博主后续还会更新更多案例有兴趣地柚柚们可以点赞收藏方便后续观看~