0%

多线程笔记(狂神)

1、实现多线程:三种实现方式、线程优先级
2.、线程的状态和控制:线程的生命周期、线程的状态控制函数
3.、线程同步:买票问题、synchronized关键字和ReentrantLock可重入锁
4、 线程通信:生产者消费者案例
5、线程池:线程池的简单使用
6、静态代理:线程实现的底层模式
7、 lambda表达式:简单使用

1. 实现多线程

1.1进程和线程的区别【背诵】

进程:是正在运行的程序。是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。线程:是CPU调度和分派的基本单位。它可与同属一个进程的其他的线程共享进程所拥有的全部资源。区别如下:
1、根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
2、开销方面:进程之间切换开销大线程之间切换的开销小
3、所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
4、内存分配:系统为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存。线程组之间只能共享资源。
5、包含关系:线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

1.2实现多线程:继承Thread类 【应用 】

实现步骤:

  1. 定义一个类MyThread继承Thread类

  2. 在MyThread类中重写run()方法

  3. 创建MyThread类的对象

  4. 启动线程

run()方法和start()方法的区别:

  • 调用run()方法,是串行执行,先执行完run方法体中的所有内容、再执行后面的命令
  • 调用start()方法,是多线程执行,轮流交替的执行

实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//MyThreadDemo类 执行多线程
/*
结果显示两个线程交替执行
*/
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread("高铁");
MyThread my2 = new MyThread("飞机");
MyThread my3 = new MyThread();
my1.start();
my2.start();
my3.start();
}
}
//MyThread类 继承Thread类
public class MyThread extends Thread {
public MyThread(){}
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int i =0;i<100;i++){
System.out.println(getName()+":"+i);
}
}
}

1.3实现多线程:实现Runnable接口【应用】

实现步骤

  1. 定义一个类MyRunnable实现Runnable接口
  2. 在MyRunnable类中重写run()方法
  3. 创建MyRunnable类的对象
  4. 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
  5. 启动线程

Thread的构造方法

  • Thread(Runnable target) 分配一个新的Thread对象
  • Thread(Runnable target, String name) 分配一个新的Thread对象、并命名

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyRunnableDemo {
public static void main(String[] args) {
MyRunnable myRun = new MyRunnable();
// 分配一个新的Thread对象、并命名
Thread thread1 = new Thread(myRun,"飞机");
Thread thread2 = new Thread(myRun,"高铁");
thread1.start();
thread2.start();
}
}

public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//输出线程名和序号i
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}

实现Runnable接口的优点

  1. 接口可以实现多个,避免了Java单继承的局限性
  2. 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想

1.4实现多线程:实现 Callable接口【了解】

实现步骤

  1. 定义一个类Mycallable类,重写call方法,有返回值
  2. 定一个测试类,创建Mycallable类的对象myCallable
  3. 使用FutureTask来包装Callable对象,得到task对象
  4. 使用task对象 创建线程Thread(task,”name”)
  5. 启动线程,还可以获得线程最后的返回值

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.concurrent.*;
public class MyCallableDemo {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
// 使用FutureTask来包装Callable对象
FutureTask<Integer> task1 = new FutureTask<Integer>(myCallable);
FutureTask<Integer> task2 = new FutureTask<Integer>(myCallable);
//创建线程
Thread thread1 = new Thread(task1,"飞机");
Thread thread2 = new Thread(task2,"火车");
thread1.start();
thread2.start();
//可以获取返回值
try {
System.out.println(thread1.getName()+task1.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}

import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
//重写call方法
@Override
public Integer call() throws Exception {
int i = 0;
for (i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return i;
}
}

实现Callable接口的优点

  1. 接口可以实现多个,避免了Java单继承的局限性
  2. 可以有返回值,可以抛出异常

1.5设置和获取线程名称 【应用】

Thread类中设置和获取线程名称的方法

  • void setName(String name) 将此线程的名称更改为等于参数name

  • String getName() 返回此线程的名称

  • 获取main()方法所在的线程名称

    • Thread currentThread() 返回对当前正在执行的线程对象的引用

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
my1.setName("高铁");
my2.setName("飞机");
// MyThread my1 = new MyThread("高铁");
// MyThread my2 = new MyThread("飞机");
my1.start();
my2.start();
//System.out.println(Thread.currentThread().getName());
}
}

