一、什么是线程安全问题
为什么有线程安全问题?
当多个线程同时共享同一个全局变量或静态变量,做写的操作(修改变量值)时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作时不会发生数据冲突问题。
案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。
/** * 需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。 * Created by yz on 2018/04/01. */ public class ThreadDemo { public static void main(String[] args) { // t1 t2同时共享同一变量trainCount ThreadTrain threadTrain = new ThreadTrain(); Thread t1 = new Thread(threadTrain, "窗口1"); Thread t2 = new Thread(threadTrain, "窗口2"); (); (); } } // 售票窗口 class ThreadTrain implements Runnable{ // 总共有100张火车票 private int trainCount = 100; public void run() { while (trainCount > 0){ try { // 休眠50秒 Thread.sleep(50); } catch (InterruptedException e) { (); } // 出售火车票 sale(); } } // 卖票方法 public void sale(){ System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票"); trainCount--; } }
运行结果:
原因解析:
卖票方法加判断,不能百分百解决问题
// 卖票方法 public void sale(){ if(trainCount > 0){ System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票"); trainCount--; } }
原因解析:
多个线程共享同一个局部变量,会发生线程安全问题吗?不会
解决办法:
synchronized --- 自动锁
lock --- jdk1.5并发包 --- 手动锁
线程之间如何同步?
同步是保证数据原子性,原子性就是数据不能受到其他线程干扰。
二、使用同步代码块解决线程安全问题
什么地方需要考虑加锁?
考虑在真正产生共享同一个全局变量的时候使用,不要用synchronized去包裹整个代码。
// 卖票方法 public void sale(){ // 同步代码块 synchronized 包裹需要线程安全的问题。 synchronized (object){ if(trainCount > 0){ System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票"); trainCount--; } } }什么是同步代码块?
答:就是将可能会发生线程安全问题的代码,给包裹起来
synchronized(对象){ // 这个对象可以为任意对象
可能会发生线程冲突的问题
}
对象如同锁,持有锁的线程可以在同步块中执行,没持有锁的线程,即使获取cup的执行权,也进不去。
使用synchronized必须有一些条件:
1.必须要有两个或者两个以上的线程需要发生同步。
2.多个线程想同步,必须使用同一把锁
3.保证只有一个线程进行执行
synchronized原理:
1.首先有一个线程已经拿到了锁,其他线程已经有cup执行权,一直排队,等待释放锁。
2.锁是在什么时候释放?代码执行完毕或者程序抛出异常都会被释放掉。
3.锁已经被释放掉的话,其他线程开始进行抢锁(资源竞争),谁抢到谁进入同步中去,其他线程继续等待。
好处:解决了多线程的安全问题
弊端:效率非常低,多个线程需要判断锁,比较消耗资源,抢锁的资源。
同步是保证多个线程之间共享同一个全局变量数据安全问题,保证数据原子性。
synchronized 只能有一个线程进行执行(就像厕所只有一个坑,好多人等着上厕所,谁进入厕所谁上锁,其他人在外等待),这个线程不释放锁的话,其他线程就一直等,就会产生死锁问题。
三、同步函数使用、this锁
同步函数
什么是同步函数?
答:在方法上加上synchronized进行修饰 称为同步函数。
// 卖票方法 public synchronized void sale(){ if(trainCount > 0){ System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票"); trainCount--; } }同步函数使用的是什么锁?this锁
怎么证明同步函数使用的是this锁?
两个线程之间实现同步,一个线程使用this锁同步代码块,一个线程使用同步函数,这两个线程如果同步,说明同步函数使用的是this锁。
/** * 需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。 * Created by yz on 2018/04/01. */ public class ThreadDemo { public static void main(String[] args) throws InterruptedException { // t1 t2同时共享同一变量trainCount ThreadTrain threadTrain = new ThreadTrain(); Thread t1 = new Thread(threadTrain, "窗口1"); Thread t2 = new Thread(threadTrain, "窗口2"); (); Thread.sleep(40); threadTrain.flg = false; (); } } // 售票窗口 class ThreadTrain implements Runnable{ // 总共有100张火车票 private int trainCount = 100; public boolean flg = true; public void run() { if(flg){ while (trainCount > 0) { try { // 休眠50秒 Thread.sleep(50); } catch (InterruptedException e) { (); } // 执行同步代码块 this锁 sale1(); } }else { // 执行同步函数 while (trainCount > 0){ try { // 休眠50秒 Thread.sleep(50); } catch (InterruptedException e) { (); } // 执行同步函数 sale(); } } } // 卖票方法 同步函数 public synchronized void sale(){ if(trainCount > 0){ System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票"); trainCount--; } } // 卖票方法 public void sale1(){ // 同步代码块 synchronized 包裹需要线程安全的问题。this锁 synchronized (this){ if(trainCount > 0){ System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票"); trainCount--; } } } }
面试问题:
一个线程使用同步函数,另一个线程使用同步代码块this锁,能够同步吗?可以同步。
一个线程使用同步函数,另一个线程使用同步代码块(非this锁),能够同步吗?不能同步。this锁 synchronized(参数)只要是任何一个类型都可以把它叫做锁
public void sale(){ // 同步代码块 synchronized 包裹需要线程安全的问题。 synchronized (this){ if(trainCount > 0){ System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票"); trainCount--; } } }
四、静态同步代码块
同步函数分为:
1.非静态同步函数:public synchronized void sale(){}
2.静态(static关键字)同步函数,还会用this锁吗?public static synchronized void sale(){}
静态同步函数不使用this锁,不同步。
当一个变量被static修饰的话存放在内存永久区,当class文件被加载的时候会被初始化。
private static int trainCount = 100;静态同步函数与当前字节码文件一起使用可以同步。
// 卖票方法 同步函数 public static synchronized void sale(){ if(trainCount > 0){ System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票"); trainCount--; } } // 卖票方法 public void sale1(){ // 同步代码块 synchronized 包裹需要线程安全的问题。this锁 synchronized (ThreadTrain.class){ if(trainCount > 0){ System.out.println(Thread.currentThread().getName()+",出售第"+(100-trainCount+1)+"张票"); trainCount--; } } }
两个线程,一个线程使用同步函数,另一个线程使用静态同步函数能实现同步吗?
不能,同步函数使用this锁,静态同步函数使用当前字节码文件(字节码文件就是当前class文件)。
加锁保证同步,同步保证数据安全问题、原子问题。
使用synchronized、lock都属于单个jvm中同步。分布式锁、高并发和jvm同步是没有任何关系,是和集群有关系。
五、多线程死锁
什么是多线程死锁?
答:同步中嵌套同步,导致锁无法释放
六、Java内存模型
多线程三大特性
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么都不执行。
可见性:当多个线程访问一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:程序执行的顺序按照代码先后顺序执行。
java内存模型和java内存结构区别?
java内存模型属于多线程可见性 jmm
java内存结构是jvm内存分配
七、Volatile可见性
什么是Volatile
Volatile 关键字的作用是变量在多个线程之间可见,但不保证原子性。
线程可见性:线程1本地内存的变量值发生改变之后,立马通知给另一个线程。
/** * Volatile可见性 * Created by yz on 2018/4/2. */ public class ThreadVolatile { public static void main(String[] args) throws InterruptedException { ThreadVolatileDemo td = new ThreadVolatileDemo(); (); Thread.sleep(3000); // 主线程修改了共享全局变量为false (false); System.out.println("flg值已经修改为false!"); Thread.sleep(1000); System.out.println(td.flg); } } class ThreadVolatileDemo extends Thread{ public boolean flg = true; @Override public void run() { System.out.println("子线程开始执行..."); while(flg){ // ThreadVolatileDemo线程依然读取的是本地线程内存值 } System.out.println("子线程执行结束..."); } public void setFlg(boolean flg){ this.flg = flg; } }
问题,当flg值修改为false后,程序一直停止不了
解决,使用volatile关键字强制刷新主内存
public volatile boolean flg = true;
如果没有添加休眠时间,在不添加volatile关键字时,修改flg值后,flg值是实时修改,程序执行完毕结束。
主线程添加休眠时间后,在不添加volatile关键字时,修改flg值后,不会及时通知给子线程。
八、AtomicInteger原子类
创建10个线程,每个线程执行1000次,共享count变量。
/** * Volatile非原子性 * Created by yz on 2018/4/2. */ public class VolatileNoAtomic extends Thread{ // 需要10个线程同时共享count static修饰关键字,存放在静态区,只会存放一次,所有线程都会共享 private volatile static int count = 0; @Override public void run() { for (int i = 0; i < 1000; i++) { count++; } System.out.println(getName()+","+count); } public static void main(String[] args) { // 创建10个线程 VolatileNoAtomic[] vaList = new VolatileNoAtomic[10]; for (int i = 0; i < vaList.length; i++) { vaList[i] = new VolatileNoAtomic(); } for (int i = 0; i < vaList.length; i++) { vaList[i].start(); } } }
加上volatile关键字数据也不准确
java并发包: jdk1.5
AtomicInteger原子类,用于计数
/** * AtomicInteger原子类 * Created by yz on 2018/4/2. */ public class VolatileNoAtomic extends Thread{ // 需要10个线程同时共享count static修饰关键字,存放在静态区,只会存放一次,所有线程都会共享 //private volatile static int count = 0; private static AtomicInteger count = new AtomicInteger(0); @Override public void run() { for (int i = 0; i < 1000; i++) { //count++; // 以原子方式将当前值加 1 count.incrementAndGet(); } System.out.println(getName()+","+count.get()); } public static void main(String[] args) { // 创建10个线程 VolatileNoAtomic[] vaList = new VolatileNoAtomic[10]; for (int i = 0; i < vaList.length; i++) { vaList[i] = new VolatileNoAtomic(); } for (int i = 0; i < vaList.length; i++) { vaList[i].start(); } } }
九、ThreadLock原理剖析
import ; /** * Created by yz on 2018/04/02. */ public class ThreadLocal01 { public static void main(String[] args) { // 怎样不共享ResNumber,每个线程都用自己的ResNumber,不使用new // ResNumber resNumber1 = new ResNumber(); // ResNumber resNumber2 = new ResNumber(); // ResNumber resNumber3 = new ResNumber(); ResNumber resNumber = new ResNumber(); LocalThreadDemo t1 = new LocalThreadDemo(resNumber); LocalThreadDemo t2 = new LocalThreadDemo(resNumber); LocalThreadDemo t3 = new LocalThreadDemo(resNumber); (); (); (); } } /** * 使用ThreadLocal , ResNumber不共享,在每个线程中使用,互不影响 */ class ResNumber{ public int count = 0; public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){ protected Integer initialValue(){ return 0; } }; public String getNumber(){ count = threadLocal.get()+1; threadLocal.set(count); return count+""; } // ThreadLocal get set伪代码 Map<Object,Object> map; public void set(Integer count){ // () 获取当前线程的引用 map.put(Thread.currentThread(),count); } public String get(){ return (String) map.get(Thread.currentThread()); } } class LocalThreadDemo extends Thread{ private ResNumber resNumber; public LocalThreadDemo(ResNumber resNumber){ this.resNumber = resNumber; } @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(getName()+","+resNumber.getNumber()); } } }
多线程并发同步解决方案
业务场景1:
业务需求1:假如现在有20个人去售票厅窗口买票,但是窗口只有2个,那么同时能够买票的只能有2个人,当2个人中任意1个人买完票离开窗口之后,等待的18个人中又会有一个人可以占用窗口买票。
真实需求:控制并发数为2拆解转化业务需求:
人=线程
2个窗口=资源
在窗口买票=表示线程正在执行
离开售票窗口=线程执行完毕
等待买票=线程阻塞,不能执行
解决方案:信号量 Semaphore
应用场景:用于流量控制,限流
代码:
/**
* 信号量 Semaphore
* Created by yz on 2018/3/4.
*/
public class SemaphoreDemo {
class MyTask implements Runnable{
private Semaphore semaphore; // 信号量
private int user; // 第几个用户
public MyTask(Semaphore semaphore, int user) {
= semaphore;
= user;
}
@Override
public void run() {
try {
// 获取信号量许可,才能占用窗口
();
// 运行到这里说明获取到了许可,可以去买票了
("用户"+ user + "进入窗口,准备买票...");
((long)()*10000); // 模拟买票时间
("用户"+ user + "买票完成,准备离开...");
((long)()*10000);
("用户"+ user + "离开售票窗口...");
// 释放信号量许可证
();
} catch (InterruptedException e) {
();
}
}
}
private void execute(){
// 定义窗口个数
final Semaphore s = new Semaphore(2);
// 线程池
ExecutorService threadPool = ();
// 模拟20个用户
for (int i = 0; i < 20; i++) {
(new MyTask(s,(i+1)));
}
// 关闭线程池
();
}
public static void main(String[] args) {
SemaphoreDemo semaphoreDemo = new SemaphoreDemo();
();
}
}
业务场景2:
业务需求2:公司周末组织去聚餐,首先各自从家里出发到聚餐地点,当所有人全部到齐之后,才开始吃饭 如果人员未到齐,到的人就只能等待在那里,直到所有人都到齐之后才能吃饭或者做后面的事情。
解决方案:同步屏障 CyclicBarrier
应用场景:用于多线程计算数据,最后合并计算结果,例如多个老师打分,最后合并算平均分
代码:
/**
* 同步屏障 CyclicBarrier
* Created by yz on 2018/3/4.
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
final CyclicBarrier cb = new CyclicBarrier(3, new Runnable() {
@Override
public void run() {
// 在吃饭之前做点别的事情
("人员全部到齐了,拍照留念...");
try {
(3000);
} catch (InterruptedException e) {
();
}
}
});
// 线程池
ExecutorService threadPool = ();
// 模拟3个用户
for (int i = 0; i < 3; i++) {
final int user = i+1;
Runnable r = new Runnable() {
@Override
public void run() {
try {
// 模拟每个人来的时间各不一样
((long)()*10000);
(user+"到达聚餐地点,当前已有"+ (()+1) +"人到达");
// 设置屏障 等待,只有当线程都到达之后,才能往下面走
();
if(user == 3){
("人员全部到齐,开始吃饭...");
}
((long)()*20000);
(user+"吃完饭了,准备回家...");
// CyclicBarrier 可以重复使用 doSomething
} catch (Exception e) {
();
}
}
};
(r);
}
();
}
}
业务场景3:
业务需求3:假如A团伙绑了B,告诉B的家人C,需要1000万赎人,A与C达成一致意见到某一个地点交换人质,于是,A团伙和C同时到达交换地点,然后同时一手交钱一手交人质。
解决方案:Exchanger 两个线程之间进行数据交换
应用场景:用于两个线程之间交换数据,例如校对工作
代码:
/**
* Exchanger 两个线程交换数据
* Created by yz on 2018/3/4.
*/
public class ExchangerDemo {
public static void main(String[] args) {
// 定义交换器,交换String 类型的数据,当然是可以为任意类型
Exchanger<String> exchanger = new Exchanger<>();
// 定义线程池
ExecutorService threadPool = ();
// 绑架者A
(new Runnable() {
@Override
public void run() {
try {
// 准备人质
String renzhi = "B";
String money = (renzhi);
("绑架者用B交换回:"+money);
} catch (InterruptedException e) {
();
}
}
});
// 家属C
(new Runnable() {
@Override
public void run() {
try {
// 准备1000万
String money = "1000万";
String renzhi = (money);
("C用1000万交换回:"+renzhi);
} catch (InterruptedException e) {
();
}
}
});
();
}
}
业务场景4:
业务需求4:比如有一个任务A,他需要等待其他几个任务(BCD)都执行完毕之后才能执行这个任务
解决方案:ConutDownLatch 倒计时器
应用场景:可以用于模拟高并发
ConutDownLatch 与 CyclicBarrier 区别:
共同点:都能够实现线程之间的等待
不同点:
ConutDownLatch 一般用于某个线程A等待若干个其他线程执行完任务之后,它才能执行
CyclicBarrier 一般用于一组线程互相等待到某个状态,然后这一组线程在同时执行
ConutDownLatch 是不能重用的,CyclicBarrier 可以重复使用
代码:
/**
* ConutDownLatch 倒计时器
* Created by yz on 2018/3/4.
*/
public class ConutDownLatchDemo {
public static void main(String[] args) {
// 定义倒计时器
final CountDownLatch latch = new CountDownLatch(3);
// 模拟一个子任务B
new Thread(){
@Override
public void run() {
try {
// 模拟任务执行时间
((long)()*10000);
("子任务B"+().getName()+"正在执行");
((long)()*10000);
("子任务B"+().getName()+"执行完毕");
// 倒计时减掉1
();
} catch (InterruptedException e) {
();
}
}
}.start();
// 模拟一个子任务C
new Thread(){
@Override
public void run() {
try {
// 模拟任务执行时间
((long)()*10000);
("子任务C"+().getName()+"正在执行");
((long)()*10000);
("子任务C"+().getName()+"执行完毕");
// 倒计时减掉1
();
} catch (InterruptedException e) {
();
}
}
}.start();
// 模拟一个子任务D
new Thread(){
@Override
public void run() {
try {
// 模拟任务执行时间
((long)()*10000);
("子任务D"+().getName()+"正在执行");
((long)()*10000);
("子任务D"+().getName()+"执行完毕");
// 倒计时减掉1
();
} catch (InterruptedException e) {
();
}
}
}.start();
// main 线程为主任务A
("等待3个子任务执行完毕"+().getName()+"主任务才开始执行");
try {
// 等待子任务执行完毕 此时阻塞
();
("说明BCD三个子任务已经执行完毕");
// 继续执行主任务
("继续执行主任务:"+().getName());
} catch (InterruptedException e) {
();
}
}
}