实现多线程
进程: 正在运行的程序
1. 是系统进行资源分配和调用的独立单位
1. 每一个进程都有它自己的内存空间和系统资源
线程: 是进程中的单个顺序控制流,是一条执行路径
单线程: 一个进程只有一条执行路径
多线程: 一个进程有多条执行路径
多进程的实现方式 — Thread
Thread概述:
1. 在java.lang包下无需导包
1. entends Object implements **Runnable**
1. 线程是程序中执行的线程, Java虚拟机允许应用程序同时执行多个线程
创建线程:
1. **继承Thread类-- 方式一**
1. 定义一个类(MyThread)声明为Thread的子类,继承Thread
1. (MyThread)这个子类应该重写Thread中的run()方法
1. 创建该子类(MyThread)的实例对象
1. 启动线程
2. 方式一中的问题:
1. 重写run()方法的原因是因为run()方法是用来封装被线程执行的子类代码的
1. run()方法和start()方法区别在于
1. run(): 封装线程执行的代码,直接调用,相当于普通调用
1. **start():启动线程,由JVM调用底层run()方法**
代码实现:
// 定义一个子类继承Thread
public class MyThread extends Thread{
// 因为Thread封装了其余方法,重写run()声明该类是线程
@Override
public void run() {
_for (int i = 0; i < 1000; i++) {
System._out.println(_i); } } }
———————————————————————-
public class ThreadDemo {
public static void main(String[] args) {
// 使用多线程,表示两个线程同时进行
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
// 直接使用run(),系统并不会启动多线程
// 必须使用start()方法启动线程调用run()方法
// myThread1.run();
// myThread2.run();
myThread1.start();
myThread2.start(); } }_
设置线程和获取线程名称
Thread类中设置和获取线程名称的方法
1. void **setName(String name): 将此线程的名称改为参数name**
1. String getName(): 返回此线程的名称
1. Thread **currentThread() 返回对当前正在执行的线程对象的引用**
代码实现:
public class ThreadDemo {
_public static void main(String[] args) {
// 创建多个线程对象,表示两个线程同时进行
// 使用Thread的带参构造方法直接给实例对象命名
// 需要在被实例类中定义带参方法调用父类Thread(String name),使用super()访问方法
MyThread myThread1 = new MyThread(“飞机”);
MyThread myThread2 = new MyThread(“高铁”);
// void setName(String name):将此线程的名称改为参数name
// myThread1.setName(“飞机”);
// myThread2.setName(“高铁”);
myThread1.start();
myThread2.start();
// Thread currentThread():返回对当前正在执行的线程对象的引用
System._out.println(_Thread._currentThread().getName()); // main当前执行名称为main } }
————————————————————————————————————-
// 定义一个子类继承Thread
public class MyThread extends Thread{
// 因为Thread封装了其余方法,重写run()声明该类是线程
@Override
public void run() {
_for (int i = 0; i < 100; i++) {
// 使用Thread的默认gerName()可获取默认的线程名称
// String getName():返回此线程的名称
System._out.println(_getName()+”:”+i); } }
public MyThread() { }
public MyThread(String name) {
super(name); }}
线程调度
线程有两种调度模型
1. 分时调度模型:
1. **所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片**
2. 抢占式调度模型:
2. **优先让优先级高的线程使用CPU, 如果线程的优先级相同, 那么会随机选择一个, 优先级高的线程获取的CPU时间片相对多一些**
Java使用的是第二种调度模型— 抢占式调度模型
假如计算机只有一个CPU, 那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片(也就是CPU使用权)才可以执行指令. 所以所多线程程序的执行是有随机性的,因为谁先抢到CPU的使用权也不一定
Thread类中设置和获取优先级的方法
1. public final int **getPriority(): 返回此线程的优先级**
1. public final void** setPriority(int newPriority): 更改此线程的优先级**
1. 线程默认的优先级是:5
1. 线程优先级的范围是:1-10
1. 线程优先级仅仅表示线程获取的CPU时间片的几率比较高,但是只有运行多次后才能看出效果
代码实现:
public class ThreadPriorityDemo {
_public static void main(String[] args) {
// 实例线程对象,设置线程姓名
ThreadPriority tp1 = new ThreadPriority(“飞机”);
ThreadPriority tp2 = new ThreadPriority(“高铁”);
ThreadPriority tp3 = new ThreadPriority(“汽车”)_;
// public final int getPriority():返回此线程的优先级<br /> System._out_.println_(_tp1.getPriority_())_; // 5<br /> System._out_.println_(_tp2.getPriority_())_; // 5<br /> System._out_.println_(_tp3.getPriority_())_; // 5
// Thread中对线程的优先级有设定最小值为1 默认值为5 最大值为10<br /> System._out_.println_(_Thread._MIN_PRIORITY)_; // 1<br /> System._out_.println_(_Thread._MAX_PRIORITY)_; // 10<br /> System._out_.println_(_Thread._NORM_PRIORITY)_; // 5
// public final void setPriority(int newPriority):更改此线程的优先级<br /> tp1.setPriority_(_1_)_;<br /> tp2.setPriority_(_5_)_;<br /> tp3.setPriority_(_10_)_;<br /> System._out_.println_(_tp1.getPriority_())_; // 1
// 启动线程<br /> tp1.start_()_;<br /> tp2.start_()_;<br /> tp3.start_()_; _} }_<br />_-----------------------------------------------_<br />public class ThreadPriority extends Thread _{<br /> _@Override<br /> public void run_() {<br /> _for _(_int i = 0; i < 100; i++_) {<br /> _System._out_.println_(_getName_() _+ ":" + i_)_; _} }<br /> _public ThreadPriority_() { }<br /> _public ThreadPriority_(_String name_) { _super_(_name_)_; _} }_
线程控制
1. static void **sleep(long millis): 使当前正在执行的线程停留(暂停执行)指定的毫秒数**
1. void** join() 等待这个线程死亡,多线程情况下,有一条线程限制join()方法,其余线程都需要等待该线程结束**
1. void **setDaemon(boolean on): 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出**
void sleep(long millis): 使当前正在执行的线程停留(暂停执行)指定的毫秒数
public class ThreadSleep extends Thread{
// 该类继承Thread类 重写run()方法
@Override
public void run() {
_for (int i = 0; i < 100; i++) {
// 使用Thread方法的getName获取线程方法
System._out.println(_getName()+”:”+i);
// 控制每个线程的进度,设置线程毫秒数,每隔1s执行线程
try {
Thread._sleep(_1000);
} catch (InterruptedException e) {
e.printStackTrace(); } } }
public ThreadSleep() { }
// 重写Thread的String name
public ThreadSleep(String name) { super(name); } }
————————————————————————————————
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep(“刘备”);
ThreadSleep ts2 = new ThreadSleep(“曹操”);
ThreadSleep ts3 = new ThreadSleep(“孙权”);
ts1.start();
ts2.start();
ts3.start(); } }_
void join():等待这个线程死亡,多线程情况下,有一条线程限制join()方法,其余线程都需要等待该线程结束
public class ThreadJoin extends Thread {
@Override
public void run() {
_for (int i = 0; i < 100; i++) {
System._out.println(_getName() + “:” + i); } }
public ThreadJoin() { }
// 重写Thread的String name
public ThreadJoin(String name) { super(name); }}
—————————————————————
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin(“皇阿玛”);
ThreadJoin tj2 = new ThreadJoin(“紫薇”);
ThreadJoin th3 = new ThreadJoin(“小燕子”);
tj1.start();
// 使用等待该线程死亡join()方法,只有该线程结束后其余线程才能运行
try {
tj1.join();
} catch (InterruptedException e) {
e.printStackTrace(); }
tj2.start();
th3.start();
void setDaemon(boolean on): 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
public class TheadDaemon extends Thread {
@Override
public void run() {
_for (int i = 0; i < 100; i++) {
System._out.println(_getName() + “: “ + i); } }
public TheadDaemon(String name) { super(name); }
public TheadDaemon() { }}
————————————————————————
public class ThreadDaemonDemo {
public static void main(String[] args) {
TheadDaemon td1 = new TheadDaemon(“李畅”);
TheadDaemon td2 = new TheadDaemon(“赵明”);
// 设置当前线程为主线程
Thread._currentThread().setName(“龙飞”);
// 设置其余两个线程为守护线程
td1.setDaemon(_true);
td2.setDaemon(true);
td1.start();
td2.start();
for (int i = 0; i < 10; i++) {
System._out.println(_Thread._currentThread().getName() + “:” + i); } } }
线程的生命周期
_
多线程的实现方式— Runnable接口
多线程的实现方案有两种:
1. 实现Runnable接口
2. 继承Thread类
实现Runnable接口
1. 定义一个类实现Runnable接口
2. 在该子类中重写Runnable()的run()方法
3. 创建该类的对象
4. 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
5. 启动线程
实现Runnable接口的好处
1. 避免Java单继承的局限性
2. 适合多个相同程序的代码处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
// 让该类实现Runnable接口,并重写run()方法
public class MyRunnable implements Runnable {
@Override
public void run() {
_for (int i = 0; i < 100; i++) {
// 该类实现了Runnable接口但是没有继承Thread类的方法
// 不能直接使用getName方法,可以通过Thread使用
System._out.println(_Thread._currentThread().getName() + “: “ + i); } }}
——————————————-
public class MyRunnableDemo {
_public static void main(String[] args) {
// 创建实现Runnable接口类的对象
MyRunnable mr = new MyRunnable();
// 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
// 将实现类的对象作为参数传入Thread(Runnable target)
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
// 创建多线程的名字Thread(Runnable target,String name)
Thread t3 = new Thread(mr,”高铁”);
Thread t4 = new Thread(mr,”飞机”);
// 启动线程
t1.start();
t2.start();
t3.start();
t4.start(); }}_
线程同步
案例:卖票
需求: 某电影院目前正在上映国产大片,共有100张票,而他只有3个窗口卖票,请设计一个程序模拟该电影院卖票
思路:
1. 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量 privtae int tickets=100;
1. 在SellTIcket类中重写run()方法实现卖票,代码实现如下:
1. 判断票数大于0 , 卖票,并告知是哪个窗口卖票
1. 卖了票之后,总票数-1
1. 票没有了,也有可能人来问,所以用死循环
3. 定义一个测试类SellTicketDemo,里面有main方法,代码实现如下
1. 创建SellTicket类的对象
1. 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
1. 启动线程
代码实现:
//定义一个类SellTicket实现Runnable接口,
public class SellTicket implements Runnable {
// 里面定义一个成员变量 privtae int tickets=100;
private int tickets = 100;
@Override
public void run() {
//票没有了,也有可能人来问,所以用死循环
while (_true) {
// 判断票数大于0 , 卖票,并告知是哪个窗口卖票
if (tickets > 0) {
System._out.println(Thread.currentThread().getName() + “正在售卖第” + tickets + “票”);
// 卖了票之后,总票数-1
tickets—; } } } }————————————————————————
// 创建SellTicket类的对象
public class SellTicketDemo {
public static void main(String[] args) {
// 创建三个Thread类的对象(实现Runnable接口的类,线程的姓名)
// 把实现了Runnable接口的SellTicket对象作为构造方法的参数
// 并给出对应的窗口名称
SellTicket st = new SellTicket();
Thread t1 = new Thread(st,”一号窗口”);
Thread t2 = new Thread(st,”二号窗口”);
Thread t3 = new Thread(st,”三号窗口”);
// 启动线程
t1.start();
t2.start();
t3.start(); } }
卖票案例的思考
刚才讲解了电影院卖票程序,好像没有什么问题,但是在实际生活汇总,售票时出票也需要时间,所以在每售出一场票的时候,需要有一点时间的延迟,接下来我们去修改卖票程序中卖票的时间
因为出票需要时间,设置每次出票时间为100毫秒,用sleep()方法实现
因为设置sleep()延迟方法,卖票出现了两个问题:
1. **相同的票出现了多次**
1. **出现了负数的票**
问题的原因:线程执行是有随机性的,tickets是公用的同时执行,然后同时—
// 创建SellTicket类的对象
public class SellTicketDemo {
_public static void main(String[] args) throws InterruptedException {
// 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
SellTicket st = new SellTicket();
Thread t1 = new Thread(st,”一号窗口”);
Thread t2 = new Thread(st,”二号窗口”);
Thread t3 = new Thread(st,”三号窗口”);
// 启动线程
t1.start();
t2.start();
t3.start(); } }
——————————————————————————-
//定义一个类SellTicket实现Runnable接口,
public class SellTicket implements Runnable {
// 里面定义一个成员变量 privtae int tickets=100;
private int tickets = 100;
@Override
public void run() {
//票没有了,也有可能人来问,所以用死循环
while (true) {
// 使用sleep()方法设置延迟
// 但是出现了两个问题,相同的票出现多次,并且出现负数
try {
Thread._sleep(_100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断票数大于0 , 卖票,并告知是哪个窗口卖票
if (tickets > 0) {
System._out.println(_Thread._currentThread().getName() + “正在售卖第” + tickets + “票”);
// 卖了票之后,总票数-1
tickets—; } } } }
卖票案例数据安全问题的解决
为什么出现问题?(这也是判断多线程程序是否会有数据安全问题的标准)
1. 是否是多线程环境
1. 是否有共享数据
1. 是否有多条语句操作共享数据
代码分析:
1. 是多线程
Thread t1 = new Thread_(_st,"一号窗口"_)_;<br /> Thread t2 = new Thread_(_st,"二号窗口"_)_;<br /> Thread t3 = new Thread_(_st,"三号窗口"_)_;
2. 有共享数据
private int tickets = 100;
3. 有多条语句操作共享数据
if _(_tickets > 0_) {<br /> _System._out_.println_(_Thread._currentThread()_.getName_() _+ "正在售卖第" + tickets + "票"_)_;<br /> // 卖了票之后,总票数-1<br /> tickets--; _} } } }_<br />如何解决多线程安全问题?<br />解决问题基本思想
1. 让程序没有安全问腿
2. 如何让程序没有安全问题就是让程序不满足上面的三个条件
实现解决问题思想
1. 多线程和共享数据无法更改,只能更改多条语句操作共享数据
1. **把多条语句操作的共享数据锁死,不论何时让多线程只能有一个线程执行该语句**
同步代码块 — synchronized(同步)
想要锁死多条语句操作共享数据,可以使用Java提供的同步代码块实现
- **格式:**
synchronized(任意对象){
多条语句操作共享数据的代码
}
- **synchronized(任意对象): 这个方法相当于将代码枷锁,参数任意对象就是当成一把锁**
同步的好处和弊端
- 好处:解决了多线程的数据安全问题,不会同时执行一个数据
- 弊端:当线程过多时,因为每个线程都会判断执行语句是否有同步锁synchronized(),比较消耗资源,降低程序运行效率
代码逻辑:
// 创建SellTicket类的对象
public class SellTicketDemo {
_public static void main(String[] args) throws InterruptedException {
// 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
SellTicket st = new SellTicket();
Thread t1 = new Thread(st,”一号窗口”);
Thread t2 = new Thread(st,”二号窗口”);
Thread t3 = new Thread(st,”三号窗口”);
// 启动线程
t1.start();
t2.start();
t3.start(); }}
——————————————————————-
//定义一个类SellTicket实现Runnable接口,
public class SellTicket implements Runnable {
// 里面定义一个成员变量 privtae int tickets=100;
private int tickets = 100;
// 因为代码块中新建Object对象是再循环语句执行,所以每个线程执行都要执行该语句相当于执行三把锁
// 在外部创建局部变量,让每个线程共享这一把锁
private Object obj = new Object();
@Override
public void run() {
//票没有了,也有可能人来问,所以用死循环
while (true) {
// 使用synchronized()方法将该语句锁死
// 参数为任意对象Object是所有对象的父类
// 新建一个Object,表示不论哪个对象调用这个语句都可以执行
synchronized (obj){
// 使用sleep()方法设置延迟
// 但是出现了两个问题,相同的票出现多次,并且出现负数
try {
Thread._sleep(_100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断票数大于0 , 卖票,并告知是哪个窗口卖票
if (tickets > 0) {
System._out.println(_Thread._currentThread().getName() + “正在售卖第” + tickets + “票”);
// 卖了票之后,总票数-1
tickets—; } } } } }
同步代码块方法
1. **同步方法就是把synchronized 关键字加到方法上**
1. **格式: 修饰符****synchronized ****返回值类型 方法名(方法参数) { 方法体}**
1. **同步方法的锁对象是 ****this ****当前对象**
2. ** 同步静态方法: 就是把synchronized 关键字加到static 关键字修饰的静态方法上**
1. **格式: 修饰符 ****static synchronized ****返回值类型 方法名(方法参数){ 方法体}**
1. **同步静态方法的锁对象是 类名.class**
代码分析:
1. 定义一个x为0 写if语句判断x%2==0,将synchronized()的代码块分成两个代码块,并且两个代码块都可以是synchronized()锁定
1. 将else里面的synchronized()代码块封装为一个方法SellTicket()
1. 将synchronized作为关键字加到方法体上进行修饰,让该方法为同步代码模块方法
1. 格式: 修饰符synchronized 返回值类型 方法名(方法参数){ 方法体 }
1. 但是通过synchronized 修饰是非静态方法是对方法内部的对象(**this**)进行修饰的,是this这个对象的锁
1. 所以非静态synchronized (任意对象) 参数就是该类的当前对象**this **
1. 如果对象为当前对象,通过**static修饰符**也可以调用该方法,将修饰符synchronized 以及 tickets的修饰符改为static,
1. this 关键字是 [Java](http://c.biancheng.net/java/) 常用的关键字
1. 可用于任何实例方法内指向当前对象
1. 也可指向对其调用当前方法的对象
1. 或者在需要当前类型对象引用时使用
4. 通过同步静态方法:把synchronized修饰符放在static修饰的静态方法上
1. 格式: 修饰符static synchronized 返回值类型 方法名(方法参数){ 方法体 }
1. 通过反射得到参数类synchronized (类名.class)
1. 所以同步静态方法的锁的对象是 **类名.class**
//定义一个类SellTicket实现Runnable接口,
public class SellTicket implements Runnable {
// 里面定义一个成员变量 privtae int tickets=100;
private int tickets = 100;
// 因为代码块中新建Object对象是再循环语句执行,所以每个线程执行都要执行该语句相当于执行三把锁
// 在外部创建局部变量,让每个线程共享这一把锁
private Object obj = new Object();
private int x = 0;
@Override
public void run() {
//票没有了,也有可能人来问,所以用死循环
while (_true) {
if (x % 2 == 0) {
// 使用synchronized()方法将该语句锁死
// 参数为任意对象Object是所有对象的父类
// 新建一个Object,表示不论哪个对象调用这个语句都可以执行
synchronized (obj) {
// 使用sleep()方法设置延迟
// 但是出现了两个问题,相同的票出现多次,并且出现负数
try {
Thread._sleep(_100);
} catch (InterruptedException e) {
e.printStackTrace(); }
// 判断票数大于0 , 卖票,并告知是哪个窗口卖票
if (tickets > 0) {
System._out.println(_Thread._currentThread().getName() + “正在售卖第” + tickets + “票”);
// 卖了票之后,总票数-1
tickets—; } }
} _else {
/synchronized (obj) {
// 使用sleep()方法设置延迟
// 但是出现了两个问题,相同的票出现多次,并且出现负数
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace(); }
// 判断票数大于0 , 卖票,并告知是哪个窗口卖票
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + “正在售卖第” + tickets + “票”);
// 卖了票之后,总票数-1
tickets—; } }/
sellTicket(); }
x++; } }
// private void sellTicket() {
// synchronized (obj) {
// // 使用sleep()方法设置延迟
// // 但是出现了两个问题,相同的票出现多次,并且出现负数
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// // 判断票数大于0 , 卖票,并告知是哪个窗口卖票
// if (tickets > 0) {
// System.out.println(Thread.currentThread().getName() + “正在售卖第” + tickets + “票”);
// // 卖了票之后,总票数-1
// tickets—;
// }
// }
// }
// 再方法添加synchronized修饰符表示该方法被锁
private synchronized void sellTicket() {
// 使用sleep()方法设置延迟
// 但是出现了两个问题,相同的票出现多次,并且出现负数
try { Thread._sleep(_100); } catch (InterruptedException e) {
e.printStackTrace(); }
// 判断票数大于0 , 卖票,并告知是哪个窗口卖票
if (tickets > 0) {
System._out.println(_Thread._currentThread().getName() + “正在售卖第” + tickets + “票”);
// 卖了票之后,总票数-1
tickets—; } } }
线程安全的类
StringBuffer:
- 线程安全,可变的字符序列。 字符串缓冲区就像一个String ,但可以修改。 在任何时间点,它包含一些特定的字符序列,但可以通过某些方法调用来更改序列的长度和内容。
- 从版本JDK 5开始,这个类别已经被一个等级类补充了,这个类被设计为使用一个线程StringBuilder 。 StringBuilder应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步。
Vector — list
- Vector类实现了可扩展的对象数组。像数组一样,它包含可以使用整数索引访问的组件。但是, Vector的大小可以根据需要增长或缩小,以适应在创建Vector之后添加和删除项目
- 从Java 2平台v1.2,这个类被改造为实现List接口,使其成为成员Java Collections Framework 。 与新集合实现不同, Vector是同步的。 如果不需要线程安全的实现,建议使用ArrayList代替Vector
HashTable — map
- 该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键值或值。
- 从Java 2平台v1.2,这个类被改造为实现Map接口,使其成为成员Java Collections Framework 。 与新的集合实现不同, Hashtable是同步的。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable 。 如果需要线程安全的并发实现,那么建议使用ConcurrentHashMap代替Hashtable 。
- **HashTable,StringBuffer,Vector 底层都是被synchronized static 同步静态方法修饰的**
- public static <T> [List](../../java/util/List.html)<T> synchronizedList([List](../../java/util/List.html)<T> list)
- 返回由指定列表支持的同步(线程安全)列表。 为了保证串行访问,重要的是通过返回的列表完成对后台列表的所有访问
- 使用List<?> list = Collections.synchronizedList(new ArrayList<>()); 可以使ArrayList具有线程安全性
Lock锁
Lock锁的由来
虽然之前使用synchronized(任意对象)对代码加锁,理解同步代码块和同步方法的锁对象问题,但是没有直观的看到synchronized在哪里给代码加了锁,又在哪里解开锁.Java为了更清晰的表达加锁和解锁,JDK5以后提供了一个新的锁对象Lock
Lock锁介绍
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作
Lock是个Interface 接口不能直接实例对象,使用ReentrantLock类实现Lock
Lock锁方法
- **void lock() 获得锁。 **
- **void unlock() 释放锁。**
ReentrantLock实现类介绍
实现接口Lock,并且与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。
ReentrantLock构造方法
ReentrantLock() 创建一个 ReentrantLock的实例。
public class SellLock implements Runnable{
_private int tickets =100;
// Lock是接口,使用Lock的实例类
// ReentrantLock()无参构造方法创建一个ReentrantLock的实例。
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
// 但是未来避免程序出现问题,加锁使用try捕捉代码块
try {
// 使用Lock类中的lock()方法对下面代码块加锁
lock.lock();
try {
Thread._sleep(_100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 判断票数大于0 , 卖票,并告知是哪个窗口卖票
if (tickets > 0) {
System._out.println(_Thread._currentThread().getName() + “正在售卖第” + tickets + “票”);
// 卖了票之后,总票数-1
tickets—;
}
}_finally {
// 再使用Lock类中的unlock()方法给上面的代码块解锁
// 使用try捕捉异常finally修饰解锁
lock.unlock(); } } }}_
生产者和消费者模式概述
生产者和消费者模式概述
生产者消费者模式是一个经典的多线程协作模式, 弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻,所谓生产者消费者问题,主要包含两类线程:
- **一类是生产者线程用于生产数据**
- **一类是消费者线程用于消费数据**
未来解耦生产者和消费者的关系,通常会使用共享的数据区域,类似与一种仓库
- **生产者生产数据之后直接放置再共享数据中,并不需要关心消费者的行为**
- **消费者只需要从共享数据区去获取数据,并不需要关心生产者的行为**
为了体现生产和消费过程中的等待和唤醒,Java提供了几个方法供我们使用,这几个方法再
Object类中Object类的等待和唤醒方法:
void wait() 导致当前线程等待
直到另一个线程调用该对象的notify()方法或者notifyAll()方法
void notify() 唤醒正在等待对象监视器的单个线程
void notifyAll() 唤醒正在等待对象监视器的所有线程
生产者和消费者模式案例
生产者消费者案例中包含的类:
- 奶箱类(Box): 定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
- 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
- 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
- 测试类(main): 里面有main方法,main方法中的代码步骤如下
- 测试类步骤:
1. 创建奶箱对象,将其定义为共享数据区域
1. 创建生产者对象,把奶箱对象座位构造方法参数传递, 因为在这个类中要调用存储牛奶的操作
1. 创建消费者对象,把乃相对性座位构造方法参数传递,因为在这个类中要调用获取牛奶的操作
1. 创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
1. 启动线程
// 奶箱类(Box): 定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
public class Box {
** **// 定义一个成员变量,一共有多少瓶奶
private int milk;
// 定义一个成员变量,判断奶箱的状态,默认奶箱中没有牛奶定义false
private boolean state = false;
// 一键生成get,set方法,使用synchroniezd修饰方法让放牛奶方法变成同步代码块
public synchronized void setMilk(_int milk) {
** _// 如果有牛奶(默认没有为false),等待消费
// 使用java中的wait()方法让该线程等待
// 该方法是放牛奶,判断状态
if (_state) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace(); } }
_// 先走上面的判断语句判断是否有牛奶
// state为true表示有奶,执行wait()等待消费者消费就行
// 如果state判断为false,则不走判断语句的wait()等待消费
// 而是进行下面操作生产牛奶
this.milk = milk;
System.out.println(“送奶工将第:” + this.milk + “奶放入”);
// 生产牛奶后表示有牛奶,所以将牛奶的状态改为true
// 状态state为true以后多线程消费者同时执行setMilk方法
state = true;
// 每次执行完该操作 唤醒其他线程
notifyAll(); }
_public synchronized void getMilk() {
_// 如果没有牛奶,应该等待生产
if (!state) {
_try {
wait();
} catch (InterruptedException e) {
e.printStackTrace(); } }
_// 因为是多线程同时执行,state是true同时执行getMilk用户消费
// 但是判断语句的state为false,消费就需要等待因为没有牛奶了
// 需要执行wait()等待setMilk添加牛奶
System.out.println(“用户拿到第:” + this.milk + “奶”);
// 消费完毕后,修改奶箱的状态为false
state = false;
// 每次执行完该操作 唤醒其他线程
notifyAll(); } }
——————————————————————-
// 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
public class Customer implements Runnable {
_private Box box;
public Customer(Box box) {
_// 同理该box是Customer类自定义的对象
// 需要创建成员对象Box的实例对象box
// 该Box是Customer类自定义的成员类,和Box类无关
// 主要目的是将Customer类的box指向Box类
this.box = box; }
@Override
public void run_() {
_// 消费者可以无限获取牛奶
while (_true) {
box.getMilk(); } }}
———————————————————————
_// 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
public class Producer implements Runnable _{
_// 创建Box类的实例对象将其变成Producer类的成员对象
private Box box;
// 使用构造函数,将整个Box类传递过来
// 使用this.box 让Producer类实例的box=Box类的实例对象
public Producer(_Box box) {
this.box = box; }
_// Producer类的box因为构造方法指向了Box类的实例对象所以可以调用Box类的方法
@Override
public void run() {
_for (int i = 1; i <= 5; i++) {
box.setMilk(i); } } }
————————————————————————-
public class BoxDEMO {
public static void main(String[] args) {
_// 创建奶箱对象,将其定义为共享数据区域
Box box = new Box();
// 创建生产者对象,把奶箱对象座位构造方法参数传递, 因为在这个类中要调用存储牛奶的操作
Producer producer = new Producer(_box)_;
// 创建消费者对象,把乃相对性座位构造方法参数传递,因为在这个类中要调用获取牛奶的操作
Customer customer = new Customer(_box)_;
// 创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递**
Thread t1 = new Thread(_producer, “生产者”);
Thread t2 = new Thread(customer, “消费者”);
// 启动线程
t1.start();
t2.start(); } }