总共有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();
}
}
这种方式也是正确的。