目录
前言
DOS系统有一个非常明显的特点,只要一中病毒之后系统就会立刻死记,因为传统的DOS系统是采用单进程的处理方式,所以只能有一个程序独自运行,其他程序无法运行。在Windows系统中,即使出现了病毒,系统照样可以正常使用,因为在Windows中采用的是多进程的处理方式,那么在同一个时间段上会有多个程序同时运行。线程实际上就是在进程的基础上的进一步划分,如果一个进程都没有,则线程肯定会消失;而如果线程消失了,进程未必会消失。而且,所有的线程都是在进程的基础上并发运行的(同时运行)。
一、线程的基本介绍
多线程:相当于老板请员工来帮我做事。
1. 中央处理器(CPU)
CPU的中文名称是中央处理器,是进行逻辑运算用的,主要由运算器、控制器、寄存器三部分组成,从字面意思看运算就是起着运算的作用,控制器就是负责发出CPU每条指令所需要的信息,寄存器就是保存运算或指令的一些临时文件,这样可以保证更高的速度,也就是我们的线程运行在CPU之上。
- 单核 :单核的CPU是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。同时间段内有几个多线程需要CPU去运行时,CPU也只能交替去执行多个线程中的一个线程,但是由于其执行速度特别快,因此感觉不出来。
- 多核 :多核的CPU才能更好的发挥多线程的效率。
2. 程序
开发写的代码称之为程序。程序就是一堆代码·,一组数据和指令集,是一个静态的概念。
3. 进程(Process)
- CPU从硬盘中读取一段程序到内存中,该执行程序的实例就叫做进程
- 一个程序如果被CPU多次被读取到内存中,变成多个独立的进程。将程序运行起来,我们称之为进程。进程是执行程序的一次执行过程,他是动态的概念。进程存在生命周期,也就是说程序随着程序的终止而销毁。进程之间是通过TCP/IP端口实现交互的。
简单的理解:一个应用程序(一个进程就是一个软件),一个程序至少包含一个进程,一个进程中至少包含一条线程;
4. 线程
- CPU处理数据时,某一个时刻点任何CPU都只能处理一个程序。
- 线程是进程中的实际运作的单位,是进程的一条流水线,是程序的实际执行者,是最小的执行单位。通常在一个进程中可以包含若干个线程。线程是CPU调度和执行的最小单位。
- 一个进程可以有多个线程,如视频可以同时看图像、听声音、看弹幕,等等;
- 很多线程都是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器,如果是模拟出来的多线程,即一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换很快,所以就有同时执行的错觉。
对于java程序来说,当在DOS命令窗口中输入:
- java HelloWorld 回车之后。会先启动JVM,而JVM就是一个进程。
- JVM再启动一个主线程调用main方法(main方法就是主线程)。
- 同时再启动一个垃圾回收线程负责看护,回收垃圾。
注意 :使用多线程机制之后,main方法结束只是主线程结束了,其他线程还没结束,但没有主线程也不能运行。最起码,现在的java程序中至少有两个线程并发,一个是 垃圾回收线程,一个是 执行main方法的主线程。
5. 进程与线程的关系
- 进程 :可以看做是现实生活当中的公司。
- 线程 :可以看做是公司当中的某个员工。
注意 :进程A和进程B的内存独立不共享。多线程开发
6. 多线程开发
6.1 并发
同一对象被多个线程同时操作;(这是一种假并行。即一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉)。
特点 :同时安排若干个任务,这些任务可以彼此穿插着进行;有些任务可能是并行的,比如买菜、发邮件和去洗脚的某些路是重叠的,这时你的确同时在做三件事;但进菜市场和发邮件和接娃三者是互斥的,每个时刻只能完成其中一件。换句话说,并发允许两个任务彼此干扰。
6.2 并行
你(线程)做你的事,我(线程)做我的事,咱们互不干扰并同时进行。
6.3 串行
一个程序处理当前进程,按顺序接着处理下一个进程,一个接着一个进行
特点 : 前一个任务没搞点,下一个任务就只能等着。
7. 多线程的优点
- 提高应用程序的响应。堆图像化界面更有意义,可以增强用户体验。
- 提高计算机系CPU的利用率
- 改善程序结构,将即长又复杂的进程分为多个线程,独立运行,利于理解和修改。
7.1 何时需要多线程
- 程序需要同时执行两个或多个任务
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序时。
二、线程的创建和启动
1. 多线程实现的原理
Java语言的JVM允许程序运行多个线程,多线程可以通过java中的java.lang.Thread类来体现。
Thread特性:
- 每个线程多事通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体。
- 通过Thread方法的start()方法来启动这个线程,而非直接调用run()。
2. 线程的创建及注意事项
第一种方式:继承Thread类
- 创建一个继承Thread类的子类
- 重写Thread类的run()方法
- 创建Thread类的子类的对象
- 通过此对象调用start()来启动一个线程
package com.thread;
/**
* 我的线程
*
* @author 云村小威
*
* @2023年7月21日 下午12:02:44
*/
public class MyThread extends Thread {
@Override
public void run() {
// 编写程序,这段程序运行在分支线程中
}
}
package com.thread;
/**
*
* @author 云村小威
*
* @2023年7月21日 下午12:37:53
*/
public class Text01 {
public static void main(String[] args) {
MyThread t = new MyThread();
// 启动线程
t.start();
// run方法不会启动线程,不会分配新的分支栈(这种方式就是单线程)
t.run();
}
}
注意 :
- t.run()不会启动线程,只是普通的调用方法而已。不会分配新的分支栈。(这种方法就是单栈程)
- t.start()方法的作用是 :启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
package com.thread;
/**
* 我的线程
*
* @author 云村小威
*
* @2023年7月21日 下午12:02:44
*/
public class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name=name;
}
@Override
public void run() {
// 编写程序,这段程序运行在分支线程中
for (int i = 0; i < 5; i++) {
System.out.println(name+"运行:"+i);
//休眠
try {
sleep((int)Math.random()*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.thread;
/**
*
* @author 云村小威
*
* @2023年7月21日 下午12:37:53
*/
public class Text01 {
public static void main(String[] args) {
MyThread t1 = new MyThread("A");
MyThread t2 = new MyThread("B");
// 启动线程
t1.start();
t2.start();
}
}
运行:
注意 :
- start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
- 从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
- Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
- 实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。
- 但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。
3. Thread类的常用方法
Start() | 启动当前线程的run()方法 |
run() | 通常需要重写Thread类的此方法,将创建的线程要执行的操作声明在此方法中 |
currentThread() | 静态方法,返回当前代码执行的线程 |
getName() | 获取当前线程的名字 |
setName() | 设置当前线程的名字 |
yield() | 暂停当前正在执行的线程对象,并执行其他线程(释放当前CPU的执行权) |
join() | 等待该线程终止的时间(millis毫秒),再可执行其他线程 |
stop() | 已过时,当执行此方法时,强制结束当前线程 |
sleep(long millitime) | 让线程睡眠指定的毫秒数,在指定时间内,线程是阻塞状态 |
isAlive() | 判断当前线程是否处于活动状态 |
setPriority() | 更改线程的优先级 |
setDaemon() | 将该线程标记为守护线程或用户线程 |
4. 实现java.lang.Runnable接口
第二种方式:实现java.lang.Runnable接口
步骤同继承Thread类;
package com.thread;
/**
* 通过实现Runnable接口创建线程
*
* @author 云村小威
*
* @2023年7月21日 下午12:02:44
*/
public class MyThread2 implements Runnable {
private String name;
public MyThread2(String name) {
this.name=name;
}
@Override
public void run() {
// 编写程序,这段程序运行在分支线程中
for (int i = 0; i < 5; i++) {
System.out.println(name+"运行:"+i);
//休眠
try {
Thread.sleep((int)Math.random()*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.thread;
/**
*
* @author 云村小威
*
* @2023年7月21日 下午12:37:53
*/
public class Text02 {
public static void main(String[] args) {
new Thread(new MyThread2("A")).start();
new Thread(new MyThread2("B")).start();
}
}
运行:
5. 采用匿名内部类创建
第三种方式:采用匿名内部类创建线程
package com.thread;
/**
* 匿名内部类创建线程
* @author 云村小威
*
* @2023年7月21日 下午12:37:53
*/
public class Text03 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("t线程"+i);
}
}
});
//启动线程
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("main线程"+i);
}
}
}
运行:
说明 :
- Thread2类通过Runnable接口,使得该类有了多线程类的特征。Run()方法是多线程程序的一个约定。所有的多线程代码都在 run()方法里面。Thread类实际上也是实现了Runnable接口的类。
- 在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target)构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
- 实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
6. Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runnable接口的话,则很容易的实现资源贡共享。
总结 : 实现Runnable接口比继承Thread类具有的有优势
- 适合多个相同的程序代码的线程去处理统一资源
- 可以避免java中的单继承的限制
- 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
- 线程池只能放入实现Runnable或callable类线程,不能直接放入继承Therad的类
main方法其也是一个线程。在java中所有的线程都是同时启动的,至于什么时候、那哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实习就是在操作系统中启动了一个进程。
三、线程的生命周期
1、新建状态(new):新建一个线程对象
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行的线程池中,变得可运行,等待获取CPU的使用权
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
阻塞的情况分为三种 :
- 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait方法会释放持有的锁)
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状。(sleep是不会释放持有的锁)
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
四、线程调度
1. 调整线程优先级 :
Java线程有优先级,优先级高的线程会获得较多的运行机会。
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
static int MAX_PRIORITY 线程可以具有的最高优先级,取值为10。 static int MIN_PRIORITY 线程可以具有最低优先级,取值为1。 static int NORM_PRIORITY 分配给线程的默认优先级,取值为5。
Thread类的setPriority和getPriority()方法分别用来设置和获取线程的优先级。
每个线程都有默认的优先级,主线程的默认优先级为Thread.NORM_PRIORITY。
用法:
Thread t1 = new Thread("t1"); Thread t2 = new Thread("t2"); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORIRY);
注意:
并不是设置了优先级就一定先执行,根据电脑性能等一些有影响还有一点随机性的,只是优先概率大一点。
2. 线程睡眠
Thread.sleep(long millis(毫秒)):让线程进入休眠(阻塞状态)放弃占有CPU时间片,让给其他线程使用。
sleep方法可做到间隔特定的时间,去执行一段特定的代码,每隔多久就执行一次。
package com.thread;
/**
* 线程睡眠
* @author 云村小威
*
* @2023年7月21日 下午12:37:53
*/
public class Text04 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
//睡眠0.8秒
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行:
3. 线程让步
Thread.yield()方法:暂停当前正在执行的线程对象,把执行机会让給相同或者更高优先级的线程。
package com.thread;
/**
* 线程让步
* @author 云村小威
*
* @2023年7月21日 下午12:37:53
*/
public class Text05 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 15; i++) {
Thread.yield();
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
});
t.setName("t");
t.start();
//主线程
for (int i = 0; i < 15; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
运行结果:让主线程快走完了才运行
4. sleep()和yield()的区别
- sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
- sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。
- 实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
- 另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
5. 线程加入
Thread.join() 方法:用于等待其他线程终止
如果线程A中调用了线程B的join方法,那么线程A阻塞,直到线程B执行完后,线程A从阻塞状态转为就绪状态,等待获取CPU的使用权。join方法要在start方法调用后调用才有效,线程必须启动,再加入。
package com.thread;
/**
* 线程加入
*
* @author 云村小威
*
* @2023年7月21日 下午12:37:53
*/
public class Text06 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new JoinThread("t1"));
Thread t2 = new Thread(new JoinThread("t2"));
t1.start();
t1.join();
t2.start();
}
}
class JoinThread implements Runnable {
private String name;
public JoinThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(name + "-->" + i);
}
}
}
运行结果:
主线程一定会等子线程都结束了才结束!
五、线程同步
1. 概念
即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多种。
1、为什么要创建多线程?
在一般情况下,创建一个线程是不能提高程序执行效率的,所以要创建多个线程。
2、为什么要线程同步?
多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。
3、线程同步是什么意思?
同步就是协同步调,按预定的先后次序进行运行。如:你做完,我再做。
错误理解 :“同”字从字面上容易理解为一起动作,其实不是。“同”字应是指协同、协助、互相配合。
正确理解 :所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其他线程也不能调用这个方法。
线程同步的作用 :
- 线程有可能和其他线程共享一些资源,比如:内存、文件、数据库等
- 当多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。
- 线程同步的真是意思其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
2. 同步互斥访问(synchronized)
基本上所有解决线程安全问题的方式都是采用“序列化临界资源访问”的方式,即在同一时刻只有一个线程操作临界资源,操作完了才能让其他线程进行操作,也称同步互斥访问。
在Java中一般采用synchronized和Lock来实现同步互斥访问。
3. Synchronized关键字
首先了解一下互斥锁 :就是能达到互斥访问目的的锁
- 如果对一个变量加上互斥锁,那么在同一时刻,该变量只能有一个线程能访问,即当一个线程访问临界资源时,其他线程只能等待。
- 在Java中,每一个对象都有一个锁标记(monitor),也被称为监视器,当多个线程访问对象时,只有获取了对象的锁才能访问。
- 在我们编写代码的时候,可以使用synchronized修饰对象的方法或者代码块,当某个线程访问这个对象synchronized方法或者代码块时,就获取到了这个对象的锁,这个时候其他对象是不能访问的,只能等待获取到锁的这个线程执行完该方法或者代码块之后,才能执行对象的方法。
4. synchronized同步代码块实现
- 第一种:synchronized代码块方式(灵活)
- 第二种:在实例方法上使用synchronized
- 第三种 : 在静态方法上使用synchronized
package com.thread;
/**
* synchronized同步代码块实现
*
* @author 云村小威
*
* @2023年7月21日 下午12:37:53
*/
public class Text07 {
// 第一种
public void myThread1(Thread thread) {
// this代表当前对象
synchronized (this) {
for (int i = 0; i < 5; i++) {
System.out.println(thread.getName() + ":" + i);
}
}
}
// 第二种
public synchronized void myThread2(Thread thread) {
// this代表当前对象
for (int i = 0; i < 5; i++) {
System.out.println(thread.getName() + ":" + i);
}
}
// 第三种
public static synchronized void myThread3(Thread thread) {
// this代表当前对象
for (int i = 0; i < 5; i++) {
System.out.println(thread.getName() + ":" + i);
}
}
}
5. 什么是类锁和对象锁
5.1 概念
对象锁:在java中每个对象都有一个唯一的锁,对象锁用于对象实例方法或者一个对象实例上面的 —— 一个对象一把锁,100个对象100把锁。
类锁:是用于一个类静态方法或者class对象的,一个类的实例对象可以有多个,但是只有一个class对象 —— 100个对象,也只是1把锁。
注意上述第三种方式synchronized同步代码块实现:在静态方法添加synchronized这把锁属于类了,所有这个类的对象都共享这把锁,这就叫类锁。
5.2 释放锁时机
如果一个方法或者代码块被synchronized关键字修饰,当线程获取到该方法或代码块的锁,其他线程是不能继续访问该方法或代码块的。而其他线程想访问该方法或代码块,就必须要等待获取到锁的线程释放这个锁,而在这里释放锁只有两种情况 :
- 线程执行完代码块,自动释放锁;
- 程序报错,JVM让线程自动释放锁;
举例:这里我调用了上述的第一种和第二种实现同步方式
public static void main(String[] args) { new Text07().myThread1(new Thread()); new Text07().myThread2(new Thread()); }
6. 线程等待与线程唤醒
Object类 | 作用 |
Void wait() | 让活动在当前对象的线程无限等待(释放之前占有的锁) |
Void notify() | 唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁) |
Void notifyAll() | 唤醒在此对象监视器上等待的所有线程。 |
方法详解 :
wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是object类自带的。wait和notify方法不是通过线程对象调用的;
六、线程安全问题(生产者和消费者模式)
1、什么是 “ 生产者和消费者模式 ” ?
- 生产线负责生产,消费线程负责消费。
- 生产线程和消费线程要达到均衡
- 这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。
2、模拟一个业务需求
- 仓库我们采用List集合
- List集合中假设只能存储1个元素
- 1个元素就表示仓库满了
- 如果List集合中元素个数是0,就表示仓库空了
- 保证List集合永远都是最多存储1个元素
- 必须做到这种效果:生产1个消费1个。
模拟生产者与消费者案例:
首先我创建了一个产品类来定义我们需要生产的对象,接着创建一个工厂类写了生产和出售两个方法并实现同步锁,通过put方法将生产的鸡脚保存到容器里如果容器小于10就消费者等待(wait() 方法),如果大于就唤醒( notifyAll()方法 )所有等待的消费者。然后再定义生产者类它需要不停做事,它什么时候休息根据工厂类生产的鸡脚是否大于20,否则不可休息得一直做事。最后是消费者调用工厂类的sale方法只要保存鸡脚的容器大于10就开始购买对容器进行减少,如果没有了就让消费者进行等待。
package com.thread;
import java.util.Vector;
/**
* 生产者与消费者案例
*
* @author 云村小威
*
* @2023年7月22日 上午10:51:47
*/
public class Text08 {
public static void main(String[] args) {
// 店铺运营
// 创建一个工厂
Factory f = new Factory();
// 招三个成产员工
Producer p1 = new Producer(f);
Producer p2 = new Producer(f);
Producer p3 = new Producer(f);
new Thread(p1, "工作人员A").start();
new Thread(p2, "工作人员B").start();
new Thread(p3, "工作人员C").start();
// 引入三个消费者
Sale s1 = new Sale(f);
Sale s2 = new Sale(f);
Sale s3 = new Sale(f);
new Thread(s1, "消费者A").start();
new Thread(s2, "消费者B").start();
new Thread(s3, "消费者C").start();
}
}
/**
* 消费者
*/
class Sale implements Runnable {
Factory f = null;
public Sale(Factory f) {
this.f = f;
}
public void run() {
while (true) {
// 让消费者延迟购买的时间
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.sale();
}
};
}
/**
* 生产类
*/
class Producer implements Runnable {
Factory f = null;
public Producer(Factory f) {
this.f = f;
}
@Override
public void run() {
// 需要不停的做事
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用生产数据的方法
f.put(new Product(1, "鸡脚"));
}
}
}
/**
* 工厂类
*/
class Factory {
// 准备储存产品的集合容器,多线程优先使用Vector集合,该集合类中的方法大部分都是带有同步的概念机制。
Vector<Product> vc = new Vector<Product>();
// 专门定义一个生产数据方法
public synchronized void put(Product product) {
// 判断产品于目标数量是否相同
if (vc.size() >= 20) {
// 产品数量满足让工作人员处于等待状态
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 让等待的工作人员生产产品
this.notifyAll();
System.out.println("正在生产中...当前货架的鸡脚数目是:" + vc.size());
vc.add(product);
System.out.println("货架鸡脚总数目是:" + vc.size());
}
}
// 专门定义一个负责消费数据方法
public synchronized void sale() {
// 只要容器存在货物就可以进行购买
if (vc.size() > 10) {
this.notifyAll(); // 唤醒所有等待的消费者
System.out.println("当前货架上的鸡脚数目是:" + vc.size() + ",正在出售鸡脚。");
vc.remove(0);
System.out.println("\t目前货架上的鸡脚数目是:" + vc.size());
} else {
// 没有鸡脚,让消费者处于等待状态
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 产品类
*/
class Product {
private int pid;
private String name;
public Product() {
// TODO Auto-generated constructor stub
}
public int getPid() {
return pid;
}
public void setPid(int pid) {
this.pid = pid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Product(int pid, String name) {
super();
this.pid = pid;
this.name = name;
}
}
七、多线程小案例
1. 老虎机案例
package com.thread;
import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
/**
* 老虎机游戏程序
* @author 云村小威
*
* @2023年7月22日 上午11:27:56
*/
public class Game extends Application {
Random rd = new Random(); // 随机数对象
boolean flag = true; // 控制开关默认打开
int timer = 50; // 控制事件变量
// 通过数组保存没有规律的多张图片
String[] images = { "lib\\7.png", "lib\\橙子.png", "lib\\柠檬.png", "lib\\苹果.png", "lib\\西瓜.png", "lib\\香蕉.png", };
// 总布局
Pane pane = new Pane();
private Label a1 = new Label("", new ImageView(new Image("lib\\苹果.png", 150, 150, false, false)));
private Label a2 = new Label("", new ImageView(new Image("lib\\西瓜.png", 150, 150, false, false)));
private Label a3 = new Label("", new ImageView(new Image("lib\\柠檬.png", 150, 150, false, false)));
private Button btn1 = new Button("开始游戏");
private Button btn2 = new Button("点击停止");
{
a1.setStyle("-fx-border-color:#ccc;");
a1.setLayoutX(60);
a1.setLayoutY(40);
pane.getChildren().add(a1);
a2.setStyle("-fx-border-color:#ccc;");
a2.setLayoutX(225);
a2.setLayoutY(40);
pane.getChildren().add(a2);
a3.setStyle("-fx-border-color:#ccc;");
a3.setLayoutX(390);
a3.setLayoutY(40);
pane.getChildren().add(a3);
btn1.setLayoutX(180);
btn1.setLayoutY(220);
btn1.setPrefSize(100, 40);
pane.getChildren().add(btn1);
btn2.setLayoutX(340);
btn2.setLayoutY(220);
btn2.setPrefSize(100, 40);
pane.getChildren().add(btn2);
}
@Override
public void start(Stage stage) throws Exception {
// 创建一个场景
Scene scene = new Scene(pane, 600, 300);
stage.setScene(scene);
stage.show();
/**
* 开始游戏点击事件
*/
btn1.setOnAction(e -> {
flag = true;
new Thread() {
public void run() {
while (flag) {
try {
Thread.sleep(timer);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Platform.runLater(new Runnable() {
@Override
public void run() {
int sj1 = rd.nextInt(6);
a1.setGraphic(new ImageView(new Image(images[sj1])));
int sj2 = rd.nextInt(6);
a2.setGraphic(new ImageView(new Image(images[sj2])));
int sj3 = rd.nextInt(6);
a3.setGraphic(new ImageView(new Image(images[sj3])));
}
});
timer += 6;
if (timer >= 300) {
timer = 50;
break;
}
}
}
}.start();
});
/**
* 停止点击事件
*/
btn2.setOnAction(e -> {
flag = false;
});
}
public static void main(String[] args) {
launch();
}
}
运行效果:
2. FX版无限聊天
1. 服务器
package com.net;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
* 服务器
*
* @author 云村小威
*
* @2023年7月19日 下午9:40:54
*/
public class ServerFx extends Application {
/**
* 总布局 BorderPane
*/
BorderPane bor = new BorderPane();
// 上
HBox hb = new HBox();
TextField t = new TextField();
Button btn = new Button("启动服务");
{
hb.getChildren().addAll(t, btn);
hb.setAlignment(Pos.CENTER);
hb.setPadding(new Insets(20));
hb.setSpacing(20);
btn.setPrefSize(100, 40);
bor.setTop(hb);
}
// 中
TextArea ta = new TextArea();
{
ta.setEditable(false); // 文本域不可修改
ta.setStyle("-fx-border-color: #ccc;-fx-border-width: 10 10 10 10;");
bor.setCenter(ta);
}
// 下
HBox hb2 = new HBox();
TextField text = new TextField();
Button btn1 = new Button("发送");
{
hb2.getChildren().addAll(text, btn1);
hb2.setAlignment(Pos.CENTER);
hb2.setPadding(new Insets(20));
hb2.setSpacing(20);
bor.setBottom(hb2);
}
/**
* 将通信对象声明在start方法外面
*/
Socket s = null;
ServerSocket ss = null;
/**
* 定义所需的流对象 扩大权限方便关闭流
*/
InputStream inputStream = null;
BufferedReader br = null;
OutputStream outputStream = null;
BufferedWriter bw = null;
@Override
public void start(Stage stage) throws Exception {
stage.setTitle("服务器");
Scene scene = new Scene(bor, 800, 700);
stage.setScene(scene);
stage.show();
/**
* 启动服务点击事件
*/
btn.setOnAction(e -> {
// 创建服务器
try {
ss = new ServerSocket(Integer.parseInt(t.getText()));
} catch (IOException e1) {
e1.printStackTrace();
}
ta.appendText("服务已启动,等待客户端连接...\n");
// 开启一个线程 自动接受客户端发送的信息
new Thread() {
public void run() {
// 多线程控制接受客户端信息
try {
s = ss.accept();
ta.appendText("客户端已连接...\n");
} catch (IOException e) {
e.printStackTrace();
}
while (true) {
// 读取 获取输入流
try {
inputStream = s.getInputStream();
br = new BufferedReader(new InputStreamReader(inputStream));
String count = br.readLine();
ta.appendText("\t客户端:" + count + "\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
});
/**
* 发送信息点击事件
*/
btn1.setOnAction(e -> {
try {
outputStream = s.getOutputStream();
bw = new BufferedWriter(new OutputStreamWriter(outputStream));
// 获取文本框内容
String count = text.getText();
bw.write(count);
bw.newLine();
bw.flush();
ta.appendText("服务器:" + count + "\n");
text.setText("");
} catch (IOException e1) {
e1.printStackTrace();
}
});
}
public static void main(String[] args) {
launch();
}
}
2. 客户端
package com.net;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
/**
* 客户端
*
* @author 云村小威
*
* @2023年7月19日 下午9:41:46
*/
public class ClientFx extends Application {
/**
* 总布局 BorderPane
*/
BorderPane bor = new BorderPane();
// 上
HBox hb = new HBox();
Text ip = new Text("IP");
TextField t = new TextField("127.0.0.1");
Text tcp = new Text("端口号");
TextField t2 = new TextField();
Button btn = new Button("连接");
{
t.setDisable(true);// 设置ip地址不可编辑
hb.getChildren().addAll(ip, t, tcp, t2, btn);
hb.setAlignment(Pos.CENTER);
hb.setPadding(new Insets(20));
hb.setSpacing(20);
btn.setPrefSize(100, 40);
bor.setTop(hb);
}
// 中
TextArea ta = new TextArea();
{
ta.setEditable(false); // 文本域不可修改
ta.setStyle("-fx-border-color: #ccc;-fx-border-width: 10 10 10 10;");
bor.setCenter(ta);
}
// 下
HBox hb2 = new HBox();
TextField text = new TextField();
Button btn1 = new Button("发送");
{
hb2.getChildren().addAll(text, btn1);
hb2.setAlignment(Pos.CENTER);
hb2.setPadding(new Insets(20));
hb2.setSpacing(20);
bor.setBottom(hb2);
}
/**
* 定义所需的流对象 扩大权限方便关闭流
*/
InputStream inputStream = null;
BufferedReader br = null;
OutputStream outputStream = null;
BufferedWriter bw = null;
// 定义客户端对象
Socket s = new Socket();
@Override
public void start(Stage stage) throws Exception {
stage.setTitle("客户端");
stage.setResizable(false); // 设置窗口不可动
Scene scene = new Scene(bor, 800, 700);
stage.setScene(scene);
stage.show();
/**
* 连接点击事件
*/
btn.setOnAction(e -> {
String ip = t.getText(); // 获取ip地址
int port = Integer.parseInt(t2.getText());
try {
s = new Socket(ip, port);
ta.appendText("客户端成功连接服务器...\n");
} catch (IOException e1) {
e1.printStackTrace();
}
// 开启一个线程 (让它自动接受数据)
new Thread() {
public void run() {
while (true) {
try {
inputStream = s.getInputStream();
br = new BufferedReader(new InputStreamReader(inputStream));
String count = br.readLine();
ta.appendText("\t服务器:" + count + "\n");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
});
/**
* 发送信息点击事件
*/
btn1.setOnAction(e -> {
try {
outputStream = s.getOutputStream();
bw = new BufferedWriter(new OutputStreamWriter(outputStream));
// 获取文本框内容
String count = text.getText();
bw.write(count);
bw.newLine();
bw.flush();
ta.appendText("客户端:" + count + "\n");
text.setText("");
} catch (IOException e1) {
e1.printStackTrace();
}
});
}
public static void main(String[] args) {
launch();
}
}
运行效果: