Java多线程

学习资源:

  1. 多线程-JAVA进阶之路
  2. 《Java高并发编程详解》 汪文君
java
public class testThread extends Thread {

@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("当前事件" + i);
}

}

public static void main(String[] args) {
testThread tt = new testThread();

tt.start();

for (int i = 0; i < 200; i++) {
System.out.println("=+++其他事件+++" + i);
}
}
}

Thread类和Runnable接口实现多线程的区别

  • 应尽可能使用Runnable接口
  • 启动线程: 传入目标对象+Thread对象.start()
  • 可以避免继承Thread类出现的单继承局限性,灵活方便,方便同一个对象被多个线程使用
java
// 一份资源
StartThread station = new StartThread();

// 多个代理
new Thread(station, "Jack").start();
new Thread(station, "David").start();
new Thread(station, "Mary").start();

Runnable 接口

  • 简单使用方法
java
/**
* @author: lijinhao
* @date: 2021/12/20 20:15
*/

public class TestThread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("当前事件" + i);
}
}

public static void main(String[] args) {
TestThread2 tt3 = new TestThread2();

// Thread thread = new Thread(tt3);
// thread.start();
// 等同于:
new Thread(tt3).start();

for (int i = 0; i < 20; i++) {
System.out.println("=+++其他事件+++" + i);
}
}
}

Runnable

java
/**
* @author: lijinhao
* @date: 2021/12/20 20:37
*/


public class TestThread3 implements Runnable{

private int ticketNums = 10;


@Override
public void run() {
while(true){
if(ticketNums <= 0){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---> get ticket:" + ticketNums);
}
}

public static void main(String[] args) {
TestThread3 ticket = new TestThread3();

new Thread(ticket, "java").start();
new Thread(ticket, "python").start();
new Thread(ticket, "C++").start();
}
}

龟兔赛跑

  • 多个线程,操作一个对象
java
/**
* 多个线程,操作一个对象
* @author: lijinhao
* @date: 2021/12/20 21:03
*/

public class Race implements Runnable{

private static String winner;

@Override
public void run() {
for (int i = 0; i <= 100; i++) {

if(Thread.currentThread().getName().equals("兔子") && i % 10 ==0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean flag = gameOver(i);
if(flag){
break;
}
System.out.println(Thread.currentThread().getName() + "---> 跑了" + i + "步");
}
}

private boolean gameOver(int steps){
if(winner != null){
return true;
}{
if(steps >= 100){
winner = Thread.currentThread().getName();
System.out.println("winner is" + winner);
return true;
}
}
return false;
}

public static void main(String[] args) {
Race race = new Race();

new Thread(race, "兔子").start();
new Thread(race, "乌龟").start();
}
}

静态代理

java
/**
* * 代理对象可以做很多真实对象无法完成的事情
* * 真实对象专注于自己的事情
* @author: lijinhao
* @date: 2021/12/20 21:49
*/



public class StaticProxy {

public static void main(String[] args) {
// You you = new You();
// you.HappyMarry();


WeddingCompany wc = new WeddingCompany(new You());
wc.HappyMarry();
}

}

interface Marry{
void HappyMarry();
}

class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("you get married!");
}
}

class WeddingCompany implements Marry{
private Marry target;

public WeddingCompany(Marry target){
this.target = target;
}

@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}

private void after() {
System.out.println("收尾款,给好评");
}

private void before() {
System.out.println("收尾款,给好评");
}
}

优化代码

java
/**
* 层层简化,逐步递进优化
* @author: lijinhao
* @date: 2021/12/20 22:10
*/


public class TestLambda1 {

// 3. 静态内部类
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda2!");
}
}

static class Like implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda!");
}
}

public static void main(String[] args) {
ILike like = new Like();
like.lambda();

like = new Like2();
like.lambda();

// 4. 局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda3!");
}
}

like = new Like3();
like.lambda();

// 5. 匿名内部类
like = new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda4!");
}
};
like.lambda();

// 6. lambda简化
like = () -> System.out.println("I like lambda5!");
like.lambda();

// 其他
// ILove love = (int name) -> System.out.println("I love " + name);

ILove love = null;
love = (name) -> System.out.println("I love " + name);
love.love(123);

}
}

