java 多线程笔记二 线程安全问题
高并发下可能回出现的超卖问题
运行一下代码,模拟超卖问题,运行后会发现会出现重复编号的票
public class Test {
public static void main(String[] args) {
/**
* 模拟出售电影票
*/
PiaoThread piaoThread = new PiaoThread();
new Thread(piaoThread,"1号窗口").start();
new Thread(piaoThread,"2号窗口").start();
new Thread(piaoThread,"3号窗口").start();
new Thread(piaoThread,"4号窗口").start();
}
}
class PiaoThread implements Runnable {
//多个窗口使用的是同一个对象,所以成员变量是共享的
private int piao = 100;
@Override
public void run() {
while (true){
if (piao < 1){ break;}
System.out.println(Thread.currentThread().getName()+"卖出了第"+piao+"编号的票");
piao--;
}
}
}
同步代码块可以解决超卖问题
public class Test {
public static void main(String[] args) {
/**
* 模拟出售电影票
*/
PiaoThread piaoThread = new PiaoThread();
new Thread(piaoThread,"1号窗口").start();
new Thread(piaoThread,"2号窗口").start();
new Thread(piaoThread,"3号窗口").start();
new Thread(piaoThread,"4号窗口").start();
}
}
class PiaoThread implements Runnable {
//多个窗口使用的是同一个对象,所以成员变量是共享的
private int piao = 100;
@Override
public void run() {
while (true){
//悲观锁
// synchronized (锁对象),
// 需要是同一个对象,这里都是使用的一个对象,所以可以使用this
synchronized (this){
if (piao < 1){ break;}
System.out.println(Thread.currentThread().getName()+"卖出了第"+piao+"编号的票");
piao--;
}
}
}
}
同步方法,我们把需要同步的代码块抽取出来,idea抽取方法快捷键ctrl+alt+m
同步方法的锁对象:
如果是非静态同步方法,锁对象是this
如果是静态方法那么锁对象是该方法所在类的字节码对象(类名.class)
场景:a线程使用了同步代码块,b线程使用的同步方法,这时候ab线程需要同步那么就需要知道同步方法的锁对象是谁。
public class Test {
public static void main(String[] args) {
/**
* 模拟出售电影票
*/
PiaoThread piaoThread = new PiaoThread();
new Thread(piaoThread,"1号窗口").start();
new Thread(piaoThread,"2号窗口").start();
new Thread(piaoThread,"3号窗口").start();
new Thread(piaoThread,"4号窗口").start();
}
}
class PiaoThread implements Runnable {
//多个窗口使用的是同一个对象,所以成员变量是共享的
private int piao = 100;
@Override
public void run() {
while (true){
if (extracted()) break;
}
}
private synchronized boolean extracted() {
if (piao < 1){
return true;
}
System.out.println(Thread.currentThread().getName()+"卖出了第"+piao+"编号的票");
piao--;
return false;
}
}
lock锁解决超卖问题
public class Test {
public static void main(String[] args) {
/**
* 模拟出售电影票
*/
PiaoThread piaoThread = new PiaoThread();
new Thread(piaoThread,"1号窗口").start();
new Thread(piaoThread,"2号窗口").start();
new Thread(piaoThread,"3号窗口").start();
new Thread(piaoThread,"4号窗口").start();
}
}
class PiaoThread implements Runnable {
//多个窗口使用的是同一个对象,所以成员变量是共享的
private int piao = 100;
//创建一个lock
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();//加锁
if (piao < 1){
lock.unlock();//释放锁
break;
}
System.out.println(Thread.currentThread().getName()+"卖出了第"+piao+"编号的票");
piao--;
lock.unlock();//释放锁
}
}
}
多线程下的可见性问题
出现可见性问题的根本原因:java的内存模型(JMM),java所有共享变量都是存储在主内存中的,在线程执行的时候,线程有单独的工作内存,这个时候会把主内存中的共享变量拷贝一份到工作变量,并且对所有的变量操作都是在工作变量进行的。
复习可见性问题代码:一下代码我们的理想结果是子线程3秒后修改flag的值,然后主线程结束死循环,运行程序后发现结束不了死循环。下面主线程代码过于简单,运行速度很快,来不及去读主内存中的共享变量,所以一直读的工作内存变量。
public class Test2 {
public static void main(String[] args) {
/**
* 可见性问题
*/
MyThread myThread = new MyThread();
new Thread(myThread).start();
//主线程
while (true){
if (MyThread.flag){
System.out.println("flag为true结束主线程死循环");
break;
}
}
}
}
class MyThread implements Runnable{
static boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("修改了flag为true");
}
}
解决多线程安全问题之可见性问题方法:volatile关键字
public class Test2 {
public static void main(String[] args) {
/**
* 可见性问题
*/
MyThread myThread = new MyThread();
new Thread(myThread).start();
//主线程
while (true){
if (MyThread.flag){
System.out.println("flag为true结束主线程死循环");
break;
}
}
}
}
class MyThread implements Runnable{
//这里加上volatile关键字
static volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("修改了flag为true");
}
}
多线程安全性问题之有序性问题
多线程情况下编译器对代码重排,会产生不同的结果,引发安全性问题。
volatile关键字也可以解决有序性问题
多线程安全性问题之原子性问题
原子性问题通过原子类来解决
复现原子性问题
public class Test3 {
public static void main(String[] args) throws InterruptedException {
/**
* 模拟原子性问题
*/
MyThread3 myThread3 = new MyThread3();
new Thread(myThread3).start();
//主线程自增300000次
for (int i = 0; i < 300000; i++) {
MyThread3.i++;
}
//睡眠5秒保证两个线程300000都自增完毕
Thread.sleep(5000);
System.out.println("主线程自增300000次完毕");
//打印,这里理想结果应该是600000,
// 但是在没有处理原子性问题情况下,有时候并打不到600000次
//因为i++是3步操作,并不是原子性的,第一步是读主内存值,第二步自增,第三步写
System.out.println(MyThread3.i);
}
}
class MyThread3 implements Runnable{
static int i = 0;
@Override
public void run() {
for (int i1 = 0; i1 < 300000; i1++) {
i++;
}
System.out.println("子线程自增300000次完毕");
}
}
解决原子性问题