public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
}

1.6线程优先级【应用】

线程调度

  • 两种调度方式

    • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
    • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些**(java采用这种)**
  • 随机性

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

    • final int getPriority() 返回此线程的优先级

    • final void setPriority(intnewPriority)更改此线程的优先级 线程默认优先级是5;线程优先级的范围是:1-10

    • 线程优先级高仅仅表示获取cpu时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的结果,,跑了几次,感觉随机性太大

示例代码

各跑5000次效果依然很随机、cpu太快。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ThreadPriorityDemo {
public static void main(String[] args) {
ThreadPriority tp = new ThreadPriority();
Thread th1 = new Thread(tp, "高铁");
Thread th2 = new Thread(tp, "飞机");
Thread th3 = new Thread(tp, "自行车");
//public final void setPriority(int newPriority):更改此线程的优先级
th1.setPriority(4);
th2.setPriority(10);
th3.setPriority(1);
//public final int getPriority():返回此线程的优先级
System.out.println(th1.getPriority());
System.out.println(th2.getPriority());
System.out.println(th3.getPriority());
th1.start();
th2.start();
th3.start();
}
}

public class ThreadPriority implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
System.out.println(Thread.currentThread().getName()+ ":" + i);
}
}
}

2. 线程的状态和控制

2.1线程的生命周期【背诵】

线程的五种状态

img

线程的常用方法

方法名 方法描述
sleep(longmillisec) 休眠,在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
join() 执行,指定某一个线程执行完毕后,再执行其他线程,也可以指定时常
yield() 谦让,暂停当前正在执行的线程对象,并执行其他线程。也会争夺cpu
`suspend()` 挂起,不推荐使用suspend()去挂起线程的原因,是因为suspend()在导致线程暂停的同时,并不会 去释放任何锁资源。
`resume()` 继续执行
`stop()` 终止线程,不建议使用Thread.stop()方法,因为stop()会直接终止线程,并释放所有锁,如果释放了维持对象一致性的锁,就可能导致对象被写坏
setDaemon(boolean on) 将此线程标记为守护线程
Obj.wait() 等待,停止继续执行,而转为等待状态
Obj.notify() 通知,通知等待的线程执行

Sleep()和wait()的区别

  1. wait只能在同步(synchronize)环境中被调用,而sleep不需要。
  2. wait方法在进入wait状态的时候会释放对象的锁,但是sleep方法不会。
  3. 进入wait状态的线程能够被notifynotifyAll线程唤醒,sleep状态的线程指定确定的时间。

2.2 sleep()方法

特点:

  • sleep(时间)指定当前线程阻塞的毫秒数;
  • sleep 存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态;
  • sleep可以模拟网络延时;
  • 每一个对象都有一个锁,sleep不会释放锁。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep();
Thread th1 = new Thread(ts1, "曹操");
Thread th2 = new Thread(ts1, "刘备");
Thread th3 = new Thread(ts1, "孙权");
th1.start();
th2.start();
th3.start();
}
}

import static java.lang.Thread.sleep;
public class ThreadSleep implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
//sleep()方法
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

2.3 join()方法

特点:

  • 用在主线程里面
  • 指定某一个线程执行完毕后,再执行其他线程

代码示例:

  • 康熙是爸爸,康熙执行完之后,四阿哥和八阿哥才能争夺皇位。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin();