// 1. 定义一个函数式接口
interface ILike{
void lambda();
}

// 其他
interface ILove{
void love(int name);
}


// 2. 实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda!");
}
}


线程停止

  • 官方也已经不建议使用
  • 自己动手写一个flag
java
/**
* @author: lijinhao
* @date: 2021/12/21 16:41
*/


public class TestStop implements Runnable{

// 1. 设置一个标志位
private boolean flag = true;

@Override
public void run() {
int i = 0;
while(flag){
System.out.println("run ...... Thread: " + i++);
}

}

// 2. 设置一个公开的方法停止线程, 转换标志位
public void stop(){
this.flag = false;
}

public static void main(String[] args) {
TestStop ts = new TestStop();

new Thread(ts).start();

for (int i = 0; i < 1000; i++) {
System.out.println("main" + i);
if(i == 900){
// 调用stop方法切换标志位,让线程停止
ts.stop();
System.out.println("线程停止了!");
}
}
}
}

线程休眠

  • sleep进程进入阻塞时间是按毫秒计算的,1000ms = 1s
  • sleep存在异常InterruptedException
  • sleep时间到,线程进入就绪态
  • sleep可以模拟网络延时、倒计时
  • 每一个对象都有一个锁,sleep不会释放锁
java
/**
* 放大出现问题的发生性
* @author: lijinhao
* @date: 2021/12/21 19:03
*/


public class TestSleep implements Runnable{
private int ticketNums = 10;

@Override
public void run() {
while(true) {
if(ticketNums <= 0){
break;
}

try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.getStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---> 拿到了第" + ticketNums-- + "票");
}
}

public static void main(String[] args) {
TestSleep ticket = new TestSleep();

new Thread(ticket, "小明").start();
new Thread(ticket, "老师").start();
new Thread(ticket, "黄牛党").start();
}
}

yield

java
/**
* 礼让不一定成功
* @author: lijinhao
* @date: 2021/12/21 19:40
*/

public class TestYield {
public static void main(String[] args) {
MyYield my = new MyYield();

new Thread(my, "a").start();
new Thread(my, "b").start();
}
}

class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "线程停止执行");
}
}

线程强制执行 join

  • 线程插入到主线程前面,主线程阻塞,当线程执行结束后主线程恢复
java
/**
* @author: lijinhao
* @date: 2021/12/21 20:04
*/


public class TestJoin implements Runnable{


public static void main(String[] args) throws InterruptedException {
TestJoin tj = new TestJoin();

Thread thread = new Thread(tj);
thread.start();

// 主线程
for (int i = 0; i < 1000; i++) {
if(i == 800){
thread.join();
System.out.println("main..." + i);
}
}
}

// vip插入
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("join vip!!!!..." + i);
}
}
}

观察线程状态

java
/**
* @author: lijinhao
* @date: 2021/12/21 21:19
*/


public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
// 睡5秒
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("///////////");
});

Thread.State state = thread.getState();
System.out.println(state);

thread.start();
state = thread.getState();
System.out.println(state);

while(state != Thread.State.TERMINATED){
Thread.sleep(100);
state = thread.getState(); // 更新线程状态
System.out.println(state); // 输出状态
}
}
}

线程的优先级

Java默认的线程优先级为5,线程的执行顺序由调度程序来决定,线程的优先级会在线程被调用之前设定。 通常情况下,高优先级的线程将会比低优先级的线程有更高的几率得到执行。

  • JAVA提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行

  • 线程的优先级用数字表示,范围从1~10

plaintext
Thread.MIN_PRIORITY = 1;

Thread.MAX_PRIORITY = 10;

Thread.NORM_PRIORITY = 5;
  • 使用以下方式改变或获取优先级:

getPriority() setPriority(int ***)

java
/**
* @author: lijinhao
* @date: 2021/12/22 09:25
*/


public class TestPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---" +
Thread.currentThread().getPriority());
}

public static void main(String[] args) {
System.out.println("主线程" + "---" + Thread.currentThread().getPriority());
TestPriority test = new TestPriority();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
Thread t3 = new Thread(test);
Thread t4 = new Thread(test);
t1.start();
t2.setPriority(5);
t2.start();
t3.setPriority(10);
t3.start();
t4.setPriority(4);
t4.start();
}
}

