public class MulThreadTest {
public static int a = 0;
public void addInt1(){
synchronized(this){
a++;
System.out.println(a);
}
}
public static void main(String[] args){
MulThreadTest test = new MulThreadTest();
for(int i=0;i<3;i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int j=0;j<100;j++)
test.addInt1();
}
}).start();
}
}
}
public class MulThreadTest {
public static int a = 0;
public void addInt1(){
synchronized(this){
a++;
System.out.println(a);
}
}
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<3;i++){
new Thread(new Runnable() {
@Override
public void run() {
MulThreadTest test = new MulThreadTest();
for(int j=0;j<100;j++)
test.addInt1();
}
}).start();
}
}
}
上面两段代码只有些许的不同,但相比出来结果却是比较让人费解的,下面就我的理解来解释一下出现不同结果的原因(你们可以自己运行一下,比较一下输出结果的不同)
a++不是原子操作,分为三步:(1).读取a,(2).执行++操作,(3).写入a;这样才算执行完a++整个操作。
System.out.println(a)也不是原子操作,分两步:(1).读取a.(2).输出a。
第一段程序,我们虽然创建并启动了三个线程,但此时只有一个对象,这三个线程线程此时共同作用于一个对象的addInt1()方法,这三个线程由于存在的synchronized的关系,他们为同步的关系,即一次只能有一个线程进入addInt1()方法,其他的线程进入需要等待,直到进入addInt1()方法的线程执行完毕,释放锁后,要执行的线程才可以正常执行。
第一个程序比较形象的例子:有一个大仓库(类),里面有一个房子(因为第一段程序只创建了一个对象(包含一个addInt1()方法),所以为一个房子),门上有一把锁(房子和锁代表加synchronized的addInt1()方法),现在有三个人(代表三个线程),要进入房子办事(a++,打印a),A抢到钥匙,先打开锁,进入房间,此时因为房间有A,B,C都无法进入,只能等A办完事出来,B,C才可以进入。所以每个人进入先执行a++,在打印,最后打印出来的结果为:11(A or B or C),12(A or B or C),13(A or B or C),14(A or B or C),15(A or B or C),,,,只不过这些打印出来的数可能是A,B,C,三人中的一个。所以最后总的打印结果为11,12,13,14,15,16,,,。
关于第二段代码的结果出现同一数字打印多次和数字的顺序颠倒(如先打印10,再打印9)还有丢失数据的问题我们来一一解释。(1)我们先说同一数字打印多次,我们第二段代码创建了三个对象,实际上就有了三把锁,每个线程执行的是自己线程内的那把锁,我们让三个线程共同访问共享变量a,这样就会出现线程竞争的问题,我们假设现在有两个线程A与B,起初a=0,先线程A执行a++(分三步),这时我们A只执行了a++的步骤((1)),读取到a为0,就轮到线程B开始执行,线程B执行a++的三步(读取a为1,执行++,写入a为2)都完成了,也完成了打印((1)(2))a(此时a为2),现在又轮到线程A继续执行刚才没有执行完的操作,线程A由于之前已经读取了a为0,所以直接执行a++的步骤((2)(3)),执行完了a为1,此时线程A执行打印((1)(2))a,此时输出的a为1,所以就出现了同一数字重复输出的结果。
(2)再说数字颠倒的问题,就是原本顺序打印,现在出现比如4,5,7,6,8,9....这样的情况。同样这次就不是a++出现的问题了,而是打印语句被线程中断了。同样,我们假设有两个线程A和B,起初a=0,执行线程A的a++((1)(2)(3)),此时a为1,然后线程A继续执行打印操作,但此时只执行了打印((1))读取到a为1,还没有执行打印((2)),就轮到线程B开始执行,此时线程B执行a++((1)(2)(3)),然后继续执行打印((1)(2)),此时a为2,就输出了2,然后轮到线程A开始执行,继续完成刚才的打印((2))的操作,输出1,所以就出现了2,1这样的颠倒的顺序。
(3)再说数据丢失的情况。比如数字1没有输出。同样,我们假设有两个线程A和B,起初a=0,执行线程A的a++((1)(2)(3)),此时a为1,但此时还没执行打印,就轮到线程B开始执行。线程B执行a++((1)(2)(3)),此时a为2,继续执行打印a((1)(2)),输出2,然后轮到线程A执行,线程A继续执行打印a((1)(2)),此时a为2,输出2,这样就把数据1丢失了。
第二段程序比较形象的例子同样是把上面程序一的形象例子修改了一下:有一个大仓库(类),现在里面有了三个房子(因为第二段程序创建了三个对象(分别包含一个addInt1()方法),所以为三个房子),每个房子门上有一把锁(房子和锁代表加synchronized的addInt1()方法),静态变量a属于仓库,谁修改,别人都可以看到。现在有三个人(代表三个线程)A,B,C,要进入房子办事(a++,打印a),此时,A拿到自己的钥匙(实际有几个对象,就有几把钥匙),先打开锁,进入房间,此时因为A在自己的房间执行a++,打印a(此时可能只执行了一个原子操作如a++((1)),就被赶出来,也可能执行了几个原子操作a++((1)(2)(3),打印a((1))就被赶出来),轮到线程B或者线程C执行,最后所有线程执行完毕,出现的结果可能就是数据的错误输出(顺序颠倒,数据丢失,数据重复)。出现这样的问题还是没有让所有线程竞争同一把锁(同步),而是出现了三把锁,各自线程进出自己的锁,修改共享变量。
针对第二段程序出现的问题,我们可以考虑是不是可以给仓库(类)加上锁,这样是不是就可以正确的执行了 。
关于synchronized作用:由系统自动给被synchronized修饰的方法或代码块加锁,使被修饰的代码在同一时间只能被一个线程访问。
理解:一个线程a正在执行一个加synchronized的代码块,执行到一半的时候,cpu的这个时间片用完了,此时cpu被另一个线程b用来干其他的事情,如果线程b也想来进入此加synchronized的代码块,那么将是不被允许进入的,因为这个锁线程a还没有释放。所以当以后的某个cpu时间片被线程a抢到,a线程再将继续执行此加synchronized的代码块,所以保证了加synchronized的代码块的同步性。