public class Test3 {
public static void main(String[] args) throws InterruptedException {
/**
* 模拟原子性问题
*/
MyThread3 myThread3 = new MyThread3();
new Thread(myThread3).start();
//主线程自增300000次
for (int i = 0; i < 300000; i++) {
//MyThread3.i++;
MyThread3.atomicInteger.getAndAdd(1);
}
//睡眠5秒保证两个线程300000都自增完毕
Thread.sleep(5000);
System.out.println("主线程自增300000次完毕");
//打印,这里理想结果应该是600000,
// 但是在没有处理原子性问题情况下,有时候并打不到600000次
//因为i++是3步操作,并不是原子性的,第一步是读主内存值,第二步自增,第三步写
System.out.println(MyThread3.atomicInteger.getAndAdd(1));
}
}
class MyThread3 implements Runnable{
static AtomicInteger atomicInteger = new AtomicInteger();
@Override
public void run() {
for (int i1 = 0; i1 < 300000; i1++) {
//i++;
atomicInteger.getAndAdd(1);
}
System.out.println("子线程自增300000次完毕");
}
}
原子类工作原理:CAS机制(比较并交换)
参考CAS机制是什么?_IABQL的博客-CSDN博客_cas机制
并发包也是线程安全的,
- java.util.concurrent.CopyOnWriteArrayList<E>
- java.util.concurrent.CopyOnWriteArraySet<E>
- java.util.concurrent.ConcurrentHashMap<K,V>

其他用的到的方法
- java.util.concurrent.CountDownLatch允许一个或多个线程等待其他线程执行完后再执行
- java.util.concurrent.Exchanger<V>用于【两个】线程之间的数据交换
- java.util.concurrent.Semaphore主要作用是控制线程的并发数量
- java.util.concurrent.CyclicBarrier让一组线程都达到一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被拦截的线程才会继续执行
public class Test4 {
/**
* java.util.concurrent.CyclicBarrier
* 让一组线程都达到一个屏障(同步点)时被阻塞,
* 直到最后一个线程到达屏障时,屏障才会开门,
* 所有被拦截的线程才会继续执行
*/
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(5, new Runnable() {
@Override
public void run() {
//5名员工达到会议室了,开始开会
System.out.println("开会过程中.....");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开完会");
}
});
MyThread4 myThread4 = new MyThread4(cb);
//5名员工线程
new Thread(myThread4,"员工1").start();
new Thread(myThread4,"员工2").start();
new Thread(myThread4,"员工3").start();
new Thread(myThread4,"员工4").start();
new Thread(myThread4,"员工5").start();
}
}
class MyThread4 implements Runnable{
CyclicBarrier cb;
public MyThread4() {}
public MyThread4(CyclicBarrier cb) {
this.cb = cb;
}
@Override
public void run() {
//打印某某员工到达会议室
System.out.println(Thread.currentThread().getName()+"到达了会议室");
//等待其他员工达到会议室
try {
cb.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
//会议开完离开会议室
System.out.println(Thread.currentThread().getName()+"离开了会议室");
}
}
执行结果:
员工2到达了会议室
员工5到达了会议室
员工4到达了会议室
员工3到达了会议室
员工1到达了会议室
开会过程中.....
开完会
员工1离开了会议室
员工2离开了会议室
员工4离开了会议室
员工5离开了会议室
员工3离开了会议室
多线程安全性问题之死锁
死锁产生条件:
1,有多个线程
2,有多把锁
3,有同步代码块嵌套
参考什么是死锁?死锁产生的条件?_努力撸代码的小刑的博客-CSDN博客_死锁的条件

浙公网安备 33010602011771号