JAVA多线程_2021学习理解_SXL
引言
做个俗人,贪财好色,一身正气。
一、多线程理解(基本概念整理)
1.1 线程和进程
进程
一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程
进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程。
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
1.2 线程调度
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
1.3 同步与异步
同步:排队执行 , 效率低但是安全
异步:同时执行 , 效率高但是数据不安全
二、线程的实现方式
每个线程都有自己的一份栈空间,共用一份堆内存。
2.1 继承Thread
public class Test {
public static void main(String[] args) {
MyThread m = new MyThread();
// 启动m线程
m.start();
// 以匿名内部类实现的线程
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("匿名内部类实现的线程" + i);
}
}).start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程" + i);
}
}
}
class MyThread extends Thread {
/**
* run方法是线程要执行的方法
*/
@Override
public void run() {
// 此处是一条新的执行路径
// 执行此处不是调用run()方法,而是调用Thread对象的start()方法来启动任务线程
for (int i = 0; i < 10; i++) {
System.out.println("MyThread线程" + i);
}
}
}
2.2 实现Runnable(频率多)
实现Runnable 和 继承Thread相比有如下优势:
- 通过创建任务,然后给线程分配的方式实现的多线程,更适合多个线程执行相同任务的情况
- 可以避免单继承所带来的局限性,接口是可以多实现的
- 任务和程序是分离的,提高了程序的健壮性
- 线程池技术,接受Runnable类型的任务,不接受Thread类型的线程
public class Test{
public static void main(String[] args) {
// 实现Runnable
// 创建一个任务对象
MyRunnable r = new MyRunnable();
// 创建一个线程,分配一个任务
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
class MyRunnable implements Runnable {
/**
* run方法是线程要执行的方法
*/
@Override
public void run() {
// 此处是一条新的执行路径
// 执行此处不是调用run()方法,而是调用Thread对象的start()方法来启动任务线程
for (int i = 0; i < 10; i++) {
System.out.println("MyRunnable线程" + i);
}
}
}
2.3 实现Callable(频率少)
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class test1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// Callable写法实现
Callable<Integer> c = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(c);
Thread t = new Thread(task, "子线程");
t.start();
// 主线程不进行参与直到完成
Integer j = task.get();
System.out.println("分割线===================" + j);
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + i);
}
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// Thread.sleep(3000);
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + i);
}
return 100;
}
}
三、Thread类
线程分为守护线程和用户线程
- 用户线程:当一个进程不包含任何的存活的用户线程时,进行结束
- 守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡
public class Test2 {
public static void main(String[] args) throws InterruptedException {
// 线程的休眠 sleep
// 输出 每隔一秒循环输出数字
/*for (int i = 0; i < 10; i++) {
System.out.println(i);
Thread.sleep(1000);
}*/
Thread t0 = new Thread(new MyRunnable(), "守护线程");
// 设置守护线程
t0.setDaemon(true);
t0.start();
Thread t1 = new Thread(new MyRunnable());
t1.start();
// 添加中断标记
t1.interrupt();
// 获取线程名称
// currentThread() 获取当前线程对象
// 输出main
System.out.println(Thread.currentThread().getName());
// 输出MyRunnable子线程 Thread-num等形式
// new Thread(new MyRunnable(), "MyRunnable子线程").start();
for (int i = 0; i < 3; i++) {
System.out.println(i);
Thread.sleep(1000);
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("又是风评梁静的一天。==");
// 结束run方法,等于线程执行完成。
return;
}
}
}
}
四、线程安全问题
同步代码块和同步方法都属于隐式锁。
Java默认的是非公平锁
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。(实现方法:Lock l = new ReentrantLock(true); 显示锁构造方法中参数为true表示为公平锁)
非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
4.1 同步代码块和同步方法
public class Test {
public static void main(String[] args) {
Runnable runnable = new Ticket();
Thread t1 = new Thread(runnable,"窗口1");
Thread t2 = new Thread(runnable,"窗口2");
Thread t3 = new Thread(runnable,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable {
// 票数
private int count = 1000;
@Override
public void run() {
// 解决方案2:同步方法
/*while (true) {
Boolean flag = sale();
if (!flag) {
break;
}
}*/
// 解决方案1:同步代码块
// 格式: synchronized(锁对象) {}
while (true) {
synchronized (this) {
if (count > 0) {
// 票数
count--;
System.out.println(Thread.currentThread().getName() + "余票:" + count);
} else {
break;
}
}
}
}
// synchronized 锁住对象 非静态this 静态为类.class
public synchronized boolean sale() {
if (count > 0) {
// 票数
count--;
System.out.println(Thread.currentThread().getName() + "余票:" + count);
return true;
}
return false;
}
}
4.2 显式锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
// 线程不安全
// 解决方案1:同步代码块
// 格式: synchronized(锁对象) {}
Runnable runnable = new Ticket();
Thread t1 = new Thread(runnable,"窗口1");
Thread t2 = new Thread(runnable,"窗口2");
Thread t3 = new Thread(runnable,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable {
// 票数
private int count = 1000;
// 显示锁
Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
// 此处最好有try catch 防止死锁等异常
l.lock();
if (count > 0) {
// 票数
count--;
System.out.println(Thread.currentThread().getName() + "余票:" + count);
} else {
break;
}
l.unlock();
}
}
}
4.3 notifyAll 和 wait
public class test {
public static void main(String[] args) {
Food food = new Food();
new Cooker(food).start();
new Waiter(food).start();
}
}
/**
* 厨师
*/
class Cooker extends Thread {
private Food food;
public Cooker(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
food.setNameAndTaste("馒头", "淡的");
} else {
food.setNameAndTaste("酸菜", "酸的");
}
}
}
}
/**
* 服务生
*/
class Waiter extends Thread {
private Food food;
public Waiter(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
food.get();
}
}
}
/**
* 食物
*/
class Food {
private String name;
private String taste;
private Boolean flag = true;
public synchronized void setNameAndTaste(String name, String taste) {
if (flag) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
flag = false;
// notifyAll唤醒此对象所有线程
this.notifyAll();
// wait此线程等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get() {
if (!flag) {
System.out.println("服务生菜名称:" + name + ",味道:" + taste);
flag = true;
// notifyAll唤醒此对象所有线程
this.notifyAll();
// wait此线程等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTaste() {
return taste;
}
public void setTaste(String taste) {
this.taste = taste;
}
}
五、线程池
线程池 Executors
并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,创建线程和销毁线程需要时间,频繁的创建线程就会大大影响系统的效率。线程池容纳了多个线程,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性。
5.1 缓存线程池
/**
* 缓存线程池.
* (长度无限制)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程并放入线程池, 然后使用
*/
public class test {
public static void main(String[] args) throws InterruptedException {
// 向线程池中加入新的任务
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> System.out.println(Thread.currentThread().getName()));
service.execute(() -> System.out.println(Thread.currentThread().getName()));
service.execute(() -> System.out.println(Thread.currentThread().getName()));
Thread.sleep(1000);
// 输出结果是上面三个空闲的线程
service.execute(() -> System.out.println(Thread.currentThread().getName()));
}
}
5.2 定长线程池
/**
* 定长线程池.
* (长度是指定的数值)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
public class test {
public static void main(String[] args) throws InterruptedException {
// 向线程池中加入新的任务
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(() -> {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(() -> {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(() -> {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread.sleep(1000);
service.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
}
}
5.3 单线程线程池
/**
* 效果与定长线程池创建时传入数值1效果一致.
* 单线程线程池.
* 执行流程:
* 1. 判断线程池的那个线程是否空闲
* 2. 空闲则使用
* 3. 不空闲,则等待池中的单个线程空闲后使用
*/
public class test {
public static void main(String[] args) throws InterruptedException {
// 向线程池中加入新的任务
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
service.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
service.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
Thread.sleep(1000);
service.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
}
}
5.4 周期定长线程池
/**
* 周期任务 定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*
* 周期性任务执行时:
* 定时执行, 当某个时机触发时, 自动执行某任务
*/
public class test {
public static void main(String[] args) throws InterruptedException {
// 向线程池中加入新的任务
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/*
* 1. 定时执行一次
*
* 参数1:定时执行的任务
* 参数2:时长数字
* 参数3:时长数字的时间单位, TimeUnit的常量指定
*/
service.schedule(() -> {
System.out.println(Thread.currentThread().getName());
}, 5, TimeUnit.SECONDS);
/*
* 2. 周期性执行
*
* 参数1:定时执行的任务
* 参数2:延迟时长数字
* 参数3:周期时长数字
* 参数4:时长数字的时间单位, TimeUnit的常量指定
*/
service.scheduleAtFixedRate(() -> {
System.out.println(Thread.currentThread().getName());
}, 5, 1, TimeUnit.SECONDS);
}
}
总结
人生无限,缓缓起航,修正改错,在满足完成任务的条件下,追求完善,全身而退。