1、什么是线程安全问题?
什么是线程安全问题?
答:当多个线程共享同一个全局变量,做写的时候,可能会受到其他线程的干扰,导致
数据有问题,这种现象叫做线程安全问题。做读的时候,不会产生线程安全问题。
什么时候会发生线程安全:
多个线程同时共享同一个全局变量,做写的操作的时候,就会发生线程安全。
多个线程共享同一个局部变量,做写的操作时候不会发生线程安全问题。
分析图:
抢票线程安全案例 下面代码会出现线程安全问题
package com.leeue;
/**
*
* @classDesc: 功能描述:(模仿抢票,查看线程安全问题)
* @author:李月
* @Version:v1.0
* @createTime:@Date 2018年6月7日 上午11:27:52
*
*/
class CreateThread implements Runnable{
int count = 100;
Object obj = new Object();
public void run() {
//出售火车票
while(count > 0){
try {
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
}
sale();
}
}
public void sale(){
System.out.println(Thread.currentThread().
getName()+"正在出售"+(100-count+1)+"张票");
count--;
}
}
public class ThreadeDemo {
public static void main(String[] args) {
CreateThread createThread = new CreateThread();
Thread t1 = new Thread(createThread,"窗口001:");
Thread t2 = new Thread(createThread,"窗口002:");
t1.start();
t2.start();
}
}
运行结果,出现线程安全问题如图:
2、使用同步代码块解决线程安全问题
线程是如何实现同步()? 保证数据的原子性
原子性:如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行。
这种特性就叫原子性
线程为什么需要实现同步?
多个线程共享同一个全局变量,有数据安全性问题,保证数据的原子性。
解决办法:
1、使用synchroized --- 自动挡
2、lock ---jdk1.5 并发包 --- 手动
线程安全问题的解决思路:
多个线程不要同时操作同一个局部变量做写的操作。
使用同步代码快 synchronized 包裹有线程安全问题的代码
使用synchroized 解决线程安全问题 代码
package com.leeue;
/**
*
* @classDesc: 功能描述:(模仿抢票,查看线程安全问题)
* @author:李月
* @Version:v1.0
* @createTime:@Date 2018年6月7日 上午11:27:52
*
*/
class CreateThread implements Runnable{
int count = 100;
Object obj = new Object();
public void run() {
//出售火车票
while(count > 0){
try {
Thread.sleep(2000);
} catch (Exception e) {
// TODO: handle exception
}
sale();
}
}
public void sale(){
synchronized (obj) {//同步代码块
if(count > 0){//这个是处理最后第100张票,防止售出第101张票
System.out.println(Thread.currentThread()
.getName()+"正在出售"+(100-count+1)+"张票");
count--;
}
}
}
}
public class ThreadeDemo {
public static void main(String[] args) {
CreateThread createThread = new CreateThread();
Thread t1 = new Thread(createThread,"窗口001:");
Thread t2 = new Thread(createThread,"窗口002:");
t1.start();
t2.start();
}
}
运行结果图:
使用synchroized锁的总结
使用synchroized锁必须要有有的一些条件:
1、必须要有两个线程以上,需要发生同步。
2、多个线程想要同步,必须使用同一把锁,如上代码的 Object
3、保证只有一个线程进行执行。
同步的原理:
1、首先一个线程拿到锁,其他线程已经有了cpu执行的,
一直排队,等待其他线程释放锁。
2、锁是什么时候释放?代码执行完毕,或者程序抛出异常的时候,锁就会被释放。
3、锁已经被释放掉的话,其他线程开始抢锁,获取锁的线程进同步区。
使用synchroized锁的缺点:
多个线程需要判断锁,较为消耗资源,效率比较低。
、锁的资源竞争。
、会产生死锁问题。
3、同步函数的使用this锁
什么是同步函数?
就是在方法上加上synchroized来修饰。
同步函数使用的什么锁?怎么证明?
使用的this锁。
要证明:使用不同的锁就行了。
同步函数使用的是this锁代码
这里写代码片
面试题:一个线程使用同步函数,另一个线程使用的事同步代码块this能够同步吗?
可以实现同步。因为同步函数使用的锁就是this锁。
一个线程使用同步函数,另一个线程使用同步代码块(非this锁)能同步吗?
不能实现同步,因为锁不一样,同步函数使用的锁是this锁。
4、静态同步函数
在方法上面,加上synchroized 叫同步函数
非静态同步函数
同步函数使用的this锁。
静态(static)同步函数使用的不是this锁,使用的字节码锁,.class
因为静态函数里面都不能调用this 咯
当一个变量被static修饰的话,存放在用就去,当class文件被加载的时候会被初始化。
记住:当一个变量被static修饰的话存放在永久区,当一个class文件被加载的你好就会被初始化。
同步和加锁。
加锁是为了同步。同步是为了保证数据的安全性,也就是是原子性。
注意:只有两个线程锁是一样的,才能实现同步
静态同步函数代码
package com.leeue;
class CreateThread4 implements Runnable {
private static int count = 100;
private Object object = new Object();// 对象锁
public boolean flag = true;
public void run() {
// 模拟抢票
if (flag) {
while (count > 0) {
synchronized (CreateThread4.class) {// synchronized 代码块
if (count > 0) {
try {
Thread.sleep(50);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第:" + (100 - count + 1) + "票");
count--;
}
}
}
} else {
while (count > 0) {
sale();
}
}
}
public static synchronized void sale() {// 窗口2 静态同步函数
if (count > 0) {
try {
Thread.sleep(50);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + "正在出售第:"
+ (100 - count + 1) + "票");
count--;
}
}
}
public class ThreadDemo04 {
public static void main(String[] args) throws InterruptedException {
CreateThread4 t = new CreateThread4();
Thread t1 = new Thread(t, "窗口1:");
t.flag = false;
Thread t2 = new Thread(t, "窗口2:");
t1.start();
/* Thread.sleep(50); */
t2.start();
}
}
两个线程,一个线程使用同步函数,另一个线程使用静态同步函数能实现同步吗?
答:不能,同步函数使用的是this锁,静态同步函数使用的是当前的字节码文件。
5、多线程死锁问题
什么是死锁,多线程中死锁现象?
答:同步中嵌套同步,无法释放,一致等待变为死锁。
死锁产生原因: 死锁的产生,就是同步中嵌套同步,互相不释放。
线程1 先拿到同步代码块oj锁,再拿到同步函数的this锁。
线程2 先拿到同步函数的this锁,再拿到同步函数代码块的oj锁。
原因:线程1需要同步函数的this锁,才能继续执行,而线程2需要线程1的oj锁才能继续执行。
所以就产生了互相等待对方释放锁。产生了死锁问题。
6、多线程的三大特性
原子性:保证线程安全问题,数据一致性。
可见性:Java内存模型,线程不可见。
有序性:join wait notify 多线程之间通讯的。
7、Java内存模型
什么是Java内存模型(JMM)?
属于多线程可见性 JMM,
Java内存模型决定了一个线程与另一个线程是否可见。
Java内存模型,分为很多区域,
主内存区域:主要存放共享的全局变量
私有本地内存:存放本地线程私有变量
注意:本地内存存放主内存共享数据副本
就因为Java内存模型,所以就产生线程安全问题。
什么事Java内存结构?
属于java内存结构jvm内存分配
产生线程安全的原因:
本地内存存放的是主内存共享数据的副本。在本地私有内存完成
操作后,再刷新到主内存中去。
可以保证线程安全,就是要主内存 通知另一个线程
8、Volatile关键字
Volatitle:关键字作用是保证线程之间可见,但不保证原子性。
没有使用Volatitle关键字
package com.leeue;
/**
*
* @classDesc: 功能描述:(volatile 关键字的使用 ) 这样写,子线程可以及时的结束。
* @author:李月
* @Version:v1.0
* @createTime:@Date 2018年6月8日 下午2:17:23
*
*/
class CreateThread05 implements Runnable{
public boolean flag = true;
public void run() {
System.out.println("子线程开始执行");
while(flag){
}
System.out.println("子线程结束");
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class ThreadVolatile {
public static void main(String[] args) {
CreateThread05 t1 = new CreateThread05();
Thread thread = new Thread(t1);
thread.start();
t1.setFlag(false);
}
}
运行结果
设置主线程休眠一段时间,就不会及时刷新主内存了,注意这里有两个线程,一个主线程
一是子线程。
代码
package com.leeue;
/**
*
* @classDesc: 功能描述:(volatile 关键字的使用)
* 主线程加入了休眠,没加volatile关键字
* @author:李月
* @Version:v1.0
* @createTime:@Date 2018年6月8日 下午2:17:23
*
*/
class CreateThread05 implements Runnable{
public boolean flag = true;
public void run() {
System.out.println("子线程开始执行");
while(flag){
}
System.out.println("子线程结束");
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class ThreadVolatile {
public static void main(String[] args) throws InterruptedException {
CreateThread05 t1 = new CreateThread05();
Thread thread = new Thread(t1);
thread.start();
Thread.sleep(1000);
t1.setFlag(false);
System.out.println("flag 已经设置成false");
Thread.sleep(1000);
System.out.println(t1.flag);
}
}
运行结果
加上volatile 关键字修饰变量后
package com.leeue;
/**
*
* @classDesc: 功能描述:(volatile 关键字的使用)
* @author:李月
* @Version:v1.0
* @createTime:@Date 2018年6月8日 下午2:17:23
*
*/
class CreateThread05 implements Runnable{
public volatile boolean flag = true;
public void run() {
System.out.println("子线程开始执行");
while(flag){
}
System.out.println("子线程结束");
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class ThreadVolatile {
public static void main(String[] args) throws InterruptedException {
CreateThread05 t1 = new CreateThread05();
Thread thread = new Thread(t1);
thread.start();
Thread.sleep(1000);
t1.setFlag(false);
System.out.println("flag 已经设置成false");
Thread.sleep(1000);
System.out.println(t1.flag);
}
}
加上volatile运行结果
注意:加上了volatile关键字后将多线程之间设为可见性,强制线程每次读取 flag的值的时候都
会区主内存中去取值。
10、volatitle 与synchronized区别?
仅靠volatitle是不能保证线程的安全性的,无法保证原子性。
1、volatitle是轻量级的,只能修饰变量,synchronized是重量级的还可以修饰方法。
2、volatitle只能保证数据的可见性,不能用来同步,因为多个线程同时访问volatitle不会发生
阻塞。
3、synchronized 不仅保证了线程的可见性,还保证了原子性。因为只有获取到了锁的线程
才能进入临界区,从而保证了所有进入临界区的语句全部执行完毕。多个线程争抢进入synchronized
会出现阻塞现象。
4、线程安全性。
线程安全主要是包括了两个方面,1、可见性,2、原子性。
仅仅使用volatitle是不能保证线程的安全性的,而synchronized可以实现线程的安全性。
9、AtomicInteger原子类
证明volatitle没有数据的原子性
package com.leeue;
/**
*
* @classDesc: 功能描述:(证明volatitle是没有原子性的程序)
* @author:李月
* @Version:v1.0
* @createTime:@Date 2018年6月11日 下午1:42:45
*
*/
public class VolatitleNoAtomic extends Thread{
private static volatile int count = 0;
//static 修饰的关键字,所有线程都可以共享
@Override
public void run() {
for(int i = 0; i < 1000; i++){
count++;
}
System.out.println(getName()+":"+count);
}
public static void main(String[] args) {
//创建10个线程
VolatitleNoAtomic[] volatitleNoAtomics = new VolatitleNoAtomic[10];
for(int i = 0; i < 10; i++){
volatitleNoAtomics[i] = new VolatitleNoAtomic();
}
for(int i = 0; i < 10; i++){
volatitleNoAtomics[i].start();
//注意!!启动线程永远是使用start()而不是run()方法,
//调用run()方法会当成一个普通的方法来调用的
}
}
}
运行结果
注意!!启动线程永远是使用start()而不是run()方法,调用run()方法会当成一个普通的方法来调用的注意:
使用JDK1.5并发包里面的原子类,保证数据的原子性 AtomicIntegeter()
package com.leeue;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* @classDesc: 功能描述:(证明volatitle是没有原子性的程序)
* @author:李月
* @Version:v1.0
* @createTime:@Date 2018年6月11日 下午1:42:45
*
*/
public class VolatitleNoAtomic extends Thread{
//private static volatile int count = 0;//static 修饰的关键字,所有线程都可以共享
private static AtomicInteger count = new AtomicInteger(0);//使用jdk1.5里面的并发包里面的原子类。
@Override
public void run() {
for(int i = 0; i < 1000; i++){
//count++
count.incrementAndGet();
}
//System.out.println(getName()+":"+count);
System.out.println(getName()+":"+count.get());
}
public static void main(String[] args) {
//创建10个线程
VolatitleNoAtomic[] volatitleNoAtomics = new VolatitleNoAtomic[10];
for(int i = 0; i < 10; i++){
volatitleNoAtomics[i] = new VolatitleNoAtomic();
}
for(int i = 0; i < 10; i++){
volatitleNoAtomics[i].start();
//注意!!启动线程永远是使用start()而不是run()方法,调用run()方法会当成一个普通的方法来调用的
}
}
}
运行结果
AtomincInteger和synchronized都是解决线程安全性问题,保证数据的一致性也就是原子性
同步的概念:
程序中的同步:是程序从上往下有顺序执行
线程中的同步:是保证线程安全,保证数据的原子性。
ThreadLocal使用案例
package com.leeue.thread;
/**
*
* @classDesc: 功能描述:(验证ThreadLocal用法)
* @author:<a href="leeue@foxmail.com">李月</a>
* @Version:v1.0
* @createTime:2019年2月19日 下午9:56:57
*/
class Res {
//使用ThreadLocal,给每个线程提供一个自己的局部变量
ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0;
};
};
public Integer getNumber() {
int count = threadLocal.get() + 1;
threadLocal.set(count);
return count;
}
}
public class Demo01 extends Thread {
private Res res;
public Demo01(Res res) {
this.res = res;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "count=" + res.getNumber());
}
}
public static void main(String[] args) {
Res res = new Res();
Demo01 t1 = new Demo01(res);
Demo01 t2 = new Demo01(res);
t1.start();
t2.start();
}
}