一.进程 ,线程,多线程简介
1.进程(Process):是某数据集合上的一次运行活动,同时也是系统分配资源和调度资源的单位.在当代的面向线程设计的计算机结构中,进程是线程的容器.
2.线程(Thread):系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位
注意:进程内有一个相对独立的,可调度的执行单元
3.多线程(multithreading):是指从软件或者硬件上实现多个线程并发执行的技术,计算机因有硬件支持而能够在同一时间执行多于一个线程的开发技术.
注意:进程和线程的关系:
1).进程 是 线程 的容器 , 一个进程至少包含一个线程
包含多个线程的进程程序叫做多线程
2).进程 是 线程 中一个独立的 可调度的执行单位 , 运行资源的内存有所属进程分配
二.实现多线程的方法
实现多线程的方法一共有三种 – 继承Thread类实现多线程 / 实现Runnable接口实现多线程 / 通过Callable接口实现可回调参数的多线程
1.继承Thread类实现多线程
大致步骤:
① 子类实现Thread类 重写Run方法
② 生成子类对象
③ 子类对象开启线程
代码举例:
三个小朋友 同时在2018跨年 同时高呼三声梦想成真
1).创建继承Thread类的实现类
// ① 继承Thread类
public class child01 extends Thread {
/*
* 实现类中有name属性是 使用标准的set get方法或报错
*
* 原因:Thread类中也有name属性(用于记录当前线程名),同样有标准的set get方法
* 并且被final修饰 如果实现类使用set get方法会犯了强行重写被final修饰的方法
*
* 修改方法:将实现类的set get方法定义为给标准化的
*
* 所以继承Thread类的实现类最好不要使用name属性
*/
private String name;
public String getSelfName() {
return name;
}
public void setSelfName(String name) {
this.name = name;
}
// name:child姓名
// ctName:线程名
public child01(String name , String ctName) {
super(ctName);
this.name = name;
}
// ② 重写Run方法
@Override
public void run() {
// 完成自身多线程方法
bowl();
// 完成线程任务后上交父级
super.run();
}
// 高呼三声的方法
private void bowl() {
for (int i = 0; i < 3; i++) {
System.out.println("2018年,我 <" + name + "> 梦想成真!");
}
}
}
2).创建多线程对象并开启线程
public class practice01 {
public static void main(String[] args) {
// ③ 创建多线程对象
child01 c1 = new child01("小明", "小明线程");
child01 c2 = new child01("小红", "小红线程");
child01 c3 = new child01("小蓝", "小蓝线程");
// 获取某一线程的线程名
System.out.println(c1.getName());
// 获取child名
System.out.println(c1.getSelfName());
// 获取当前线程名
System.out.println(Thread.currentThread().getName());
// ④ 实现对象开启线程
c1.start();
c2.start();
c3.start();
}
}
2.实现Runnable接口实现多线程
大致步骤:
① 子类实现Runnable接口 重写Run方法
② 生成子类对象
③ 子类对象开启多线程
1).创建实现Runnable接口的实现类
/*
* 实现Runnable接口 实现多线程
*
* 缘由:当资源(java类)作为多个线程共有的资源时
*
* 结论:将此java类为Runnable接口的实现类以供多个线程使用
*/
// ① 实现Runnable接口
public class Ticket implements Runnable{
// 互斥锁
private static final Object mutex = new Object();
// 票数
private int num;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public Ticket() {
this.num = 20;
}
// ② 重写Run方法
@Override
public void run() {
sellTicket();
}
// 买票方法
/*
* synchronize -- 同步锁
* 1.synchronize 作为方法的关键词
* 该方法为同步方法:
* 目的:方法在同一时刻只能被一个线程所调用
* 应用场景:方法功能单一 且 方法的整体需要做同步处理
*
* 2.synchronize 作为同步代码块
* 代码块为同步代码块:
* 目的:代码夸同一时刻只能被一个线程调用
* 应用场景:方法功能逻辑较多 且 方法局部需做同步处理
*/
private void sellTicket() {
/*
* 静态代码块 synchronize(mutex)
* 参数 (mutex -- 互斥锁) 是唯一 且 是任意的对象
*
* 通常互斥锁的标识:
* 静态方法中:当前类的 类名.class 的Class对象
* 成员方法中:this
* this不唯一时:static final Object
*/
while (true) {
// 一票一锁 : 抢到票就上锁 等卖了票就可以开锁继续抢
synchronized (mutex) { // 上锁的标识
if (num > 0) {
// 获取当前线程名(卖票窗口)
String ctName = Thread.currentThread().getName();
// 窗口买的票次
int ticket = 50 - num + 1;
// 延时
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time < 100) {}
System.out.println(ctName + "抢到第" + ticket + "张票");
// 卖出一张票 票数就减少一张 所得为剩余票数
num--;
System.out.println(ctName + "卖出一张票,票数剩余: " + num + "张");
System.out.println();
}
} // 解锁
// 如果票数小于或等于0 结束买票
if (num <= 0) {
break;
}
/*
* 如果锁住的逻辑较多 在执行过程中就有可能让出CPU执行权
* 导致其竞争线程在未解锁的状态去竞争资源 从而进入锁池状态
* 失去下一次第一时间竞争CPU的机会
* 所以需要在开锁的线程 在开锁后强行让出CPU执行权Thread.yield()方法
* 注意:锁池状态的线程扎起开锁后会从新进入就绪状态
*
*/
Thread.yield();
}
}
}
2).生成实现类对象并开启线程
public class Practice02 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
// ③ 创建实现类对象
Thread t1 = new Thread(ticket, "1号窗口");
Thread t2 = new Thread(ticket, "2号窗口");
Thread t3 = new Thread(ticket, "3号窗口");
// ④ 实现类对象开启线程
t1.start();
t2.start();
t3.start();
}
}
3.线程管理类(外部) 控制 管理的线程类(内部)的 开启/关闭
CountOut线程类:
public class CountOut extends Thread{
// 计数
private int count;
// 暂停
public static volatile boolean pause;
// 结束
public static volatile boolean over;
@Override
public void run() {
while (!over) {
if (!pause) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(++count);
}
}
}
}
CountErr线程类:
public class CountErr extends Thread{
private int count;
// 暂停
public static volatile boolean pause;
@Override
public void run() {
/*
* 线程中断检测
* 1.Thread.interrupted()
* 检查当前线程的中断状态 如果发现线程中断后(中断状态为true) 该操作会清除中断状态
*
* 2.this.isinterrupted()
* 检查当前线程的中断状态 如果发现线程中断后(中断状态为true) 该操作不会清除中断状态
*
* interrupt想滚操作 与 sleep / join相关操作
* 注意:对于同一线程操作而言 不建议interrupt与sleep/join结合使用
* 原因:某线程发生interrupt操作是 会被当前线程中的sleep/join操作
* 捕获InterruptedException异常 且当前现在的中断状态也会被清除
* 注意:A线程的interrupt操作 不会影响到B线程的sleep/join操作
*/
while (!isInterrupted()) {
if (!pause) {
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time < 200) {}
System.err.println(++count);
}
}
}
}
线程管理类:
public class Practice03 {
private static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// fun1();
fun2();
}
private static void fun2() {
CountErr err = new CountErr();
err.start();
// 运行2s
sleep(2000);
err.pause = true;
// 暂停1s后继续运行
sleep(1000);
err.pause = false;
// 运行1s后结束程序
sleep(1000);
err.interrupt();
}
private static void fun1() {
CountOut out = new CountOut();
out.start();
// 运行2s
sleep(2000);
out.pause = true;
// 暂停1s后继续计数
sleep(1000);
out.pause = false;
// 计数1s后结束计数
sleep(1000);
out.over = true;
}
}
4.通过Callable接口实现可回调参数的多线程
创建实现Runnable接口的实现类:
import java.util.concurrent.Callable;
// ① 实现Callable接口(拥有泛型 call方法的结果决定)
// ② 实现Call方法(有throws 有返回值)
public class AsyncReadFile implements Callable<String> {
@Override
public String call() throws Exception {
// 功能的具体步骤 若干步
System.out.println("开始读取文件!");
StringBuffer sb = new StringBuffer("结果: ");
// 模拟费时的操作
// 读取一次耗时200毫秒 将结果拼接到可变字符串
for (int i = 0; i < 3; i++) {
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time < 1000) {}
System.out.println("读取中...");
sb.append(i + " ");
}
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time < 1000) {}
System.out.println("文件读取完毕...");
// 直接返回功能的结果
return new String(sb);
}
}
创建实现类的对象:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestARF {
public static void main(String[] args) {
System.out.println("主线程 -- 逻辑一");
// ③ 生成Callable实现类的对象
AsyncReadFile task = new AsyncReadFile();
// ④ 生成线程管理池(pool)
ExecutorService pool = Executors.newCachedThreadPool();
// ⑤ 利用pool去管理(提交任务)具体 Callable / Runnable
Future<String> future = pool.submit(task);
String res = null;
try {
res = future.get(); //当前线程会强行等待该操作
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(res);
if (future.isDone()) {
// 主动关闭pool
pool.shutdown();
}
System.out.println("主线程 -- 逻辑二");
}
}