实现多线程

进程: 正在运行的程序

  1. 1. 是系统进行资源分配和调用的独立单位
  2. 1. 每一个进程都有它自己的内存空间和系统资源

线程: 是进程中的单个顺序控制流,是一条执行路径
单线程: 一个进程只有一条执行路径
多线程: 一个进程有多条执行路径

多进程的实现方式 — Thread

Thread概述:

  1. 1. java.lang包下无需导包
  2. 1. entends Object implements **Runnable**
  3. 1. 线程是程序中执行的线程, Java虚拟机允许应用程序同时执行多个线程

创建线程:

  1. 1. **继承Thread类-- 方式一**
  2. 1. 定义一个类(MyThread)声明为Thread的子类,继承Thread
  3. 1. (MyThread)这个子类应该重写Thread中的run()方法
  4. 1. 创建该子类(MyThread)的实例对象
  5. 1. 启动线程
  6. 2. 方式一中的问题:
  7. 1. 重写run()方法的原因是因为run()方法是用来封装被线程执行的子类代码的
  8. 1. run()方法和start()方法区别在于
  9. 1. run(): 封装线程执行的代码,直接调用,相当于普通调用
  10. 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. 1. void **setName(String name): 将此线程的名称改为参数name**
  2. 1. String getName(): 返回此线程的名称
  3. 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. 1. **所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片**

2. 抢占式调度模型:

  1. 2. **优先让优先级高的线程使用CPU, 如果线程的优先级相同, 那么会随机选择一个, 优先级高的线程获取的CPU时间片相对多一些**

Java使用的是第二种调度模型— 抢占式调度模型

假如计算机只有一个CPU, 那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片(也就是CPU使用权)才可以执行指令. 所以所多线程程序的执行是有随机性的,因为谁先抢到CPU的使用权也不一定

Thread类中设置和获取优先级的方法

  1. 1. public final int **getPriority(): 返回此线程的优先级**
  2. 1. public final void** setPriority(int newPriority): 更改此线程的优先级**
  3. 1. 线程默认的优先级是:5
  4. 1. 线程优先级的范围是:1-10
  5. 1. 线程优先级仅仅表示线程获取的CPU时间片的几率比较高,但是只有运行多次后才能看出效果

代码实现:
public class ThreadPriorityDemo {
_public static void main
(String[] args) {
// 实例线程对象,设置线程姓名
ThreadPriority tp1 = new ThreadPriority
(“飞机”);
ThreadPriority tp2 = new ThreadPriority
(“高铁”);
ThreadPriority tp3 = new ThreadPriority
(“汽车”)_;

  1. // 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
  2. // 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
  3. // 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
  4. // 启动线程<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. 1. static void **sleep(long millis): 使当前正在执行的线程停留(暂停执行)指定的毫秒数**
  2. 1. void** join() 等待这个线程死亡,多线程情况下,有一条线程限制join()方法,其余线程都需要等待该线程结束**
  3. 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); } } }

线程的生命周期

image.png_

_

多线程的实现方式— 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. 1. 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量 privtae int tickets=100;
  2. 1. SellTIcket类中重写run()方法实现卖票,代码实现如下:
  3. 1. 判断票数大于0 卖票,并告知是哪个窗口卖票
  4. 1. 卖了票之后,总票数-1
  5. 1. 票没有了,也有可能人来问,所以用死循环
  6. 3. 定义一个测试类SellTicketDemo,里面有main方法,代码实现如下
  7. 1. 创建SellTicket类的对象
  8. 1. 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
  9. 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. **相同的票出现了多次**
  2. 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. 是否是多线程环境
  2. 1. 是否有共享数据
  3. 1. 是否有多条语句操作共享数据

