问题引入
我们在上一章,引入了线程的概念,小明看完后,茅塞顿开,作为动物园的经理,他想用这种方式来解决动物园买票游客排队长的现象,他决定先用程序模拟一下:
设计4个售票窗口,总计售票100张。
用多线程的程序设计并写出代码。
错误案例一
小明作为经理比较忙,把这件事情丢给了秘书小张,小张说:“经理交给我吧,这有啥难的”
public class SaleTickets {
public static void main(String[] args) {
Tickets t1 = new Tickets();
Tickets t2 = new Tickets();
Tickets t3 = new Tickets();
Tickets t4 = new Tickets();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Tickets extends Thread{
int ticketnum = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while(ticketnum!=0) {
System.out.println(super.getName()+"卖出了一张票"+"还有"+(--ticketnum)+"张");
}
}
}
小张写完后,测试了一下,好家伙直接可以卖四百张票。这该如何是好,小张想了好久最后也没有办法给小明打了电话。
错误案例二
小明回来后,细看,找到了错误。原来是定义的票数并不是公共资源,小张等于让每个人都卖了100张票,于是他把代码改为了:
public class SaleTickets {
public static void main(String[] args) {
Tickets t1 = new Tickets();
Tickets t2 = new Tickets();
Tickets t3 = new Tickets();
Tickets t4 = new Tickets();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Tickets extends Thread{
static int ticketnum = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while(ticketnum!=0) {
System.out.println(super.getName()+"卖出了一张票"+"还有"+(--ticketnum)+"张");
}
}
}
果然没有出现类似的错误,在小张的马屁声中,小明经理又写了一种方法:
public class ApiRunSale {
public static void main(String[] args) {
Tickets2 t = new Tickets2();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Tickets2 implements Runnable{
public int num = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while (num!=0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"卖出第"+(num--)+"张票");
}
}
}
随后大手一挥,好的!就这么办,明天我们就这样开始卖票!
结果,第二天,门卫老大爷抓到了好多拿着相同票的游客,大爷很生气,游客很委屈。最后经过一系列的测试,发现原来是不同的售卖员卖了同一张票…
售卖员也很委屈,因为有时候游客催的急,没来得及去问别的售卖员开票没有就得开票了。
错误案例三
小明经理说,你们哪怕是真的急也应该等一等,看清楚再出票,这样才不会出问题。
public class SaleTickets {
public static void main(String[] args) {
Tickets t1 = new Tickets();
Tickets t2 = new Tickets();
Tickets t3 = new Tickets();
Tickets t4 = new Tickets();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Tickets extends Thread{
static int ticketnum = 100;
@Override
public void run() {
int i=0;
// TODO Auto-generated method stub
while(ticketnum!=0) {
System.out.println(super.getName()+"卖出第"+ticketnum+"票"+"还有"+(--ticketnum)+"张");
i++;
try {
super.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(i);
}
}
事实上等了等以后问题更严重了,因为售票员操作机器的时候要等一会儿,这个时候另一个售票员就把属于这个游客的票给了另一个游客…
最后小明说,要不然就限定一下,为了保证你们可以同步进行,我们提前确定一下,同一时间只能有一个人可以去卖票,卖完之前不能有另一个人过来打扰。
这也就是synchronized的概念
public class ApiRunSale {
public static void main(String[] args) {
Tickets2 t = new Tickets2();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
//synchronized;
}
}
class Tickets2 implements Runnable{
public int num = 100;
@Override
public synchronized void run() {
// TODO Auto-generated method stub
while (num!=0) {
System.out.println(Thread.currentThread().getName()
+"卖出第"+(num--)+"张票");
}
}
}
可是这种情况下…进去一个线程剩下的就进不去了,所以我们的用法应该是:
public class ApiRunSale {
public static void main(String[] args) {
Tickets2 t = new Tickets2();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
//synchronized;
}
}
class Tickets2 implements Runnable{
public int num = 80000;
@Override
public void run() {
// TODO Auto-generated method stub
while (num!=0) {
synchronized(Thread.currentThread()) {
System.out.println(Thread.currentThread().getName()
+"卖出第"+(num--)+"张票");
}
}
}
}
终于,成功解决了问题。
问题总结
首先我们要了解同步锁的概念其次我们要知道我们的同步锁具体应该锁到哪里。
同步锁的概念
把有可能出现问题的代码包起来,一次只让一个线程执行。通过sychronized关键字实现同步。
当多个对象操作共享数据时,可以使用同步锁解决线程安全问题。
特点
1、 前提1,同步需要两个或者两个以上的线程。
2、 前提2,多个线程间必须使用同一个锁。
3、 同步的缺点是会降低程序的执行效率, 为了保证线程安全,必须牺牲性能。
4、 可以修饰方法称为同步方法,使用的锁对象是this。
5、 可以修饰代码块称为同步代码块,锁对象可以任意。
注意我们锁的地方必须要是合理合适的临界区,如果太大了导致只有一个线程能进去(也就是最后),锁太小了就没有什么用处了。