Thread th1 = new Thread(tj1, "康熙");
Thread th2 = new Thread(tj1, "四阿哥");
Thread th3 = new Thread(tj1, "八阿哥");
th1.start();
//调用join()方法
try {
th1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
th2.start();
th3.start();
}
}

public class ThreadJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}

2.4 yield()方法

特点:

  • 礼让线程在释放cpu之后进入就绪队列,但依然会竞争cpu。

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"A").start();
new Thread(myYield,"B").start();
}

}

class MyYield implements Runnable{

@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ "线程开始执行。");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止执行。");
}
}
// 礼让成功
//A线程开始执行。
//B线程开始执行。
//B线程停止执行。
//A线程停止执行。

//礼让不一定成
//B线程开始执行。
//A线程开始执行。
//B线程停止执行。
//A线程停止执行。

2.5 setDaemon(boolean flag)方法

特点:

  • 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出,即,主线程完成后,守护线程相继终止(自动)

代码示例:

当刘备(主线程)执行完毕后,守护线程:关羽、张飞,会自动相继退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
Thread th1 = new Thread(td1, "关羽");
Thread th2 = new Thread(td1, "张飞");
//设置主线程(也就是当前线程)的名字
Thread.currentThread().setName("刘备");
//设置为守护线程
th1.setDaemon(true);
th2.setDaemon(true);
//执行线程
th1.start();
th2.start();
//主线程循环输出
for (int i=0;i<21;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class ThreadDaemon implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}

3. 线程同步

3.1卖票的同步问题

需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,程序模拟该电影院卖票。

思路

  • 1:定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
  • 2:在SellTicket类中重写run()方法实现卖票,代码步骤如下
    • A:判断票数大于0,就卖票,并告知是哪个窗口卖的
    • B:卖了票之后,总票数要减1
    • C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
  • 3:定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
    • A:创建SellTicket类的对象
    • B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
    • C:启动线程

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread th1 = new Thread(st,"窗口1");
Thread th2 = new Thread(st,"窗口2");
Thread th3 = new Thread(st,"窗口3");
th1.start();
th2.start();
th3.start();
}
}

public class SellTicket implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正出售第" + (101 - tickets) + "张票");
tickets--;
}else{
break;
}
}
}
}
//可能出现的问题
//相同的票出现多次
//出现负数的票(票卖多了)

3.2 线程同步方案的两种锁

安全问题出现的条件有:
1.是多线程环境;2.有共享数据;3.有多条语句操作共享数据;

解决线程安全的问题:
让程序没有安全问题的环境。主要是针对第三条:锁上多条语句操作的共享数据。
​ java提供两种锁机制来实现同步:synchronized关键字、ReentrantLock可重入锁

使用同步方法的利弊:
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

synchronized关键字:

  • 实现线程之间的同步,对同步的代码加锁,使得每一次、只有一个线程能进入同步块。

  • 三种作用范围:

    • 指定加锁的对象:给指定的对象加锁——锁当前对象
    • 直接作用于实例方法:对当前实例加锁——锁当前方法
    • 直接所用于静态方法:对当前的类加锁——锁整个实例对象

ReentrantLock可重入锁:

  • 实现lock接口,扩展了synchronized得一些功能
  • 一些特点
    • 显示的操作,手动加锁、释放锁
    • 一个线程内可以重复加锁
    • 提供中断处理得能力——锁等待限时 tryLock()
    • 可实现公平锁和非公平锁

synchronized 和 Lock (ReentrantLock )有什么区别?【背诵】

  • 底层实现上来说
    • synchronized 是JVM层面的锁,是Java关键字,synchronized 的实现涉及到锁的升级和优化,具体为无锁、偏向锁、轻量级锁、自旋锁等。
    • ReentrantLock 是从jdk1.5以来提供的API层面的锁。
  • 从锁的对象来说
    • synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
  • 从操作步骤来说
    • synchronized 不需要手动获取锁和释放锁,使用简单;
    • lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
  • ReentrantLock具有更多得功能
    • ReentrantLock则可以设置中断,可通过trylock(long timeout,TimeUnit unit)设置超时方法;
    • ReentrantLock则即可以选公平锁也可以选非公平锁,通过构造方法new ReentrantLock时传入boolean值进行选择,为空默认false非公平锁,true为公平锁。
    • ReentrantLock通过绑定Conditiont条件结合await()/singal()方法实现线程的精确唤醒

