一、线程的同步
上一篇手记《多线程的创建与使用》中有个练习题:
模拟火车站售票窗口,开启三个窗口售票,总票数为100
当时我没有考虑线程安全问题,今天我再把它拿出来,用线程的同步机制来实现线程的安全。
在之前那段程序中存在线程安全问题,打印车票时可能出现重复车票以及错票。
那么线程安全问题存在的原因?
由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在安全问题。
2、如何解决线程的安全问题?
必须让一个线程操作共享数据完毕以后,其他线程才有机会参与共享数据的操作
3、java如何实现线程的安全:线程的同步机制
synchronized是java中的一个关键字,也就是说是Java语言内置的特性。
(当然还有其他方式,我就只对synchronized展开来讲)
方式一:同步代码块
synchronized(需要一个任意的对象(锁)){
//需要被同步的代码块(即为操作共享数据的代码)
}
(1)共享数据:多个线程共同操作的同一个数据(变量)
(2)同步监视器:由一个类的对象来充当,哪个线程获取此监视器,谁就执行大括号里被同步的代码。俗称:锁。
要求:所有线程必须共用同一把锁。
注:在实现的方式中,考虑同步的话,可以使用this来充当锁,但在继承的方式中,慎用。
实例:模拟火车站售票窗口,开启三个窗口售票,总票数为100
class Window2 implements Runnable{
int ticket=100;//共享数据
//Object obj=new Object();
public void run() {
while(true){
synchronized(this){//this表示当前对象,即为w,只有一个
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"售出的票号为:"+ticket--);
}
}
}
}
}
public class TestWindow2 {
public static void main(String[] args) {
Window2 w=new Window2();
Thread t1=new Thread(w);
Thread t2=new Thread(w);
Thread t3=new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
方式二:同步方法
将操作共享数据的方法声明为synchronized,即此方法为同步方法,能够保证当其中一个线程执行
此方法时,其他线程在外等待直至此线程执行完此方法。
同步方法的锁:this
实例:模拟火车站售票窗口,开启三个窗口售票,总票数为100
class Window3 extends Thread{
static int ticket=100;//共享数据
Object obj=new Object();
public void run(){
while(true){
show();
}
}
public synchronized void show(){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"售出的票号为:"+ticket--);
}
}
}
public class TestWindow3 {
public static void main(String[] args) {
Window3 w1=new Window3();
Window3 w2=new Window3();
Window3 w3=new Window3();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
4、由于同一个时间只能有一个线程访问共享数据,效率变低了
5、释放锁的操作:
(1)当前线程的同步方法、同步代码块执行结束;
(2)当前线程的同步方法、同步代码块中遇到break、return终止了该方法、该代码块的继续执行;
(3)当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束;
(4)当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停并释放锁。
6、不会释放锁的操作:线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂时停止当前线程的执行。
二、线程的通信
1、wait()与notify()和notifyAll()
(1)wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问;
(2)notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待;
(3)notifyAll():唤醒正在排队等待的所有线程结束等待。
这三个方法只有在synchronized方法或synchronized代码块中才能使用。
实例:
package com.TestThread;
//线程通信
//使用两个线程打印1-100,线程1,线程2交替打印
class PrintNum implements Runnable{
int num=1;
public void run() {
while(true){
synchronized (this) {
notify();
if (num <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
} else {
break;
}
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public class TestCommunication {
public static void main(String[] args) {
PrintNum p=new PrintNum();
Thread t1=new Thread(p);
Thread t2=new Thread(p);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
三、练习
生产者消费者问题
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,
如果店中有空位放产品了再通知生产者继续生产,如果店中没有产品,店员会告诉消费者等一下,
如果店中有产品了再通知消费者来取走产品。
分析:
- 1、是否涉及到多线程问题?是,生产者,消费者
- 2、是否涉及到共享数据?有,产品数量
- 3、是否涉及到线程的通信?有,存在生产者与消费者的通信
示例:
public class TestProduceConsume{
public static void main(String[] args) {
Clerk clerk=new Clerk();
Producer p1=new Producer(clerk);
Consumer c1=new Consumer(clerk);
Thread t1=new Thread(p1);//生产者1
Thread t3=new Thread(p1);//生产者2
Thread t2=new Thread(c1);//消费者
t1.setName("生产者1");
t3.setName("生产者2");
t2.setName("消费者");
t1.start();
t2.start();
t3.start();
}
}
class Clerk{
int product;
public synchronized void addProduct(){//生产产品
if(product>=20){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
product++;
System.out.println(Thread.currentThread().getName()+":生产了第"+product+"个产品");
notifyAll();
}
}
public synchronized void consumeProduct(){//消费产品
if(product<=0){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
System.out.println(Thread.currentThread().getName()+"消费了第"+product+"个产品");
product--;
notifyAll();
}
}
}
class Producer implements Runnable{//生产者
Clerk clerk;
public Producer(Clerk clerk){
this.clerk=clerk;
}
public void run(){
System.out.println("生产者开始生产产品");
while(true){
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
clerk.addProduct();
}
}
}
class Consumer implements Runnable{
Clerk clerk;
public Consumer(Clerk clerk){
this.clerk=clerk;
}
public void run(){
System.out.println("消费者消费产品");
while(true){
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}