代码分析:

  1. 1. 是多线程
  2. Thread t1 = new Thread_(_st,"一号窗口"_)_;<br /> Thread t2 = new Thread_(_st,"二号窗口"_)_;<br /> Thread t3 = new Thread_(_st,"三号窗口"_)_;
  3. 2. 有共享数据
  4. private int tickets = 100;
  5. 3. 有多条语句操作共享数据
  6. if _(_tickets > 0_) {<br /> _System._out_.println_(_Thread._currentThread()_.getName_() _+ "正在售卖第" + tickets + "票"_)_;<br /> // 卖了票之后,总票数-1<br /> tickets--; _} } } }_<br />如何解决多线程安全问题?<br />解决问题基本思想
  7. 1. 让程序没有安全问腿
  8. 2. 如何让程序没有安全问题就是让程序不满足上面的三个条件

实现解决问题思想

  1. 1. 多线程和共享数据无法更改,只能更改多条语句操作共享数据
  2. 1. **把多条语句操作的共享数据锁死,不论何时让多线程只能有一个线程执行该语句**

同步代码块 — synchronized(同步)

想要锁死多条语句操作共享数据,可以使用Java提供的同步代码块实现

  1. - **格式:**

synchronized(任意对象){
多条语句操作共享数据的代码
}

  1. - **synchronized(任意对象): 这个方法相当于将代码枷锁,参数任意对象就是当成一把锁**

同步的好处和弊端

  1. - 好处:解决了多线程的数据安全问题,不会同时执行一个数据
  2. - 弊端:当线程过多时,因为每个线程都会判断执行语句是否有同步锁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. 1. **同步方法就是把synchronized 关键字加到方法上**
  2. 1. **格式: 修饰符****synchronized ****返回值类型 方法名(方法参数) { 方法体}**
  3. 1. **同步方法的锁对象是 ****this ****当前对象**
  4. 2. ** 同步静态方法: 就是把synchronized 关键字加到static 关键字修饰的静态方法上**
  5. 1. **格式: 修饰符 ****static synchronized ****返回值类型 方法名(方法参数){ 方法体}**
  6. 1. **同步静态方法的锁对象是 类名.class**

代码分析:

  1. 1. 定义一个x0 if语句判断x%2==0,将synchronized()的代码块分成两个代码块,并且两个代码块都可以是synchronized()锁定
  2. 1. else里面的synchronized()代码块封装为一个方法SellTicket()
  3. 1. synchronized作为关键字加到方法体上进行修饰,让该方法为同步代码模块方法
  4. 1. 格式: 修饰符synchronized 返回值类型 方法名(方法参数){ 方法体 }
  5. 1. 但是通过synchronized 修饰是非静态方法是对方法内部的对象(**this**)进行修饰的,是this这个对象的锁
  6. 1. 所以非静态synchronized (任意对象) 参数就是该类的当前对象**this **
  7. 1. 如果对象为当前对象,通过**static修饰符**也可以调用该方法,将修饰符synchronized 以及 tickets的修饰符改为static,
  8. 1. this 关键字是 [Java](http://c.biancheng.net/java/) 常用的关键字
  9. 1. 可用于任何实例方法内指向当前对象
  10. 1. 也可指向对其调用当前方法的对象
  11. 1. 或者在需要当前类型对象引用时使用
  12. 4. 通过同步静态方法:把synchronized修饰符放在static修饰的静态方法上
  13. 1. 格式: 修饰符static synchronized 返回值类型 方法名(方法参数){ 方法体 }
  14. 1. 通过反射得到参数类synchronized (类名.class)
  15. 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:

  1. - 线程安全,可变的字符序列。 字符串缓冲区就像一个String ,但可以修改。 在任何时间点,它包含一些特定的字符序列,但可以通过某些方法调用来更改序列的长度和内容。
  2. - 从版本JDK 5开始,这个类别已经被一个等级类补充了,这个类被设计为使用一个线程StringBuilder StringBuilder应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步。

Vector — list

  1. - Vector类实现了可扩展的对象数组。像数组一样,它包含可以使用整数索引访问的组件。但是, Vector的大小可以根据需要增长或缩小,以适应在创建Vector之后添加和删除项目
  2. - Java 2平台v1.2,这个类被改造为实现List接口,使其成为成员Java Collections Framework 与新集合实现不同, Vector是同步的。 如果不需要线程安全的实现,建议使用ArrayList代替Vector

HashTable — map

  1. - 该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键值或值。
  2. - Java 2平台v1.2,这个类被改造为实现Map接口,使其成为成员Java Collections Framework 与新的集合实现不同, Hashtable是同步的。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable 如果需要线程安全的并发实现,那么建议使用ConcurrentHashMap代替Hashtable
  3. - **HashTable,StringBuffer,Vector 底层都是被synchronized static 同步静态方法修饰的**
  4. - public static <T> [List](../../java/util/List.html)<T> synchronizedList([List](../../java/util/List.html)<T> list)
  5. - 返回由指定列表支持的同步(线程安全)列表。 为了保证串行访问,重要的是通过返回的列表完成对后台列表的所有访问
  6. - 使用List<?> list = Collections.synchronizedList(new ArrayList<>()); 可以使ArrayList具有线程安全性

Lock锁

Lock锁的由来

虽然之前使用synchronized(任意对象)对代码加锁,理解同步代码块和同步方法的锁对象问题,但是没有直观的看到synchronized在哪里给代码加了锁,又在哪里解开锁.Java为了更清晰的表达加锁和解锁,JDK5以后提供了一个新的锁对象Lock

Lock锁介绍

Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作
Lock是个Interface 接口不能直接实例对象,使用ReentrantLock类实现Lock

Lock锁方法

  1. - **void lock() 获得锁。 **
  2. - **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
(); } } }}_

生产者和消费者模式概述

生产者和消费者模式概述

生产者消费者模式是一个经典的多线程协作模式, 弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻,所谓生产者消费者问题,主要包含两类线程:

  1. - **一类是生产者线程用于生产数据**
  2. - **一类是消费者线程用于消费数据**

未来解耦生产者和消费者的关系,通常会使用共享的数据区域,类似与一种仓库

  1. - **生产者生产数据之后直接放置再共享数据中,并不需要关心消费者的行为**
  2. - **消费者只需要从共享数据区去获取数据,并不需要关心生产者的行为**

image.png
为了体现生产和消费过程中的等待和唤醒,Java提供了几个方法供我们使用,这几个方法再
Object类中Object类的等待和唤醒方法:

void wait() 导致当前线程等待
直到另一个线程调用该对象的notify()方法或者notifyAll()方法
void notify() 唤醒正在等待对象监视器的单个线程
void notifyAll() 唤醒正在等待对象监视器的所有线程

生产者和消费者模式案例

生产者消费者案例中包含的类:

  1. - 奶箱类(Box): 定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
  2. - 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作
  3. - 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作
  4. - 测试类(main): 里面有main方法,main方法中的代码步骤如下
  5. - 测试类步骤:
  6. 1. 创建奶箱对象,将其定义为共享数据区域
  7. 1. 创建生产者对象,把奶箱对象座位构造方法参数传递, 因为在这个类中要调用存储牛奶的操作
  8. 1. 创建消费者对象,把乃相对性座位构造方法参数传递,因为在这个类中要调用获取牛奶的操作
  9. 1. 创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
  10. 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
(); } }