初识JAVA多线程(二)

文章目录

  • 线程同步机制
    • 什么是锁?
    • 什么是线程不安全?
    • 线程与锁
    • synchronized关键字
    • Lock接口
    • synchronized与Lock接口的比较。
  • 线程的通信
    • 生产者与消费者模式
          • 简介:
          • 作用:
            • wait()/notify()方法

线程同步机制

问题引入:
车票问题。(多个窗口对车票这一对象进行操作)

什么是锁?

每个对象都有一把锁,而一把锁只对应一个钥匙,只有当线程对象获取到对应锁的钥匙才可以对对象进行操作,只有唯一拥有钥匙的线程才可以进行资源的访问与操作。

什么是线程不安全?

  • 线程不安全的案例:
public class Test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Ticket11 ticket = new Ticket11();
		Thread t1 = new Thread(ticket,"1号窗口");
		Thread t2 = new Thread(ticket,"2号窗口");
		Thread t3 = new Thread(ticket,"3号窗口");
		t1.start();
		t1.setPriority(1);
		t2.start();
		t2.setPriority(2);
		t3.start();
		t3.setPriority(10);
	}
}

class Ticket11 implements Runnable{
	int num = 10;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
		//	synchronized(this) {
			if(num > 1) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"剩余票数为: " + --num);
				}
			else {
				break;
			}
		//	}
		}
	}
}

运行结果:
初识JAVA多线程(二)_第1张图片
票数出现负数,线程不安全

  • 原因:
    线程干扰:多个窗口同时对车票这一对象进行操作。

1、 1 、2、3号窗口同时对票数进行操作。
2、 当票数为1时,3个窗口依然对票数进行操作,此时对于每一个进程来说票数的值为1,三个进程同时对车票进行操作、票数出现-1。

线程与锁

  • 每个线程都有一把对应的锁。
  • 锁可以解决多个线程操作同一资源而可能引发的一系列安全问题(如上例中剩余票数出现负数,即为线程不安全)。

synchronized关键字

	synchronized(同步):
		1. 同步方法;
		2. 同步代码块;
	synchronized的作用:
		1、当一个线程对象在调用同步方法时,所有其他调用此方法的线程都将被阻塞,知道当前方法执行完毕。
		2、当一个线程对象在对某一字段进行读改操作时,所有其他对此实例操作的对象都将进入阻塞状态,直至当前操作结束。
		3、当一个同步方法退出后,会自动建立与后续调用的同步方法(相同对象)的一个happens-before关系,保证所有线程对对象状态的修改可见。

将可能产生线程安全问题的资源(变量或者方法)使用synchronized关键字进行修饰,使其变为同步资源。

优化案例代码。

public class Test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Ticket11 ticket = new Ticket11();
		Thread t1 = new Thread(ticket,"1号窗口");
		Thread t2 = new Thread(ticket,"2号窗口");
		Thread t3 = new Thread(ticket,"3号窗口");
		t1.start();
		t1.setPriority(1);
		t2.start();
		t2.setPriority(2);
		t3.start();
		t3.setPriority(10);
	}
}

class Ticket11 implements Runnable{
	int num = 10;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		synchronized(this){
		while(true){
		//	synchronized(this) {
			if(num > 1) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"剩余票数为: " + --num);
				}
			else {
				break;
			}
		//	}
		}
		}
	}
}

Lock接口


jdk1.5之后新增的。

Lock是锁的根接口之一,Lock代表实现类是ReentrantLock(可重入锁)。

优化案例代码:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Ticket11 ticket = new Ticket11();
		Thread t1 = new Thread(ticket,"1号窗口");
		Thread t2 = new Thread(ticket,"2号窗口");
		Thread t3 = new Thread(ticket,"3号窗口");
		t1.start();
		t1.setPriority(1);
		t2.start();
		t2.setPriority(2);
		t3.start();
		t3.setPriority(10);
	}
}

class Ticket11 implements Runnable{
	int num = 100;
	Lock lock = new ReentrantLock();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			lock.lock();
		//	synchronized(this) {
			try {
			if(num > 1) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+"剩余票数为: " + --num);
				}else	break;
			}finally {
				lock.unlock();
			}
		//	}
		}
	}
}

Lock接口可以实现的六个方法。

// 获取锁  
void lock()   

// 如果当前线程未被中断,则获取锁,可以响应中断  
void lockInterruptibly()   

// 返回绑定到此 Lock 实例的新 Condition 实例  
Condition newCondition()   

