两步帮你搞定多线程之最后一步
Posted 爱敲代码的小高
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了两步帮你搞定多线程之最后一步相关的知识,希望对你有一定的参考价值。
目录
绪论
上期介绍了多线程的概念、优势、创建方法以及几个常用的关键字。有了之前的基础过后,我们来讨论讨论线程安全问题以及其他线程进阶知识。
一:线程安全问题
1.1 提出问题
首先,给大家看一下这个代码:
public class yy1 {
private static class Counter {
private long n = 0;
public void increment() {
n++;
}
public void decrement() {
n--;
}
public long value() {
return n;
}
}
public static void main(String[] args) throws InterruptedException {
final int COUNT = 1000_0000;
Counter counter = new Counter();
Thread thread = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
counter.increment();
}
}, "李四");
thread.start();
for (int i = 0; i < COUNT; i++) {
counter.decrement();
}
thread.join();
// 期望最终结果应该是 0
System.out.println(counter.value());
}
}
大家看结果:
大家观察下是否适用多线程的现象是否一致?同时尝试思考下为什么会有这样的现象发生呢?
想给出一个线程安全的确切定义是复杂的,但我们可以这样认为:
如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。
1.2 不安全的原因
1.2.1 原子性
举个简单的例子,当我i们买票的时候,如果车站剩余票数大于0,就可以买。反之,买完一张票后,车站的票数也会自动减一。假设出现这种情况,两个人同时来买票,只剩最后一张票,前面那个人把最后一张票买了,但是短时间内票数还没减一也就是清零,这时另外一个人看到还有一张票,于是提交订单,但是其实已经没有多余的票了,那么问题就来了。这时我们引入原子性:
1.2.2 代码“优化”
二:如何解决线程不安全的问题
2.1 通过synchronized关键字
public class SynchronizedDemo {public synchronized static void methond() {}public static void main(String[] args) {method();// 进入方法会锁 SynchronizedDemo.class 指向对象中的锁;出方法会释放SynchronizedDemo.class 指向的对象中的锁}}
锁的 SynchronizedDemo 类的对象
public class SynchronizedDemo {public synchronized static void methond() {}public static void main(String[] args) {method();// 进入方法会锁 SynchronizedDemo.class 指向对象中的锁;出方法会释放SynchronizedDemo.class 指向的对象中的锁}}
明确锁的对象
public class SynchronizedDemo {public void methond() {// 进入代码块会锁 this 指向对象中的锁;出代码块会释放 this 指向的对象中的锁synchronized (this) {}}public static void main(String[] args) {SynchronizedDemo demo = new SynchronizedDemo();demo.method();}}
public class SynchronizedDemo {public void methond() {// 进入代码块会锁 SynchronizedDemo.class 指向对象中的锁;出代码块会释放SynchronizedDemo.class 指向的对象中的锁synchronized (SynchronizedDemo.class) {}}public static void main(String[] args) {SynchronizedDemo demo = new SynchronizedDemo();demo.method();}}
2.2 volatile
这里提一下volatile:
首先,被volatile关键字修饰的变量,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步
三:wait和notify关键字
3.1 wait方法
public static void main(String[] args) throws InterruptedException {Object object = new Object();synchronized (object) {System.out.println(" 等待中 ...");object.wait();System.out.println(" 等待已过 ...");}System.out.println("main 方法结束 ...");}
3.2 notify方法
class MyThread implements Runnable {
private boolean flag;
private Object obj;
public MyThread(boolean flag, Object obj) {
super();
this.flag = flag;
this.obj = obj;
}
public void waitMethod() {
synchronized (obj) {
try {
while (true) {
System.out.println("wait()方法开始.. " +
Thread.currentThread().getName());
obj.wait();
System.out.println("wait()方法结束.. " +
Thread.currentThread().getName());
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void notifyMethod() {
synchronized (obj) {
try {
System.out.println("notifyAll()方法开始.. " +
Thread.currentThread().getName());
obj.notifyAll();
System.out.println("notifyAll()方法结束.. " +
Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
if (flag) {
this.waitMethod();
} else {
this.notifyMethod();
}
}
}
public class TestThread {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
MyThread waitThread1 = new MyThread(true, object);
MyThread waitThread2 = new MyThread(true, object);
MyThread waitThread3 = new MyThread(true, object);
MyThread notifyThread = new MyThread(false, object);
Thread thread1 = new Thread(waitThread1, "wait线程A");
Thread thread2 = new Thread(waitThread2, "wait线程B");
Thread thread3 = new Thread(waitThread3, "wait线程C");
Thread thread4 = new Thread(notifyThread, "notify线程");
thread1.start();
thread2.start();
thread3.start();
Thread.sleep(1000);
thread4.start();
System.out.println("main方法结束!!");
}
}
3.3 wait和sleep对比(面试常考)
四:多线程案例
4.1 饿汉模式单线程
class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
4.2 懒汉模式单线程
class Singleton {private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}
4.3 懒汉模式多线程低性能版
class Singleton {private static Singleton instance = null;private Singleton() {}public synchronized static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}
4.4懒汉模式-多线程版-二次判断-性能高
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;}}
总结
多线程的部分暂时分享到这里,但其实还有很多没有没有涉及 ,等日后深刻理解后再来分享,码文不易,多谢大家支持,感激不尽!
以上是关于两步帮你搞定多线程之最后一步的主要内容,如果未能解决你的问题,请参考以下文章
深度分析:面试90%被问到的多线程创建线程线程状态线程安全,一次性帮你全搞定!
Java多线程从基础到并发模型统统帮你搞定!看这一篇就够了!
java面试必问:多线程的实现和同步机制,一文帮你搞定多线程编程