总共有100张票,两个站点同时出售。
程序1:
class MyThread implements Runnable{ /*一百张票*/ private int tickets = 100; @Override public void run() { while (true){ if (tickets > 0){ System.out.printf("%s线程正在卖出第%d张票!\n",Thread.currentThread().getName(),tickets); tickets --; } else { break; } } } } public class ThreadSynTest { public static void main(String[] args) { /*第一个站点卖票*/ MyThread t1 = new MyThread(); new Thread(t1).start(); /*第二个站点卖票*/ MyThread t2 = new MyThread(); new Thread(t2).start(); } }
显示结果:
从结果可以发现,这两个线程卖的是各自的100张票,显然是不对的。
程序二:我们把票数的变量改为静态的。
/** * 把票数的变量改为静态的 * @author Liao * */ class MyThread implements Runnable{ /*一百张票*/ private static int tickets = 100; @Override public void run() { while (true){ if (tickets > 0){ System.out.printf("%s线程正在卖出第%d张票!\n",Thread.currentThread().getName(),tickets); tickets --; } else { break; } } } } public class ThreadSynTest { public static void main(String[] args) { /*第一个站点卖票*/ MyThread t1 = new MyThread(); new Thread(t1).start(); /*第二个站点卖票*/ MyThread t2 = new MyThread(); new Thread(t2).start(); } }
显示结果:
从图中可以看出,还是有问题!
产生上述结果的原因是程序在执行过程中线程是来回自由切换的,有可能第一个线程刚卖出票,数量还没来得及减一,就跳到了线程二。
从上面的分析来看,要想使得多个线程操作一个资源,我们必须使用一个互斥的手段来限制多个线程同时操作一个资源。
Java提供了Synchronized关键字来确保线程的同步。
synchronized可以用来修饰一个方法和一个方法内部的某个代码块。
1:修饰代码块:
synchronized(a){ //同步代码块 }
上述代码表示当前线程霸占a对象,并执行代码块中的内容,其他线程无法执行代码块中的内容,只有当前线程执行完成之后,释放了对a对象的霸占,其他线程才有可能执行代码块中的内容。
总而言之:synchronized的功能就是一个线程正在操作某资源的时候,将不允许其他线程操作该资源,即一次只允许一个线程处理该资源。
2:修饰方法:
synchronized修饰一个方法时,实际霸占的是该方法的this指针所指向的对象,就是正在调用该方法的对象
我们先来看几种有问题的程序:
class MyThread1 implements Runnable { /* 一百张票 */ private static int tickets = 100; @Override public void run() { synchronized (this) { while (true) { /* 不能放在外面:放在外面的话就表示一个线程跳进来之后,就互斥了,其他线程就跳不进来,导致只有一个线程在卖票 */ if (tickets > 0) { System.out.printf("%s线程正在卖出第%d张票!\n", Thread.currentThread().getName(), tickets); tickets--; } else { break; } } } } } public class ThreadSynTest3 { public static void main(String[] args) { MyThread1 t = new MyThread1(); /* 第一个站点卖票 */ Thread thread1 = new Thread(t); thread1.start(); /* 第二个站点卖票 */ Thread thread2 = new Thread(t); thread2.start(); } }
程序输出的结果:
结果显示,永远都是一个线程在卖票;我觉得导致这个问题的原因是线程的启动也是调用run()方法,程序中run()方法的第一句代码就是执行synchronized这就导致了一开始就锁定了某个线程。直到这个线程把票都卖完了。
示例代码二:
class A implements Runnable{ private int tickets = 100; @Override public synchronized void run() { while (true){ if (tickets > 0){ System.out.printf("%s线程正在卖出第%d张票!\n",Thread.currentThread().getName(),tickets); tickets --; } else { break; } } } } public class ThreadTest2 { public static void main(String[] args) { A a = new A(); new Thread(a).start(); new Thread(a).start(); } }
这个程序的问题和上一个程序是类似的,程序一执行,就锁定了某个线程导致了这个线程把票全卖完了。
正确程序:
class MyThread implements Runnable { /* 一百张票,注意这里要把变量定义为静态的 */ private static int tickets = 100; @Override public void run() { while (true) { /*不能放在外面:放在外面的话就表示一个线程跳进来之后,就互斥了,其他线程就跳不进来,导致只有一个线程在卖票*/ synchronized (this) { if (tickets > 0) { System.out.printf("%s线程正在卖出第%d张票!\n", Thread.currentThread().getName(), tickets); tickets--; } else { break; } } } } } public class ThreadSynTest { public static void main(String[] args) { MyThread t = new MyThread(); /* 第一个站点卖票 */ Thread thread1 = new Thread(t); thread1.start(); /* 第二个站点卖票 */ Thread thread2 = new Thread(t); thread2.start(); } }
程序结果:
第二种方式(正确):
/** * 通过继承的方式创建线程 * @author Liao * */ class SyncTread extends Thread { /* 100张票 */ private static int tickets = 100; /* 控制同步的标示 */ private static String str = new String("s"); @Override public void run() { while (true) { synchronized (str) { if (tickets > 0) { System.out.printf("%s线程正在卖出第%d张票\n", Thread.currentThread().getName(), tickets); tickets --; } else { break; } } } } } public class ThreadSyncTest2 { public static void main(String[] args) { /*第一个线程卖票*/ SyncTread t1 = new SyncTread(); t1.start(); /*第一个线程卖票*/ SyncTread t2 = new SyncTread(); t2.start(); } }
这种方式也是正确的。