Java多线程

学习资源:

  1. 多线程-JAVA进阶之路
  2. 《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类出现的单继承局限性,灵活方便,方便同一个对象被多个线程使用
// 一份资源
StartThread station = new StartThread();

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

Runnable 接口

  • 简单使用方法
/**
* @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

/**
* @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();
}
}

龟兔赛跑

  • 多个线程,操作一个对象
/**
* 多个线程,操作一个对象
* @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();
}
}

静态代理

/**
* * 代理对象可以做很多真实对象无法完成的事情
* * 真实对象专注于自己的事情
* @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("收尾款,给好评");
}
}

优化代码

/**
* 层层简化,逐步递进优化
* @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
/**
* @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不会释放锁
/**
* 放大出现问题的发生性
* @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

/**
* 礼让不一定成功
* @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

  • 线程插入到主线程前面,主线程阻塞,当线程执行结束后主线程恢复
/**
* @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);
}
}
}

观察线程状态

/**
* @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

Thread.MIN_PRIORITY = 1;

Thread.MAX_PRIORITY = 10;

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

getPriority() setPriority(int ***)

/**
* @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:

主线程---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 进程即会退出。

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:

线程无法退出:

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...

守护进程的方法

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:

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会大大影响效率。因为方法里面需要修改的内容才需要锁,锁太多就影响效率了。

锁方法

/**
* @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);
}
}
}

锁变量

/**
* @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();
}
}

锁集合

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 ,它适合用在“读多,写少”的“并发”应用中,是线程安全的

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) 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系。

会产生死锁的情况

/**
* @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:

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

消除死锁的情况

/**
* @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:

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,可以显式加锁,释放锁

不安全的多线程

/**
* @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;
}
}
}
}

安全的锁

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. 生产者/消费者查看信号判断是否可以存/取数据

管程法

/**
* @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:

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

信号灯法

/**
* @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:

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

线程池

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

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

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

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

  • 便于线程管理

    • corePoolSize:核心池的大小
    • maxinumPoolSize:最大线程数
    • keepAliveTime: 线程没有任务时最多保持多长时间后会终止
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:

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

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

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