// 仅在调用时锁为空闲状态才获取该锁,可以响应中断  
boolean tryLock()   

// 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁  
boolean tryLock(long time, TimeUnit unit)   

// 释放锁  
void unlock()

synchronized与Lock接口的比较。

  • synchronized是JAVA的一种关键字,是内置属性,Lock是一个类,需要导入外部包才可以使用。
  • synchronized是隐式锁,不可以手动释放锁,只能等待CPU去自动释放锁。Lock是显示锁,可以手动开启,手动关闭。并且Lock类必须手动关闭锁,否则会引起死锁等线程不安全的问题。
  • synchronized关键字释放锁只能等待Cpu的 释放,会造成时间与资源的浪费。

(1)、该对象的代码块儿执行结束。
(2)、由于代码抛出异常而导致jvm释放锁。
(3)、由于该线程使用了wait()方法导致锁被释放。

线程的通信

wait()、notify()与notifyAll():

1、三种方法均为本地方法、且被finally修饰,无法被重写。
2、调用wait()方法可以使当前线程进入阻塞状态并且释放当前对象的锁。前提是当前线程拥有此对象的锁。
3、调用notify()方法,可以使被wait()方法放入阻塞状态的线程重新唤醒。只能唤醒一个线程。
4、调用notifyAll()方法可以唤醒所有正处于阻塞状态的线程全部唤醒。

注意事项:

1、由于wait()方法需要获取对象锁,所以wait()方法只能在同步方法内调用。
2、notify()与notifyAll()方法只能保证处于阻塞状态的线程被唤醒,而具体执行哪个线程由CPU的调度决定。

生产者与消费者模式

····不是23种设计模式之一。

简介:

产生数据的模块叫做生产者,处理数据的模块叫做消费者。数据从生产结束到消费的过程中临时存放的区域叫做缓冲区。
示例:
···饭店后厨炒菜是生产者,传菜员将菜从后厨送到客人桌子上,此时传菜员看做缓冲区,客人把菜吃了,成为消费者。

作用:

可以使用缓冲区来暂时存放数据,使得生产者与消费者更好的实现同步机制,减少空间与时间的浪费,提高运行效率

wait()/notify()方法

当缓冲区未达到某一状态时,消费者处于等待状态,生产者处于唤醒状态。
当缓冲区达到某一状态时,消费者被唤醒,生产者进入等待状态。

代码实现:

import java.util.LinkedList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Warehouse warehouse = new Warehouse();
		Thread t1 = new Thread(new product(warehouse),"1号生产者");
		Thread t2 = new Thread(new product(warehouse),"2号生产者");
		Thread t3 = new Thread(new product(warehouse),"3号生产者");
		Thread p1 = new Thread(new com(warehouse),"1号消费者");
		Thread p2 = new Thread(new com(warehouse),"2号消费者");
		t1.start();
		t2.start();
		t3.start();
		p1.start();
		p2.start();
	}
}

class Warehouse{
	int MAX_WAREHOUSE = 10;
	private LinkedList<Object> list = new LinkedList<>();
	public void Produce() {
		synchronized(list){
			while(list.size() == MAX_WAREHOUSE) {
				System.out.println(Thread.currentThread().getName()+"仓库已满!");
				try {
					list.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			list.add(new Object());
			System.out.println(Thread.currentThread().getName()+"生产了" + list.size()+ "个产品");
			list.notifyAll();
		}
	}
	
	public void Constumer() {
		synchronized(list) {
			while(list.size() == 0) {
				System.out.println(Thread.currentThread().getName() + "仓库已空,请生产!");
				try {
					list.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			list.remove();
			System.out.println(Thread.currentThread().getName()+"消费了1个产品,还剩余"+list.size()+"个产品");
			list.notifyAll();
		}
	}
}


class product implements Runnable{
	Warehouse warehouse;
	public product(){}
	public product(Warehouse Warehouse) {
		this.warehouse = Warehouse;
	}
	int i = 0;
	@Override
	public void run() {
		// TODO Auto-generated method stub
	while(i < 20) {
			try {
				Thread.sleep(100);
				i++;
				warehouse.Produce();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}	
}
class com implements Runnable{
	Warehouse warehouse;
	public com() {}
	public com(Warehouse warehouse) {
		this.warehouse = warehouse;
	}
	int i = 0;
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(i < 10) {
			try {
				Thread.sleep(300);
				i++;
				warehouse.Constumer();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		}
	}	
}

PS:下一篇,JAVA中的数据结构。继续坚持!

你可能感兴趣的:(java,多线程)