关于Java多线程,我想这是一个程序员进阶过程中不可避免遇到的问题,这里,为了能够更加清晰,我仅仅讲诉多线程中的同步锁(synchronized)
关于Java synchronized的比喻
这里我们假设:
(1)房子。一个object是一个房子
(2)房间。房子里面有很多房间,即方法。
(3)人
被synchronized的房间在房子左边,有一个铁门,铁门上有锁孔,地板上有锁,可以被上锁,一旦其他人就进不来了。当人出去之后,锁被打开,放在地板上。而且每次只能进来一个人。
没有被synchronized 的房间在房子右边,没有锁孔,不能被上锁,想进来就进来,进来多少人都行。
这样我们就可以推断出synchronized的有关性质。
第一
当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
即两个人要进同一个可以上锁的房间,需要的一个人进去再出来,把锁打开放在地上时候,另一个人才能进入房间。
第二
,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
即有一个人进入一个房间,并上锁之后,其他的人,可以进入其他,没有那些上锁的房间。
第三
当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
即当有人进入了有锁的房间之后,铁门已经上锁了,所以,其他人不能够再进入其他在铁门里的房间。
第四
当调用wait()之后,线程就进入的作用就是使线程阻塞并且会暂时释放掉对象的锁。而notify()/notifyAll()可以通知线程,被synchronized的模块可用。但是并不能保证改线程马上就能调用被synchronized的模块。Notify()/notifyAll()只是起到通知的作用。
即一个人从有锁的铁门出来之后,(调用wait(),进入阻塞)他进行了一个设置,当synchronized的模块可用的时候,通知他(调用notify(),进入就绪状态)。他只能继续和其他想要使用synchronized模块的人一样,并没有优先权。其他人完全没有必要让着他。
注:sleep():调用sleep进入阻塞状态(阻塞状态有等待阻塞(wait())、同步阻塞(线程在获取 synchronized 同步锁失败)、其他阻塞(线程的 sleep() 或 join()))、
Sleep()可以设置睡眠的时间,但是你不能够指望sleep可以十分精确的根据你设置的时间进行sleep()
下面是我在网上找到的一个很形象的例子,自己也编码改进,并证实了。
public class RannableTest implements Runnable {
private int ticket = 10;
@Override
publicvoid run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(3000);//设置时间间隔,提高出错几率
} catch (Exception e) {
// TODO: handleexception
e.printStackTrace();
}
if (this.ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出:"
+ this.ticket-- + "号票");
}
}
}
public static void main(String[] args) {
RannableTest rannableTest = new RannableTest();
new Thread(rannableTest, "a").start();
new Thread(rannableTest, "b").start();
new Thread(rannableTest, "c").start();
}
}
结果:
b卖出:10号票
c卖出:8号票
a卖出:9号票
c卖出:7号票
b卖出:6号票
a卖出:5号票
b卖出:4号票
c卖出:3号票
a卖出:2号票
b卖出:1号票
c卖出:0号票
a卖出:-1号票
出现了0号,-1号。具体原因b在卖出1号票的时候,还没有来得及进行自减操作,c和a就进行了if(ticket>0)的验证,通过验证之后,在c和a要打印出票的时候,b卖出1号票ticket进行自减操作才完成。导致了在ticket>0验证通过,但是实际上不能通过。
改进
public class RannableTest implements Runnable {
privateintticket = 10;
@Override
publicvoid run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(4000);
} catch (Exception e) {
// TODO: handleexception
e.printStackTrace();
}
synchronized (this) {
if (this.ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出:"
+ this.ticket-- + "号票");
}
}
}
}
publicstaticvoid main(String[] args) {
RannableTest rannableTest = new RannableTest();
new Thread(rannableTest, "a").start();
new Thread(rannableTest, "b").start();
new Thread(rannableTest, "c").start();
}
}
结果:
a卖出:10号票
b卖出:9号票
c卖出:8号票
a卖出:7号票
c卖出:6号票
b卖出:5号票
b卖出:4号票
a卖出:3号票
c卖出:2号票
c卖出:1号票
把if(ticket)和ticket--放在一个synchronized里面,保证ticket--之后(释放锁之后),其他线程才能再次调用改模块,进行if(ticket)的判断