这一节主要讲解一个问题:线程的同步。其中主要的难点就是买票、生产和消费
线程同步产生的原因
if (票数大于零) { 卖一张票; 票数减1; }
现在如果有A、B、C三位顾客同时买票,假如最后剩下一张票,当A顾客去买票时,判断票数大于零,票数还没有减1,系统又检测顾客B票数大于零,让B也去买票了,C顾客也是。这样就导致了一张票同时卖给了A、B、C三个顾客。这主要是线程同步问题导致的,因为计算机的CPU速度很快在A还没买完票的同时又同时让B、C进入了线程导致的。CPU可以在任何时间点切换线程。
class A implements Runnable {
public static int tickets = 100;
public void run() {
while(true) {
if (tickets > 0) {
System.out.printf("%s线程正在卖出第%d张票\n",
Thread.currentThread().getName(), tickets);
--tickets;
} else {
break;
}
}
}
}
public class TestTickets {
public static void main(String[] args) {
A aa1 = new A();
A aa2 = new A();
Thread t1 = new Thread(aa1);
Thread t2 = new Thread(aa2);
t1.start();
t2.start();
}
}
总结:他们处理的是同一个资源,所以就有线程同步和互斥的问题
买票例子改进 — synchronized
- synchronized可以用来修饰:
- 一个方法
- 一个方法内部的某个代码块
Synchronized修饰代码块
- 格式:
synchronized(类对象名aa) // 1行
{
同步代码 // 3行
} // 4行
- 功能:synchronized(类对象名aa)的含义是:判断aa是否已经被其他线程霸占,如果发现已经被其他线程霸占,则当前线程陷入等待中,如果发现aa没有被其他线程霸占,则当前线程霸占住aa对象,并执行第3行的同步代码块,在当前线程执行第3行代码块时,其他线程将无法再执行第3行的代码(因为当前线程已经霸占了aa对象),当前线程执行完第3行的代码块后,会自动释放对aa对象的霸占,此时其他线程会相互竞争对aa的霸占,最终CPU会选择其中某一个线程执行。
- 最终导致的结果是:一个线程正在操作某资源的时候,将不允许其他线程操作该资源,即一次只允许一个线程处理该资源
Synchronized修饰方法
- Synchronized修饰一个方法时,实际霸占的是该方法的this指针所指向的对象
- 即Synchronized修饰一个方法时,实际霸占的是正在调用该方法的对象
- 注意:霸占的专业术语叫锁定,霸占住的那个对象专业术语叫做监听器
class A implements Runnable {
public int tickets = 100;
String str = new String("哈哈");
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 TestTickets {
public static void main(String[] args) {
A aa = new A();
Thread t1 = new Thread(aa);
Thread t2 = new Thread(aa);
t1.start();
t2.start();
}
}
本程序开启了两个线程,访问的是同一个资源,并且用synchronized 锁定同一对象,达到多线程互斥执行。
class A extends Thread {
public static int tickets = 100;
public static String str = new String("哈哈"); // 一定要加static关键字
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 TestTickets {
public static void main(String[] args) {
A aa1 = new A();
A aa2 = new A();
aa1.start();
aa2.start();
}
}
第二种方式:创建了两个线程,那么如何保证两个线程访问的是同一个资源,这个时候我们必须在str对象(监听器)前面加一个static关键字,保证synchronized霸占的是同一个对象。虽然是多线程在进行买票,但是当一个线程进入synchronized,此时CPU只允许一个线程运行synchronized代码块的内容,所有达到了线程互斥的效果。
class A implements Runnable {
public int tickets = 100;
String str = new String("哈哈");
public synchronized void run() {
while(true) {
// synchronized (str) {
if (tickets > 0) {
System.out.printf("%s线程正在卖出第%d张票\n",
Thread.currentThread().getName(), tickets);
--tickets;
} else {
break;
}
}
// }
}
}
public class TestTickets {
public static void main(String[] args) {
A aa = new A();
Thread t1 = new Thread(aa);
Thread t2 = new Thread(aa);
t1.start();
t2.start();
}
}
这个程序说明,只有一个线程在买票,因为一旦进入线程的while循环,要等到买完票才能退出循环。