3.3 synchronized关键字

synchronized锁对象

先定义一个obj对象,将要同步的代码块放在synchronized(obj){}的括号里面。**sleep()方法一定要放在锁外面,才能生效。**

锁的对象必须是需要做增删改查的对象!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//格式
//synchronized(任意对象) {
//多条语句操作共享数据的代码
//}

public class SellTicket implements Runnable {
private int tickets = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

synchronized (obj) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}
}

public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}

//有点小问题 只有一个窗口买到票

synchronized锁实例方法

就是把synchronized关键字加到方法上 ,格式如下

1
2
3
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SellTicket implements Runnable {
private static int tickets = 100;
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sellTickets();
}
}
//定义一个sellTickets()同步方法
private synchronized void sellTickets() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}

public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}

synchronized锁静态方法

就是把synchronized关键字加到静态方法上 ,格式如下:

1
2
3
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SellTicket implements Runnable {
private static int tickets = 100;
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
sellTickets();
}
}
//定义一个sellTickets()同步静态方法
private static synchronized void sellTickets() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}

public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}

3.4ReentrantLock可重入锁

ReentrantLock几个重要的方法

  • lock():获得锁,如果锁已经被占用,则等待。
  • lockInterruptibly(): 获得锁,但优先响应中断。
  • tryLock(): 尝试获得锁,如果成功,返回true,失败返回false。该方法不等待,立即返回。
  • tryLock(long time, TimeUnit unit) :在给定时间内尝试获得锁。
  • unlock():释放锁

使用步骤:

  1. 使用ReentrantLock() 创建一个ReentrantLock的实例;
  2. lock()unlock()放在需要同步的代码块的首位;
  3. 为了解锁操作一定会执行,这里使用 try(){} finally{}方法;
  4. sleep()方法一定要放在锁外面,才能生效

实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class SellTicket implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

try {
lock.lock();
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}finally {
lock.unlock();
}
}
}
}

public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}

4. 线程通信

线程通信的方式:

  • wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
  • wait(long timeout) 指定等待的毫秒数
  • notify() 唤醒一个处于等待状态的线程
  • notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程先调度

注:上述方法都是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出IIIegalMonitorSateException异常.

4.1 生产者消费者(管程法)

生产者消费者模型是一个并发协作的模型:

  • 生产者:负责生产数据的模块(可能是方法、对象、线程、进程)

  • 消费者:负责处理数据的模块(可能是方法、对象、线程、进程)

  • 缓冲区(仓库):消费者和生产者之间通信的桥梁,生产者将生产好的产品放入缓冲区,消费者从缓冲区中取出产品。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//测试:生产者消费者模型-->管程法