Output:

plaintext
主线程---5
Thread-0---5
Thread-1---5
Thread-2---10
Thread-3---4

守护线程 daemon

线程分为用户线程守护线程。虚拟机必须确保用户线程执行完毕,虚拟机不用等待守护线程执行完毕,例如后台记录操作日志,监控日志,垃圾回收等待。

JVM 程序在什么情况下能够正常退出?

The Java Virtual Machine exits when the only threads running are all daemon threads.

当 JVM 中不存在任何一个正在运行的非守护线程时,则 JVM 进程即会退出。

java
import java.util.concurrent.TimeUnit;

/**
* @author: lijinhao
* @date: 2021/12/22 10:21
*/

public class TestDaemon1 {
public static void main(String[] args) throws InterruptedException {
// 创建一个非守护线程;
Thread thread = new Thread(() -> {
// 模拟非守护线程不退出的情况;
while (true){
try{
TimeUnit.SECONDS.sleep(1);
System.out.println("I am running...");
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
// 启动线程;
thread.start();

TimeUnit.SECONDS.sleep(2);
// 主线程即将退出;
System.out.println("The main thread ready to exit...");

}
}

Output:

线程无法退出:

plaintext
I am running...
The main thread ready to exit...
I am running...
I am running...
I am running...
I am running...
I am running...
I am running...
I am running...

守护进程的方法

java
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
* @author: lijinhao
* @date: 2021/12/22 10:27
*/

public class TestDaemon2 {
public static void main(String[] args) throws InterruptedException {
// 添加一个钩子(Hook)线程, 用来监听 JVM 退出,并输出日志;
Runtime.getRuntime()
.addShutdownHook(new Thread(() -> System.out.println("The JVM exit success")));
Thread thread = new Thread(() -> {
while(true){
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("I am running...");
}catch (InterruptedException e){
e.printStackTrace();
}
}
});

thread.setDaemon(true);
thread.start();

TimeUnit.SECONDS.sleep(2);
System.out.println("The main thread ready to exit...");
}
}

Output:

plaintext
I am running...
The main thread ready to exit...
The JVM exit success

可以看到,当主线程退出时,JVM 会随之退出运行,守护线程同时也会被回收,即使你里面是个死循环也不碍事。

钩子进程

来源: Java 多线程之 Hook (钩子) 线程

通常情况下,我们可以向应用程序注入一个或多个 Hook (钩子) 线程,这样,在程序即将退出的时候,也就是 JVM 程序即将退出的时候,Hook 线程就会被启动执行

一些常见应用场景:

  1. 防止程序重复执行,具体实现可以在程序启动时,校验是否已经生成 lock 文件,如果已经生成,则退出程序,如果未生成,则生成 lock 文件,程序正常执行,最后再注入 Hook 线程,这样在 JVM 退出的时候,线程中再将 lock 文件删除掉;

PS: 这种防止程序重复执行的策略,也被应用于 Mysql 服务器,zookeeper, kafka 等系统中。

Hook 线程中也可以执行一些资源释放的操作,比如关闭数据库连接,Socket 连接等

线程同步

当遇到同一个资源,多个人想要使用的情况时,最天然的解决办法就是排队。处理多线程问题时,多个线程访问同一个对象,并且某些线程还要修改对象。此时就需要线程同步。线程同步实际上是一种等待机制,多个需要同时访问此对象的线程要进入此对象的等待池队列,等待前面的线程使用完毕(每个对象都有一把锁),下一个线程再开始使用。

  • 线程同步的应用场景就是多个线程操作同一个资源。

  • 上下文切换

​ 上下文切换(有时也称做进程切换或任务切换)是指 CPU 从一个进程(或线程)切换到另一个进程(或线程)。上下文是指某一时间点 CPU 寄存器和程序计数器的内容。(有时也称做进程切换或任务切换)是指 CPU 从一个进程(或线程)切换到另一个进程(或线程)。上下文是指某一时间点 CPU 寄存器和程序计数器的内容。

并发

  • 同一个对象多个线程同时操作

由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 Synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

  • 一个线程持有锁会导致进程的其他线程挂起;
  • 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

要针对方法提出一套机制,这套机制就是synchronized 关键字,它包括两种用法synchronized 方法和synchronized
同步方法:

public synchronized void method(int args) {}
synchronized方法控制对“对象”的访问,每个对象对应一把锁),每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁继续执行。

使用线程同步锁的三个方法:

  1. 使用使用synchronized同步锁锁住方法
  2. 使用synchronized同步锁锁住变化的参数
  3. 使用Lock同步锁

缺陷: 若将一个大的方法申明微synchronized会大大影响效率。因为方法里面需要修改的内容才需要锁,锁太多就影响效率了。

锁方法

java
/**
* @author: lijinhao
* @date: 2021/12/22 16:21
*/

public class SafeDrawMoney{
public static void main(String[] args) {
Account account = new Account(100, "旅行基金");

Drawing you = new Drawing(account, 50,"你");
Drawing gf = new Drawing(account, 100, "你老婆");

you.start();
gf.start();
}
}

class Account{
int money;
String name;

public Account(int money, String name) {
this.money = money;
this.name = name;
}
}

class Drawing extends Thread{
// 账户
Account account;
// 取了多少钱
int drawingMoney;
// 手里还有多少钱
int nowMoney;

public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}

@Override
public void run() {
// 需要增删改的对象
synchronized (account){
if(account.money - drawingMoney < 0){
System.out.println(Thread.currentThread().getName()+"余额不足");
return;
}
// 卡内余额 = 余额 - 你取的钱
account.money = account.money - drawingMoney;

// 手里的钱
nowMoney = nowMoney + drawingMoney;

System.out.println(account.name+"余额为:" + account.money);

// Thread.currentThread().getName() = this.getName()
System.out.println(this.getName() + "手里的钱:" + account.money);
}
}
}

锁变量

java
/**
* @author: lijinhao
* @date: 2021/12/22 16:08
*/

public class SafeBuyTicket implements Runnable{
private int ticketNums = 10;
boolean flag = true; // 外部停止方式

@Override
public void run() {
// 买票
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

// synchronized 同步方法,锁的是this
public synchronized void buy() throws InterruptedException {
if(ticketNums <= 0){
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "买到第"+ ticketNums-- + "张票");

}

public static void main(String[] args) {
SafeBuyTicket ticket = new SafeBuyTicket();

new Thread(ticket, "老人").start();
new Thread(ticket, "小孩").start();
new Thread(ticket, "黄牛党").start();
}
}

锁集合

java
import java.util.ArrayList;
import java.util.List;

/**
* @author: lijinhao
* @date: 2021/12/22 17:05
*/


public class SafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();

for (int i = 0; i < 10000; i++) {
new Thread(() ->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
try{
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(list.size());
}
}

多线程使用集合时推荐使用CopyOnWriteArrayList ,它适合用在“读多,写少”的“并发”应用中,是线程安全的

java
import java.util.concurrent.CopyOnWriteArrayList;

/**
* @author: lijinhao
* @date: 2021/12/22 17:52
*/


public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(list.size());
}
}

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都会停止执行的情况,某一个同步块同时拥有“两个以上对象的锁”时,就有可能发生死锁的问题

也就是多个线程互相拿着对方需要的资源,然后形成僵持

解决方法:同一个代码块不能同时报两把锁

产生死锁的四个必要条件:

(1) 互斥条件:一个资源每次只能被一个进程使用。

(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

(3) 不剥夺条件: 进程已获得的资源,在末使用完之前,不能强行剥夺。

(4) 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系。

会产生死锁的情况

java
/**
* @author: lijinhao
* @date: 2021/12/22 18:09
*/

public class DeadLock {
public static void main(String[] args) {
Makeup girl1 = new Makeup(0, "mary");
Makeup girl2 = new Makeup(1, "daisy");

girl1.start();
girl2.start();
}
}

class Makeup extends Thread{
// 需要的资源只有一份,用static来保证
static LipStick lipstick = new LipStick();
static Mirror mirror = new Mirror();

int choice;
String girlName;

Makeup(int choice, String girlName){
this.choice = choice;
this.girlName = girlName;
}

@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

// 化妆, 互相持有对方的锁,就是需要拿到对方的资源
private void makeup() throws InterruptedException{
if(choice == 0){
synchronized (lipstick){
System.out.println(this.girlName + " get lipstick's lock");
Thread.sleep(1000);
// 一秒后想获得镜子
synchronized (mirror){
System.out.println(this.girlName + " get mirror's lock");
}
}
}else {
synchronized (mirror) {
System.out.println(this.girlName + " get mirror's lock");
Thread.sleep(1000);
// 一秒后想获得口红
synchronized (lipstick) {
System.out.println(this.girlName + " get lipstick's lock");
}
}
}
}
}

class Mirror{

}

class LipStick{

}

output:

plaintext
mary get lipstick's lock
daisy get mirror's lock

消除死锁的情况

java
/**
* @author: lijinhao
* @date: 2021/12/22 18:09
*/

public class DeadLock {
public static void main(String[] args) {
Makeup girl1 = new Makeup(0, "mary");
Makeup girl2 = new Makeup(1, "daisy");

girl1.start();
girl2.start();
}
}

class Makeup extends Thread{
// 需要的资源只有一份,用static来保证
static LipStick lipstick = new LipStick();
static Mirror mirror = new Mirror();

int choice;
String girlName;

Makeup(int choice, String girlName){
this.choice = choice;
this.girlName = girlName;
}

@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

// 化妆, 互相持有对方的锁,就是需要拿到对方的资源
private void makeup() throws InterruptedException{
if(choice == 0){
synchronized (lipstick){
System.out.println(this.girlName + " get lipstick's lock");
Thread.sleep(1000);
}
// 一秒后想获得镜子
synchronized (mirror){
System.out.println(this.girlName + " get mirror's lock");
}
}else {
synchronized (mirror) {
System.out.println(this.girlName + " get mirror's lock");
Thread.sleep(1000);
}
// 一秒后想获得口红
synchronized (lipstick) {
System.out.println(this.girlName + " get lipstick's lock");
}
}
}
}

class Mirror{

}

class LipStick{

}

output:

plaintext
mary get lipstick's lock
daisy get mirror's lock
mary get mirror's lock
daisy get lipstick's lock

Lock锁

  • 从jdk5.0开始,java提供了更轻大的线程同步机制,通过显式定义同步锁对象来实现同步,同步锁使用lock对象充当

  • lock接口是控制多个线程对共享资源进行访问的工具

    锁提供了对共享资源的独占访问,每次只能有一个线程对lock对象加锁,线程开始访问共享资源之前应先获得lock对象

  • ReentranLock类实现了Lock,它拥有与Synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock,可以显式加锁,释放锁

不安全的多线程

java
/**
* @author: lijinhao
* @date: 2021/12/22 21:01
*/

public class TestLock {
public static void main(String[] args) {
TestLock2 tl = new TestLock2();
new Thread(tl).start();
new Thread(tl).start();
new Thread(tl).start();

}

}

class TestLock2 implements Runnable{
int ticketNums = 10;


@Override
public void run() {
while(true){
if(ticketNums > 0){
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(ticketNums--);
}else{
break;
}
}
}
}

安全的锁

java
import java.util.concurrent.locks.ReentrantLock;

/**
* @author: lijinhao
* @date: 2021/12/22 21:01
*/

public class TestLock {
public static void main(String[] args) {
TestLock3 tl = new TestLock3();
new Thread(tl).start();
new Thread(tl).start();
new Thread(tl).start();
}

}

class TestLock3 implements Runnable{
int ticketNums = 10;

// 定义lock锁
private final ReentrantLock lock = new ReentrantLock();

@Override
public void run() {
while(true){
lock.lock(); // 加锁
try{
if(ticketNums > 0){
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(ticketNums--);
}else{
break;
}
}finally {
// 解锁
lock.unlock();
}

}
}
}

Synchronized与Lock的对比

  1. Lock是显示锁(手动开启和关闭锁,别忘记关闭锁)

    Synchronized是隐式锁,出来作用域自动释放

  2. Lock只有代码块锁

    Synchronized有代码块和方法锁

使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多子类)

