1、什么是线程安全,为什么会有安全问题
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
案例:需求现在有100张火车票,有两个窗口同时抢火车票,请使用多线程模拟抢票效果。
class ThreadTrain implements Runnable {
private int count = 100;
private static Object oj = new Object();
@Override
public void run() {
while (count > 0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
sale();
}
}
private void sale() {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "票");
count--;
}
}
}
public class TreadDemo {
public static void main(String[] args) {
ThreadTrain threadTrain = new ThreadTrain();
Thread t1 = new Thread(threadTrain, "1号窗口");
Thread t2 = new Thread(threadTrain, "2号窗口");
t1.start();
t2.start();
}
}
运行结果如下:出现重复售票了。
多线程共享一个全局变量时,做写操作可能会发生数据冲突问题
2、线程安全解决办法
就上面存在的问题,我们该如何解决呢?如何解决多线程之间线程安全问题?
使用多线程之间同步synchronized(自动上锁和解锁)或使用锁lock(手动)。
将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
1、同步代码块
synchronized(同一个数据){
可能会发生线程冲突问题
}
接着上面的案例的代码,给sale()方法修改为如下就可以啦!
private void sale() {
synchronized (oj) {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "票");
count--;
}
}
}
}
2、同步函数
什么是同步函数?
就是在方法上修饰synchronized称为同步函数
把sale()方法改写如下就可以啦!
private synchronized void sale() {
if (count > 0) {
System.out.println(Thread.currentThread().getName() + "出售第" + (100 - count + 1) + "票");
count--;
}
}
结果如下:
思考:同步代码块和同步函数用的是什么锁?
同步函数的使用的锁是this。
同步函数和同步代码块的区别:
同步函数的锁是固定的this。
同步代码块的锁是任意的对象。(this,object等)
3、多线程会出现死锁,什么是多线程死锁?
同步中嵌套同步,导致锁无法释放
class ThreadTrain6 implements Runnable {
// 这是货票总票数,多个线程会同时共享资源
private int trainCount = 100;
public boolean flag = true;
private Object mutex = new Object();
@Override
public void run() {
if (flag) {
while (true) {
synchronized (mutex) {
// 锁(同步代码块)在什么时候释放? 代码执行完, 自动释放锁.
// 如果flag为true 先拿到 obj锁,在拿到this 锁、 才能执行。
// 如果flag为false先拿到this,在拿到obj锁,才能执行。
// 死锁解决办法:不要在同步中嵌套同步。
sale();
}
}
} else {
while (true) {
sale();
}
}
}
public synchronized void sale() {
synchronized (mutex) {
if (trainCount > 0) {
try {
Thread.sleep(40);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
trainCount--;
}
}
}
}
public class TreadDemo {
public static void main(String[] args) throws InterruptedException {
ThreadTrain6 threadTrain = new ThreadTrain6(); // 定义 一个实例
Thread thread1 = new Thread(threadTrain, "一号窗口");
Thread thread2 = new Thread(threadTrain, "二号窗口");
thread1.start();
Thread.sleep(40);
threadTrain.flag = false;
thread2.start();
}
}
4、多线程的三大特性
原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
有序性
程序执行的顺序按照代码的先后顺序执行。
一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。
5、Java内存模型
共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
6、volatile与synchronized区别
仅靠volatile不能保证线程的安全性。(原子性)
①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。
线程安全性包括两个方面,①可见性。②原子性。
仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性。
.
作者:程序员那些破事儿
出处: https://blog.csdn.net/wjq6940
欢迎投稿分享个人工作,生活,项目经验。
号内有各类编程教学视频资源哦!