//生产者、消费者、缓冲区、产品
class Producer extends Thread{
SynContainer container;

public Producer(SynContainer container){
this.container = container;
}

//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Product(i));
System.out.println("生产了"+i+"个产品。");
}
}
}
class Consumer extends Thread{
SynContainer container;

public Consumer(SynContainer container){
this.container = container;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了--->"+container.pop().id+"个产品。");
}
}
}
class Product{
int id;
public Product(int id) {
this.id = id;
}
}
// 缓冲区
class SynContainer{

Product[] products = new Product[10];//需要容器大小
int count = 0; //容器计数器

public synchronized void push(Product product){//生产者放入产品
if(count==products.length){ //如果缓冲区满了,就需要等待消费者消费
try {
this.wait();//生产者等待下一次生产
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,就继续生产产品
products[count] = product;
count++;
this.notifyAll();// 可以通知消费者消费了
}

//消费者消费产品
public synchronized Product pop(){
if(count==0){
//等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
Product product = products[count];
//消费了之后,就可以通知生产者继续生产
this.notifyAll();
return product;
}
}

public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Producer(container).start();
new Consumer(container).start();
}
}

4.2 生产者消费者(信号灯法)

为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库:生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为。

(先把代码放出来、以后再看需不需要深究。)

1:奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Box {
private int number;
private boolean state = false;
//放牛奶
public synchronized void putMilk(int number){
if (state){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.number=number;
System.out.println("送奶工投递第"+this.number+"瓶牛奶");
state = true;
//唤醒其他线程
notifyAll();
}
//取牛奶
public synchronized void getMilk(){
if (!state){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("用户拿到第"+this.number+"瓶牛奶");
state = false;
notifyAll();
}
}

2:生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Producer implements Runnable {
private Box box;
public Producer(Box box) {
this.box=box;
}

@Override
public void run() {
for (int i = 1;i<=30;i++){
box.putMilk(i);
}
}
}

3:消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Custromer implements Runnable {
private Box box;
public Custromer(Box box) {
this.box=box;
}

@Override
public void run() {
while(true) {
box.getMilk();
}
}
}

4:测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下

  • A:创建奶箱对象,这是共享数据区域
  • B:创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
  • C:创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
  • D:创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
  • E:启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class BoxDemo {
public static void main(String[] args) {
Box box = new Box();
Producer p = new Producer(box);
Custromer c = new Custromer(box);

Thread th1 = new Thread(p);
Thread th2 = new Thread(c);

th1.start();
th2.start();
}
}

//送奶工投递第1瓶牛奶
//用户拿到第1瓶牛奶
//送奶工投递第2瓶牛奶
//用户拿到第2瓶牛奶
//送奶工投递第3瓶牛奶
//用户拿到第3瓶牛奶

5. 线程池

5.1 线程池介绍

思路

  • 为了避免系统频繁地创建和销毁线程,我们可以让创建的线程进行复用。
  • 将活跃的线程放入线程池中,方便取用
  • 创建线程——从线程池中获得空闲线程
  • 关闭线程——向池子归还线程

好处:

  • 提高响应速度、减少了创建新线程的时间

  • 降低资源消耗、重复利用线程池中的线程,不需要每次都创建

  • 便于线程的管理

    • corePoolSize 核心池的大小
    • maximumPoolSize 最大线程数
    • keepAliveTime 当线程池线程数量超过corePoolSize时,多余的空闲线程的存活时间。 即,超过corePoolSize的空闲线程,在多长时间内,会被销毁。
    • workQueue: 任务队列,被提交但尚未被执行的任务。
    • handler: 拒绝策略。当任务太多来不及处理,如何拒绝任务。

线程的创建、执行和关闭

  • ExecutorService service = Executors.newFixedThreadPool(10);

  • service.execute(new MyThread());

  • service.shutdown();

线程创建的几种方法

方法 解释
ExecutorService newFixedThreadPool(int nThreads) 返回一个固定线程数量的线程池。该线程池中的线 程数量始终不变。
ExecutorService newSingleThreadExecutor() 返回一个只有一个线程的线程池。
ExecutorService newCachedThreadPool() 该方法返回一个可根据实际情况调整线程数量的线程池。
ScheduledExecutorService newSingleThreadScheduledExecutor() 该方法返回一个 ScheduledExecutorService 对 象,线程池大小为 1。 扩展了 在给定时间执行某任务的功能,如在某个固定的延时之后执行,或者周期性执行某个任务。
ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 该方法也返回一个 ScheduledExecutorService 对象,但 该线程池可以指定线程数量。

5.2 线程池示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//测试线程池
public class TestPool {
public static void main(String[] args) {
// 1.创建服务,创建线程池
// newFixedThreadPool(10); 参数是线程池的大小
ExecutorService service = Executors.newFixedThreadPool(10);

// 2. 执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());

//3. 关闭连接
service.shutdown();
}
}

class MyThread implements Runnable{

@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
// 输出
//pool-1-thread-1
//pool-1-thread-2
//pool-1-thread-3
//pool-1-thread-5
//pool-1-thread-4

6. 静态代理——线程实现的底层模式

6.1 静态代理模式:

真实对象和代理对象都实现同一个接口,代理对象要代理真实角色。就和实现线程一样,真实对象和Thread都实现了Runnable接口,然后用new Thread(myRun,"飞机");实现线程即是用Thread对象代理真实的myRun对象。

静态代理的好处:

  1. 代理对象可以做很多真实对象做不了的事情

  2. 真实对象可专注做自己的事情

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 人间四大喜事: 久旱逢甘霖,他乡遇故知,洞房花烛夜,金榜题名时
interface Marry{
void happyMarry();
}

// 真实角色
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("Nick要结婚了,超开心...");
}
}

// 代理角色
class WeddingCompany implements Marry{
private You target;

public WeddingCompany(You target) {
this.target = target; //真实目标角色
}

@Override
public void happyMarry() {
System.out.println("结婚之前,布置现场");
this.target.happyMarry(); //真实对象
System.out.println("结婚之后,收尾款");
}
}

public class StaticProxy {
public static void main(String[] args) {
// 真实对象自己调方法
You nike = new You();
nike.happyMarry();

//通过代理帮你调方法
WeddingCompany weddingCompany = new WeddingCompany(new You());
weddingCompany.happyMarry();
}
}
/*
Nick要结婚了,超开心...
结婚之前,布置现场
Nick要结婚了,超开心...
结婚之后,收尾款
*/

6.2 线程中的静态代理分析

1.Thread类实现了Runable接口,**即Thread类相当于上文中的”婚庆公司”**

image

2.我们写的类也是实现了Runnable接口,即我们写的类相当于上文中的”结婚人You”

image

3.在实现了Runnable接口后通过代理类Thread对象完成线程的启动

  • 在代理类Thread对象的创建中,声明了我们所写的实际对象,eg:”myRunnable”。

  • 然后由Thread类协助我们完成这一系列的操作。

  • 看似简单的start()背后,代理类Thread还帮助我们做了很多事。

7. lambda表达式

函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做” 。

Lambda 表达式,也可称为闭包。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。Lambda表达式可以替代以前广泛使用的内部匿名类,各种回调,比如事件响应器、传入Thread类的Runnable等。

Lambda表达式的使用前提:函数式接口,即有一个接口,接口中有且仅有一个抽象方法。

lambda 表达式的语法格式如下:

1
2
3
4
5
(parameters) ->{ statements; }
/*
形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
*/

lambda 表达式省略规则:

  • 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
  • 如果参数有且仅有一个,那么小括号可以省略
  • 如果代码块的语句只有一条,可以省略大括号和分号,和return关键字

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//函数式接口
public interface ILove {
void love(int a );
}
//正文...
public class lambdaDemo {
public static void main(String[] args) {
// 匿名内部类
ILove love1 = new ILove() {
@Override
public void love(int a) {
System.out.println("I Love you: " + a);
}
};
love1.love(1);

//Lambda表达式
ILove love2 = (int a)->{
System.out.println("I Love you: " + a);
};
love2.love(2);

//去掉参数类型
ILove love3 = (a)->{
System.out.println("I Love you: " + a);
};
love3.love(3);

ILove love4 = a->{
System.out.println("I Love you: " + a);
};
love4.love(4);

// lambda表达只有一行代码,还可继续简化成下面的情况
ILove love5 = a->System.out.println("I Love you: " + a);
love5.love(5);
}
}
-------------感谢阅读没事常来-------------