  • 优先使用顺序:

​ Lock > 同步代码块(已经进入了方法体,分配了相应的资源) > 同步方法(在方法体之外)

线程协作

线程通信:等待、通知

生产者消费者问题

在生产者消费者问题中,仅有Synchronized是不够的

  • Synchronized可阻止并发更新同一个共享资源,实现了同步
  • Synchronized不能用来实现不同线程之间的消息传递(通信)

JAVA提供了几个方法解决线程之间的通讯问题

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

注意:均是Object类的方法,都只能在同步方法或同步代码块中使用,否则会抛出异常IIIegalMonitorStateException

解决方法

  1. 管程法

    i. 生产者将生产好的数据放入数据缓存区,消费者从缓存区取出数据

  2. 信号灯法

    i. 生产者/消费者查看信号判断是否可以存/取数据

管程法

java
/**
* @author: lijinhao
* @date: 2021/12/22 21:29
*/


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


class Productor extends Thread{
Container container;

public Productor(Container container) {
this.container = container;
}

// 生产产品
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了第 "+i+" 只鸡!");
}
}
}

class Consumer extends Thread{
Container container;
public Consumer(Container container) {
this.container = container;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了第 "+ container.pop().id+" 只鸡");
}
}
}

// product
class Chicken{
int id;

public Chicken(int id) {
this.id = id;
}
}

