一步一步分析问题先看下面
1.当我开1000个线程出售1000张票时,数据正常,但是当我开1100个时就会出现负数?为什么啊?明明已经加了判断
count>0
public class C{
int count=1000;//火车票
static long currenttime;
static long endtime;
static File file=new File("C:/Users/绝影/Desktop/log.txt");
static OutputStream out=null;
static OutputStreamWriter output=null;
static {
System.out.println("我是静态块");
try {
out=new FileOutputStream(file);
output=new OutputStreamWriter(out);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void one() throws IOException {
// TODO 线程输出方法
try {
if(count>0) {
synchronized(this) {
output.write("车票"+(count--)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
System.out.println("车票"+(count)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
}
}else{
endtime=System.currentTimeMillis();
System.out.println(endtime);
System.out.println("抱歉现在没有车票了"+"总共用了时间"+(endtime-currenttime)+"卖完完余票");
System.exit(0);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
output.flush();
}
}
public static void main(String[] args) throws InterruptedException {
currenttime=System.currentTimeMillis();
final C output = new C();
final R b = new R();
final BB e = new BB();
System.out.println("当前时间"+currenttime);
//方式1
for(int j=1;j<=1100;j++) {
new Thread() {
public void run() {
try {
try {
sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}//模仿网络延迟
output.one();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}.start();
}
}
}
2.分析如下
if(count>0) {
synchronized(this) {
output.write("车票"+(count--)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
System.out.println("车票"+(count)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
}
}
分析应该是这样的,虽然加了synchronized保证了同一个时刻当出现多个线程抢占时,这时候只有一个线程能进入到synchronized块中,但是对于if(count>0) 判断其它线程还是可以访问。因为我电脑时双核四线程,
双核四线程是每个核心不停地在两个线程之间切换,以达到和四核相似的处理能力,自然同一个时刻可以跑两个线程。
如果我们假设某一时刻内存中的数count是1,执行类似i--操作,比方第1000个线程进行了if(count>0)后运行同步块中的内容,这时候第1001个线程已经拷贝了之前内存中变量1的副本进行了if(count>0)操作,遇到了 synchronized发现有线程在使用,此时等待,当第1000个线程执行完同步副本变量到内存时,此时count=0,然后第1001个线程已经用之前的变量副本1进行了count>0的判断,所以还是进入了同步块,自然就出现了负数问题。为了验证假设当我在同步快中再次执行if(count>0)操作后在操作变量后,count不在出现负数,说明假设成立,也说明我们线程在使用到变量时,每次都会去读取当前内存中的当前变量内容。多线程不同步出现线程共享变量安全是因为每次读取的内存中内容不是最新更新的一个才导致。
其实单核多线程我觉得也会出现,当第1000个执行了if(count>0)的判断后让出了cpu,切换到了第1001个执行,第1001个执行完判断并且执行完同步块中的内容更新内存中的count为0,但是第1000个线程已经执行过if(count>0),所以不会再执行这个判断,会接着往后执行,所以如果同步块中不加判断还是会出现负数,这个有点类似于单例模式的这种模型,为了在多线程下保证单例
-
public class Test18 {
-
static Object lock=new Object();
-
private static Test18 singleton=null;
-
private Test18 (){}
-
public static Test18 getInstance(){
-
if(singleton==null){
-
synchronized (lock) {
-
if(singleton==null)
-
singleton=new Test18();
-
}
-
}
-
return singleton;
-
}
-
}
所以修改同步块内容为
synchronized(this) {
if(count>0) {
output.write("车票"+(count--)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
System.out.println("车票"+(count)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
}
}
二:用lock达到同样效果
class R extends Thread{
static int count=1000;//火车票
static long currenttime;
static long endtime;
int agent=0;
static File file=new File("C:/Users/绝影/Desktop/log.txt");
static OutputStream out=null;
static OutputStreamWriter output=null;
private Lock lock = new ReentrantLock();// 锁对象
static {
System.out.println("我是静态块");
try {
out=new FileOutputStream(file);
output=new OutputStreamWriter(out);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void one() throws IOException {
// TODO 线程输出方法
try {
if(count>0) {
lock.lock();// 得到锁
if(count>0) {
output.write("车票"+(count--)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
System.out.println("车票"+(count)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
}
lock.unlock();// 释放锁
}else{
endtime=System.currentTimeMillis();
System.out.println(endtime);
System.out.println("抱歉现在没有车票了"+"总共用了时间"+(endtime-currenttime)+"卖完完余票");
System.exit(0);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
output.flush();
}
}
@Override
public void run() {
try {
try {
sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
one();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class C {
int count = 1000;// 火车票
static long currenttime;
static long endtime;
public static void main(String[] args) throws InterruptedException {
currenttime = System.currentTimeMillis();
final R b = new R();
System.out.println("当前时间" + currenttime);
// 方式二
for (int j = 1; j <= 1200; j++) {// 1000
new Thread(b).start();
}
}
}
方法三:synchronized关键字加到方法上,这种只是锁的范围大,效率低
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
class BB implements Runnable{
static int count=1000;//火车票
static long currenttime;
static long endtime;
int agent=0;
static File file=new File("C:/Users/绝影/Desktop/log.txt");
static OutputStream out=null;
static OutputStreamWriter output=null;
static {
System.out.println("我是静态块");
try {
out=new FileOutputStream(file);
output=new OutputStreamWriter(out);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized void one() throws IOException {
// TODO 线程输出方法
try {
if(count>0) {
output.write("车票"+(count--)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
System.out.println("车票"+(count)+"正在 "+Thread.currentThread().getName()+" 出售"+"\r\n");
}else{
endtime=System.currentTimeMillis();
System.out.println(endtime);
System.out.println("抱歉现在没有车票了"+"总共用了时间"+(endtime-currenttime)+"卖完完余票");
System.exit(0);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
output.flush();
}
}
@Override
public void run() {
try {
one();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class C{
static long currenttime;
static long endtime;
public static void main(String[] args) throws InterruptedException {
currenttime=System.currentTimeMillis();
final BB e = new BB();
System.out.println("当前时间"+currenttime);
//方式三
for(int j=1;j<=1200;j++) {//1000
new Thread(e).start();
}
}
}
|