多线程使用共享资源出现的线程安全问题
线程安全是什么:
当多个线程访问某个方法时,不管你通过怎样的调用方式或者说这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
以下是一个不符合线程安全定义的代码
Tickets类的代码
package com.lk.syn0322;
public class Tickets {
int tickets=100;
}
Users类的代码
package com.lk.syn0322;
public class Users implements Runnable {
Tickets tk;
public Users(Tickets tk)
{
this.tk=tk;
}
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i=0;i<50;i++)
{
//注意数据的更改权是只能一个进行操作,在多个线程的时候可能一个线程会突然失去它的控制的权力
//比如这里在打印完以后线程A失去控制权,到线程B但是线程B并没有获得数据并没有在A当中进行更改
System.out.println(Thread.currentThread().getName()+"线程:"+"购买了第"+tk.tickets+"张票");
tk.tickets--;
}
}
}
测试类的代码
package com.lk.syn0322;
public class Test {
public static void main(String[] args) {
System.out.println("开始测试了");
Tickets tk =new Tickets();
//创建的这个对象并不是一个线程
Users user1 =new Users(tk);
//线程的真正的开启还是要利用Thread函数才行
new Thread(user1,"线程A").start();
new Thread(user1,"线程B").start();
}
}
出现的问题和后果
产尘这种后果的原因的阐述:
因为两个线程对于票的操作对象是同一个,因为它们是并发的,但是数据的更改途径只有一个,当线程A执行完打印语句的时候,可能线程B也执行到了打印语句,这个时候线程A并没有将tickets的数据更改
因此这种并发处理共享数据容易发生数据的混乱
解决办法:利用同步锁(互斥锁)
synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。
注意synchronized方法括号当中锁住的是括号当中的内容,不是大括号当中的内容,当别的线程想要获取锁住的内容的时候,因为上一个线程A没有释放这个线程锁,所以这个线程B没有办法获得锁当中的数据内容只有等待
通俗易懂的例子:六个人的宿舍在寝室里面都要去上厕所,但是只有一个卫生间,当一个同学在里面上厕所的时候另外的同学是不能够进去进行操作的,只有当这个同学操作完了,将门打开我们才能够进行操作
注意事项(synchronized):要注意这个的使用范围,随意的使用可能影响程序的运行的效率,比如几个线程把锁一直霸占着,需要的程序得不到想要的数据只能干等着,就会使得计算机运行效率降低
线程安全问题的解决
只需要将继承Runnable的Users类更改以下即可
实例代码:
package com.lk.syn0322;
public class Users implements Runnable {
Tickets tk;
public Users(Tickets tk)
{
this.tk=tk;
}
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i=0;i<50;i++)
{
//注意数据的更改权是只能一个进行操作,在多个线程的时候可能一个线程会突然失去它的控制的权力
//比如这里在打印完以后线程A失去控制权,到线程B但是线程B并没有获得数据并没有在A当中进行更改
//有点疑惑为啥不是AB交换来着购买票;是不是因为这个线程没有结束,所以tk没有释放,只有当一个类结束了以后它里面的数据才会释放
//为啥用this它会出现AB交换买票的情况
ticket(tk);
}
}
//锁住了tk这个实例,当它不释放的时候,线程B是没有办法进入tk当中的
public synchronized void ticket(Tickets tk)
{
System.out.println(Thread.currentThread().getName()+"线程:"+"购买了第"+tk.tickets+"张票");
tk.tickets--;
}
}
执行的效果:
可见加了同步锁以后线程安全得到了保障
存在的需要探讨的地方(疑惑点)
注意:当我们在synchronized()括号当中是空值的时候我们默认是锁住的是this(当前类的实例)
当我们锁住当前实例的时候
出现AB相互执行的情况
但是当我们锁住的是tk实例对象的时候,A先出现五十遍以后才会出现B的原因是啥