// 缓冲区
class Container{
// 需要一个容器大小
Chicken[] chickens = new Chicken[10];

// 容器计算器
int count = 0;

public synchronized void push(Chicken chicken){
//如果容器满了,需要等待消费者消费
if(count == chickens.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果没有满,则需要丢入产品
chickens[count] = chicken;
count++;
// 可以通知消费者消费
this.notifyAll();
}

// 消费者消费产品
public synchronized Chicken pop(){
// 判断能否消费
if(count == 0){
// 等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 可以开始消费
count--;
Chicken chicken = chickens[count];
// 通知生产者生产
this.notifyAll();
return chicken;
}
}

Output:

java
……
生产了第 27 只鸡!
生产了第 28 只鸡!
生产了第 29 只鸡!
生产了第 30 只鸡!
生产了第 31 只鸡!
消费了第 22 只鸡
消费了第 32 只鸡
消费了第 31 只鸡
消费了第 30 只鸡
……

信号灯法

java
/**
* @author: lijinhao
* @date: 2021/12/22 21:41
*/

public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}

// 生产者 -> 演员
class Player extends Thread{
TV tv;

public Player(TV tv) {
this.tv = tv;
}

@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i % 2 == 0){
this.tv.play("播放中国好声音");
}else{
this.tv.play("播放新闻联播");
}
}
}
}

