----------android培训、java培训、java学习型技术博客、期待与您交流! -----------
1,线程引发的数据不安全问题介绍
就拿火车票售票为分析,假设一个火车站有是个售票窗口,需要卖出10张票,不能卖0号票以及负号票,从10往下递减。
用继承Thread类实现:(注意:ticketnum在类中要静态化,否则就是私有成员了,那就是4个线程一共卖出40张票了)
class saleticket extends Thread{
//因为采用继承方式执行线程对象,所以共享数据要设置为静态的,这样共享数据属于
//这个子线程类的,而不是具体的某个子线程对象,各个对象可以对这个共享数据操作
//否则的话,每个线程都可以卖出十张票了,那么多线程就失去原有的意义了
private static int ticketnum = 10;
public void run() {
while(ticketnum>0){
System.out.println(this.getName()+"卖出第"+(ticketnum--)+"张票");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
验证结果:
卖出了0号票,说明线程安全出现了问题。这种出问题的几率很难捕捉到,5到6次才能捕捉到这个现象。下面用Runnable方法来实现,把睡眠动作放在打印之前,这样,问题很容易展现出来,能够卖出-2号票。
用Runnable接口实现:(因为Runnable接口的实例对象只有一个,四个线程公用这个对象,所以ticketnum可以为私有成员了,当线程结束是,对象就会被GC回收,节省了内存资源)
class saleticket1 implements Runnable{
//因为saleticket1对象之创建了一次,但是通过这个saleticket1创建了四个线程
//相当于四个线程对saleticket1对象进行操作,所以就可以把共享数据设置为私有
//非静态的,这样生命周期就可以缩短很多,节约资源
private int ticketnum = 10;
public void run() {
while (ticketnum>0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "卖出第" + (ticketnum--)
+ "张票");
}
}
}
验证结果:
2,锁的原理
线程出现了安全问题,那么用什么办法解决呢,那就是用锁机制,锁的使用注意事项:
//1,在继承thread类中,锁必须是类的静态成员,否则不起作用,线程只是调用自己的锁,对其他线程没有影响
//2,在实现Runnable接口中,锁不需要是静态成员,因为线程公用一个runnable对象
代码如下:
class synchronized_method extends Thread{
private static int ticketnum = 10;
private static Object mutex = new Object();
public void run() {
while(true){
//同步的范围约定,共享数据的第一次和最后一次的使用范围即可其他不需要同步
//切忌:不要在while循环上上锁,上锁就只有一个线程卖票了
//助记方法:火车的卫生间 1,红色(有人)关门,类似上锁
// ...........
// 2,绿色(没人)开门,类似解锁
synchronized (mutex) {
if(ticketnum <= 0){
break;
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.getName()+"卖出第"+(ticketnum--)+"张票");
}
}
}
}
结果验证:
3,同步方式在线程创建方式中的可行性分析(继承Thread类)
a,建立私有对象锁
class synchronized_ticket extends Thread{
private static int ticketmun = 10;
private Object obj = new Object();
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized(obj){
if(ticketmun <= 0){
break;
}
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(this.getName()+"sales:\t"+ticketmun--);
}
}
}
/*
* 结论:这种方法不可行
* 原因:object对象是线程私有的,且在初始化时使用new方式生成的
* 这样四个线程就有四个Object对象,在run方法中使用同步代码块,锁使用自己
* 创建的obj对象,这样四个线程都是用自己的obj实例,起不到共享数据同步的作用了
* */
}
验证结果:
b,建立类静态锁
class synchronized_ticket1 extends Thread{
private static int ticketmun = 10;
private static Object obj = new Object();
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized(obj){
if(ticketmun <= 0){
break;
}
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(this.getName()+"sales:\t"+ticketmun--);
}
}
}
/*
* 结论:这种方法可行
* 原因:object对象是静态私有的,且在类初始化时显示方式生成的
* 这样四个线程就共享这个Object对象,在run方法中使用同步代码块,锁变成了共享锁
* ,这样四个线程就需要抢夺锁资源了,方法可行
* */
}
验证结果:
c,建立私有对象锁,但是锁是一个引用对象,引用从构造函数传进来的Object对象
class synchronized_ticket2 extends Thread{
private static int ticketmun = 10;
private Object obj;
public synchronized_ticket2(Object obj){
this.obj = obj;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized(obj){
if(ticketmun <= 0){
break;
}
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(this.getName()+"sales:\t"+ticketmun--);
}
}
}
/*
* 结论:这种方法可行
* 原因:object对象是私有的,但是在类实例化时引用了一个对象锁,并且
* 这四个线程的obj同时这引用个Obj对象,在run方法中使用同步代码块,锁变成了共享锁
* ,这样四个线程就需要抢夺锁资源了,方法可行
* */
}
验证结果:
d,在thread子类中定义同步函数,并且在run方法中调用,这个方法可行吗?
假设同步函数是静态的,那么就有可能实现线程同步,下面是代码实现,
class synchronized_ticket3 extends Thread{
private static int ticketmun = 10;
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
if(ticketmun <= 0){
break;
}
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
show();
}
}
public static synchronized void show(){
System.out.println(Thread.currentThread().getName()+"sales:\t"+ticketmun--);
}
/*
* 结论:这种方法不可行
* 原因:虽说使用了静态同步函数,但是同步的锁还是线程子类对象本身
* */
}
验证结果:
e,如果无法在线程子类中实现同步函数,那使用其他类(静态内部类)是否可行呢?
class synchronized_ticket4 extends Thread{
private static int ticketmun = 10;
private static synchronized_ticket4.synclass syn = new synchronized_ticket.synclass();
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
if(ticketmun <= 0){
break;
}
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
syn.sellTicket(ticketmun--);
}
}
static class synclass{
public synchronized void sellTicket(int tickNum){
if(tickNum >0){
try {
Thread.sleep(10);
}catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread()+". sale: ticket"+ tickNum--);
}
}
}
/*
* 结论:这种方法完全可行
* 原因: 因为内部类被初始化时只有一个类对象,这样就可以同步了
* 补充:内部类也可以,只要是在类初始化是被定义为静态的就可以了,不再赘述
* private static synchronized_ticket4.synclass syn = new synchronized_ticket4().new synclass();
* */
}
验证结果:
f,改进方法:将共享对象封装在一个内部类中,着呀更容易理解,同时对共享数据进行的隐藏,
class synchronized_ticket5 extends Thread{
private static synchronized_ticket5.synclass syn = new synchronized_ticket5().new synclass();
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
syn.sellTicket();
}
}
private class synclass{
private int ticketmun = 10;
public synchronized void sellTicket(){
if(ticketmun >0){
try {
Thread.sleep(10);
}catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread()+". sale: ticket"+ticketmun --);
}
}
}
/*
* 结论:这种方法完全可行
* 原因: 因为静态内部类被初始化时只有一个类对象,这样就可以同步了
* 补充:内部类也可以,只要是在类初始化是被定义为静态的就可以了,不再赘述,更严谨
* 的做法是把内部类私有化,外部无法直接调用
* */
}
验证结果:
补充:实际开发中不需要使用私有内部类,普通的一个java类就可以作为锁,只要保证每个线程引用同一个锁对象即可,就是把私有内部类提取到外面,用线程引用就可以了,不需要太严谨。