//消费者 -> 观众
class Watcher extends Thread{
TV tv;

public Watcher(TV tv) {
this.tv = tv;
}

@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}

//产品 -> 节目
class TV {
// 演员表演,观众等待
// 观众观看,演员等待
String voice; // 表演的节目
boolean flag = true;

// 表演
public synchronized void play(String voice){
// 观众等待,演员开始表演
if(!flag){
try{
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
System.out.println("演员表演了 " + voice);
// 通知观众观看
this.notifyAll();
this.voice = voice;
this.flag = !this.flag;

}

// 观众观看
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了 "+voice);
// 通知演员开始表演
this.notifyAll();
this.flag = !this.flag;
}
}

output:

plaintext
演员表演了 播放中国好声音
观众观看了 播放中国好声音
演员表演了 播放新闻联播
观众观看了 播放新闻联播
演员表演了 播放中国好声音
观众观看了 播放中国好声音
演员表演了 播放新闻联播
观众观看了 播放新闻联播

线程池

  • 背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大

  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具

  • 好处:提高相应速度(减少了创建新线程的时间)

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

  • 便于线程管理

    • corePoolSize:核心池的大小
    • maxinumPoolSize:最大线程数
    • keepAliveTime: 线程没有任务时最多保持多长时间后会终止
java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* @author: lijinhao
* @date: 2021/12/22 21:52
*/


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

service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());

// 2. 关闭连接
service.shutdown();

}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}

output:

plaintext
pool-1-thread-1
pool-1-thread-4
pool-1-thread-3
pool-1-thread-2

以上为入门java多线程所有内容

项目代码位置: https://github.com/Leekinghou/JavaCoreTechnology