Java-基础-多线程

Java多线程

1. 普通线程和多线程的区别

1.1 程序,进程和线程的概念

  • 程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念(是死的)
  • 进程:是执行程序的一次执行过程,它是一个动态的概念,是系统的资源分配的单位
  • 线程:通常在一个进程中可以包含若干个线程,线程是CPU调度和执行的单位

2. 线程创建的方式

2.1 Thread Class(重点)

  1. 自定义线程类继承Thread类
  2. 重写run()方法,编写线程的执行体
  3. 创建线程对象,调用start()方法启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.zhuuu.lesson1;

public class ThreadLesson extends Thread {
public void run(){
//run方法的线程体
for (int i = 0; i < 20; i++) {
System.out.println("这是线程一" + i);
}
}


public static void main(String[] args) {
//main线程,主线程

//创建一个线程对象
ThreadLesson threadLesson = new ThreadLesson();

//调用start()方法开启线程
threadLesson.start();

for (int i = 0; i < 20; i++) {
System.out.println("这是线程二" + i);
}
}

}

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
这是线程二0
这是线程二1
这是线程一0
这是线程一1
这是线程一2
这是线程一3
这是线程一4
这是线程一5
这是线程一6
这是线程一7
这是线程一8
这是线程一9
这是线程一10
这是线程一11
这是线程一12
这是线程一13
这是线程一14
这是线程一15
这是线程一16
这是线程一17
这是线程一18
这是线程一19
这是线程二2
这是线程二3
这是线程二4
这是线程二5
这是线程二6
这是线程二7
这是线程二8
这是线程二9
这是线程二10
这是线程二11
这是线程二12
这是线程二13
这是线程二14
这是线程二15
这是线程二16
这是线程二17
这是线程二18
这是线程二19

2.1.1 静态代理模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package lesson6;
//静态代理模式总结:
//真实对象和代理对象都要实现同一个接口
//代理对象要代理真实角色

//好处:
// 代理对象可以做很多真实角色做不到的事情
// 真实对象专注做自己的事情

public class staticProxy {
public static void main(String[] args) {

// Thread底层是静态代理模式
//new Thread(new You()).start();
new WeddingCompany(new You()).HappyMarry();
}
}

//需要事先的接口
interface Marry{
void HappyMarry();
}

//真实对象
class You implements Marry{
@Override
public void HappyMarry(){
System.out.println("结婚开心");
}
}

//代理角色
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("结婚前布置现场");
}
}

2.2 Runnable接口(重点)

  1. 定义MyRunnable类实现Runnable接口
  2. 实现run()方法,编写线程的执行体
  3. 创建线程对象,调用start()方法启动线程
  • 实现Runnable接口,避免了继承Thread类的单继承局限性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.zhuuu.lesson2;

public class RunnableLesson implements Runnable {
//run方法线程体
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("这是线程一"+i);
}
}

public static void main(String[] args) {
//创建runnable接口实现类对象
RunnableLesson runnableLesson = new RunnableLesson();

//创建线程对象,通过先出对象来开启我们的线程。(原理:静态代理)
new Thread(runnableLesson).start();

for (int i = 0; i < 20; i++) {
System.out.println("这是线程二"+i);
}
}
}

输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
这是线程二2
这是线程二3
这是线程二4
这是线程二5
这是线程二6
这是线程二7
这是线程二8
这是线程二9
这是线程一0
这是线程一1
这是线程一2
这是线程一3
这是线程一4
这是线程一5
这是线程一6
这是线程一7
这是线程一8
这是线程一9
这是线程一10
这是线程一11
这是线程一12
这是线程一13
这是线程一14
这是线程一15
这是线程一16
这是线程一17
这是线程一18
这是线程一19
这是线程二10
这是线程二11
这是线程二12
这是线程二13
这是线程二14
这是线程二15
这是线程二16
这是线程二17
这是线程二18
这是线程二19

2.3 Callable接口(了解)

  1. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行的服务
  5. 提交执行
  6. 获取结果
  7. 关闭服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//callable实现多线程下载图片
package com.zhuuu.lesson5;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.*;

public class TestCallable implements Callable<Boolean> {
private String url;
private String name;

public TestCallable() {
this.url = url;
this.name = name;
}



//重写call方法
@Override
public Boolean call() throws Exception {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件名是" + name);
return true; //重写call方法是有返回值的
}

public static void main(String[] args) {
TestCallable t1 = new TestCallable();
TestCallable t2 = new TestCallable();

//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(2);

//提交执行
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);

//获取结果
Boolean rs1 = r1.get();
Boolean rs2 = r2.get();

//关闭服务
ser.shutdownNow();
}
}

//下载器
class WebDownloader {
//下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (MalformedURLException e) {
e.printStackTrace();
System.out.println("IO异常,downloader出现问题");
}
}
}

3. 并发的问题

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.zhuuu.lesson3;

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

@Override
public void run(){
while (true){
if (ticketNums <=0){
break;
}
try {
Thread.sleep(230);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "票");
}

}

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

new Thread(ticket,"小米").start();
new Thread(ticket,"黄牛").start();
new Thread(ticket,"小红").start();

}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
黄牛拿到了第10
小米拿到了第9
小红拿到了第8
小红拿到了第7
小米拿到了第6
黄牛拿到了第5
小米拿到了第3
黄牛拿到了第4
小红拿到了第2
小红拿到了第1
黄牛拿到了第0
小米拿到了第0

多个线程操作同一个对象的情况下,线程不安全,数据会紊乱

4. 龟兔赛跑的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.zhuuu.lesson4;

public class Race implements Runnable{
private static String winner; //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();
}
}

5. lambda表达式

作用:

  • 避免匿名内部类定义过多
  • 去掉一堆没有意义的代码,只留下核心的逻辑

5.1 函数式接口

Functional Interface(函数式接口) 是学习lambda表达式关键所在

  • 函数式接口定义:

    • 任何接口,如果只包含唯一一个抽象的方法,那么他就是一个函数式接口
    1
    2
    3
    public interface Runnable{
    public abstract void run();
    }
    • 对于函数式接口,可以通过lambda表达式创建该接口对象

5.2 lambda举例分析

1. JDK1.8之后才有的特性

2. 本质是不断简化而来

  1. 无参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.zhuuu;

public class lambdaExpression {

//3.静态内部类
static class Like2 implements Ilike{
public void lambda() {
System.out.println("this is lambda2");
}
}

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

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

//4. 局部内部类:定义在方法里面
class Like3 implements Ilike{
public void lambda() {
System.out.println("this is lambda3");
}
}

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


//5. 匿名内部类:没有类的名称,必须借助接口或者父类
Ilike ilike = new Ilike() {

public void lambda() {
System.out.println("this is lambda4");
}
};
ilike.lambda();

//6.用lambda简化
ilike = () -> {
System.out.println("this is lambda5");
};
ilike.lambda();

}
}


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


// 2.实现类
class Like implements Ilike{
public void lambda() {
System.out.println("this is lambda");
}
}
  1. 有参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.zhuuu;

public class lambdaExpression2 {

static class Love implements Ilove {
@Override
public void love(int a) {
System.out.println("that is lambda " + "for " + a);
}
}


public static void main(String[] args) {
class Love implements Ilove {
@Override
public void love(int a) {
System.out.println("that is lambda " + "for " + a);
}
}

Love love = new Love();
love.love(2);


new Ilove() {
@Override
public void love(int a) {
System.out.println("that is lambda " + "for " + a);
}
};
love.love(3);


Ilove ilove = (int a) -> {
System.out.println("that is lambda " + "for " + a);
};
love.love(520);
}


interface Ilove {
void love(int a);
}
}

5.3 lambda简化

1
2
3
4
5
6
7
8
// 简化 1.去掉参数 2.去掉花括号 3.去掉括号
Ilove ilove = a -> System.out.println("that is lambda " + "for " + a);
love.love(520);

//总结:lambda表达式
//1.只有一行代码的情况下才能简化为一行,如果有多行,那么就用代码块包裹
//2.前提必须是函数式接口(接口里面只有一个方法)
//3.去掉参数要么都去掉,要么要用同一个括号包起来

注意事项:前提接口只能有一个方法

6. 线程状态

6.1 停止线程

  1. 注意事项
1
2
3
4
// 测试stop
//1. 建议线程正常停止 --》利用次数,不建议死循环
//2. 建议使用标志位--》设置一个标志位
//3. 不要试用stop或者destroy等过时JDK不建议使用的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.zhuuu;

// 测试stop
//1. 建议线程正常停止 --》利用次数,不建议死循环
//2. 建议使用标志位--》设置一个标志位
//3. 不要试用stop或者destroy等过时JDK不建议使用的方法

public class TestStop implements Runnable{

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



public void run() {
int i = 0;
//标志位为flag
while (flag){
System.out.println("runThread" + i);
}
}

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

public static void main(String[] args) {
//new 对象 开启线程
TestStop testStop = new TestStop();
new Thread(testStop).start();

for (int i = 0; i < 1000; i++) {
System.out.println("这是第" + i + "次的线程");
if (i == 900){
//调用stop方法切换标志位,让线程停止
testStop.stop();
System.out.println("线程在此停止");
}
}
}
}

6.2 线程休眠

模拟10s倒计时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TestSleep2 {

//模拟10s倒计时
public static void main(String[] args) {
tenDown();
}

public static void tenDown(){
int num = 10;

while (true){
try {
//1000ms = 1s
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
if(num < 0){
break;
}
}
}
}

模拟实时显示系统时间

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestSleep2 {
public static void main(String[] args) {
Date date = new Date(System.currentTimeMillis());//获取系统当前时间
while (true){
try {
Thread.sleep(1000); //每隔一秒显示时间
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
date = new Date(System.currentTimeMillis()); //刷新显示新时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

6.3 线程礼让

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.zhuuu;

//礼让不一定成功,看CPU心情

public class yieldTest {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"A").start();
new Thread(myYield,"B").start();
}
}


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

成果的结果如下:

6.4 线程强制执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.zhuuu;

//测试join方法 可以想象为插队
public class TestJoin implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程vip来了" + i);
}
}

public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();



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

6.6 查看线程状态

  1. 查看JDK文档

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.zhuuu;

//观察线程的状态

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

//观察状态 NEW
Thread.State state = thread.getState();
System.out.println(state); // NEW

//观察启动后 RUNNABLE
thread.start();
state = thread.getState();
System.out.println(state); // Run

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

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
============
TERMINATED

6.7 线程优先级

打开Thread源码:

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.zhuuu;


//测试线程的优先级
public class testPriority{
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() +"--->" +Thread.currentThread().getPriority()); //这是输出主线程的优先级

MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);

//先设置优先级在启动
t1.start();

t2.setPriority(1);
t2.start();

t3.setPriority(2);
t3.start();


t5.setPriority(Thread.MAX_PRIORITY); // 默认等于10
t5.start();
}


static class MyPriority implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority()); //这是输出每一个线程的优先级
}
}

}

结果如下:性能倒置

6.8 守护线程

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕 (main线程)
  • 虚拟机不用等待守护线程执行完毕 (gc线程)
  • 守护线程如:垃圾回收,记录操作日志,监控内存

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.zhuuu;

public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();


Thread thread = new Thread(god);
thread.setDaemon(true); //默认是false表示用户线程,正常是用户线程
thread.start();//上帝守护线程启动

new Thread(you).start(); // 你:用户线程
}
}

//上帝
class God implements Runnable{
public void run() {
while (true){
System.out.println("God bless you");
}
}
}


//你
class You implements Runnable{
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("一辈子开心的活着");
}
System.out.println("====goodbye!world=======");
}
}

最后我们消失了,但上帝还在

7. 线程同步

  • 发生场景:多个线程操作同一个资源

  • 什么是并发?

  • 解决方法:队列 + 锁 (synchronized)

  • synchronized会发生的问题

    • 一个线程持有锁会导致其他所有需要这个锁的线程挂起
    • 在多线程的竞争下,加锁会导致性能下降
    • 如果一个优先级高的线程被优先级低的线程拿到了锁,会导致优先级倒置,引起性能的问题

7.1 线程不安全的情况

  1. 示例 : 不安全的买票
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

//线程不安全:有负数

public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"陪跑").start();
new Thread(station,"可恶的黄牛党").start();
new Thread(station,"可恶的机器人").start();

}
}


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


public void run() {
//买票
while (flag){
buy();
}
}

private void buy(){
//判断是否有票
if(ticketNums <= 0){
flag = false;
return;
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
  1. 模拟银行取钱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 不安全的取钱
// 两个人去银行取钱,账户
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100,"结婚基金");
Drawing you = new Drawing(account,50,"你");
Drawing wife = new Drawing(account,100,"你老婆");

you.start();
wife.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() {
if(account.money - drawingMoney < 0){
System.out.println(Thread.currentThread().getName()+"钱不够了,取不了");
return;
}
System.out.println(account.money);
System.out.println(this.getName()+nowMoney);
}
}
  1. ArrayList 不安全性

原因:少的元素是因为(在同一个时间操作了添加了同一个线程,有重复的结果)

根本原因:从主内存拷贝了工作内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//线程不安全的集合
//这里开启了100000条线程

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

public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 100000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}

结果如下:

7.2 Synchronized

作用:

  • 类似于private关键字(保证数据对象只能被方法访问)
  • Synchronized用法包括两种:
    • 同步方法
    • 同步方法块
  • Synchronized方法控制对“对象”的访问,每个对象对应一把锁
  • 每个synchroniez方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞
  • 方法一旦执行,就会独占这个锁,直到该方法返回才会释放锁,后面被阻塞的线程才能获得这个锁

7.2.1 同步方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 安全的买票
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket station = new BuyTicket();
new Thread(station,"陪跑").start();
new Thread(station,"可恶的黄牛党").start();
new Thread(station,"可恶的机器人").start();

}
}


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



public void run() {
//买票
while (flag){
buy();
}
}

private synchronized void buy(){ //加入了synchronized关键字
//判断是否有票
if(ticketNums <= 0){
flag = false;
return;
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}


//买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}

7.2.2 同步块

锁的对象要是增删改的对象

安全的银行取钱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 安全的取钱
// 两个人去银行取钱,账户
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100,"结婚基金");
Drawing you = new Drawing(account,50,"你");
Drawing wife = new Drawing(account,100,"你老婆");

you.start();
wife.start();
}
}


//账户
class Account{
int money; // 余额
String name; // 卡名

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



//synchronized默认的锁是this,对象本身
//银行 : 模拟取款
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){ //改变锁的对象是account,方法丢入同步方法块
if(account.money - drawingMoney < 0){
System.out.println(Thread.currentThread().getName()+"钱不够了,取不了");
return;
}
System.out.println(account.money);
System.out.println(this.getName()+nowMoney);
}
}
}

Arraylist安全的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//线程安全的集合

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

public class UnsafeList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 100000; i++) {
new Thread(()->{
synchronized (list){ //锁的对象是list
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}

7.2.3 JUC包

  • java.util.concurrent (java的并发包
  • CopyOnWriteArrayList是一个线程安全的集合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package TestJUC;

import java.util.concurrent.CopyOnWriteArrayList;

//java.util.concurrent (java的并发包)
//测试JUC安全类型的集合
//CopyOnWriteArrayList是一个线程安全的集合
public class TestJUC {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();

for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();

try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

8. 锁

8.1 死锁

死锁:多个线程互相抱着对象需要的资源,然后形成僵持

死锁的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

//死锁:多个线程互相抱着对象需要的资源,然后形成僵持


public class DeadLock {
public static void main(String[] args) {
MakeUp g1 = new MakeUp(0,"灰姑凉");
MakeUp g2 = new MakeUp(1,"白雪公主");

g1.start();
g2.start();
}
}


//口红
class LipsStick{

}


//镜子
class Mirror{

}

//化妆
class MakeUp extends Thread {

//需要的资源只有一份,用static保证只有一份
static LipsStick lipsStick = new LipsStick();
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 (lipsStick) { //拿到口红的锁
//获得口红的锁
System.out.println(this.girlname + "获得口红的锁");
Thread.sleep(1000);

synchronized (mirror) { //1秒钟后想获得镜子
System.out.println(this.girlname + "获得镜子的锁");
}
}
} else { //拿到镜子的锁
synchronized (mirror) {
//获得口红的锁
System.out.println(this.girlname + "获得镜子的锁");
Thread.sleep(2000);

synchronized (mirror) { //2秒钟后想拿到口红
System.out.println(this.girlname + "获得口红的锁");
}
}
}
}
}

避免死锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipsStick) { //拿到口红的锁
//获得口红的锁
System.out.println(this.girlname + "获得口红的锁");
Thread.sleep(1000);
}
synchronized (mirror) { //1秒钟后想获得镜子
System.out.println(this.girlname + "获得镜子的锁");
}
} else { //拿到镜子的锁
synchronized (mirror) {
//获得口红的锁
System.out.println(this.girlname + "获得镜子的锁");
Thread.sleep(2000);
}
synchronized (mirror) { //2秒钟后想拿到口红
System.out.println(this.girlname + "获得口红的锁");
}
}
}

8.2 死锁避免的方法

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

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源

解决方法:就是解决上面的四个条件

8.3 Lock(锁)

之前不安全的案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//测试lock锁

public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();

new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}


class TestLock2 implements Runnable{
int tickNums = 10;

public void run() {
while (true){
if (tickNums > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else break;
}
}
}

定义lock锁:

1
2
//定义lock锁(可重入锁)
private final ReentrantLock lock = new ReentrantLock();

使用lock锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();

new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}


class TestLock2 implements Runnable{
int tickNums = 10;

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

public void run() {
while (true){
try {
lock.lock();// 加锁
if (tickNums > 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else break;
}finally {
// 解锁
lock.unlock();
}
}
}
}

8.4 Synchronized和Lock的对比

9. 线程协作/线程通信

解决线程之间通信的方法:

解决方法 1:找个第三者缓冲区

解决方法 2:信号灯法(设置标志位)

9.1 管程法

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108


//测试:生产者消费者模型 (利用缓冲区):管程法

public class TestPc {
public static void main(String[] args) {
Container container = new Container();
new Producter(container).start();
new consumer(container).start();
}
}


//生产者
class Producter extends Thread{
Container container;
public Producter(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 + "只鸡");
}
}
}


//产品
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;
}
}

9.2 信号灯法

  • 利用标志位
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
//测试声称这消费者问题2 : 信号灯法(标志位解决)

public class TestPc2 {
public static void main(String[] args) {
TV tv = new TV();
new actor(tv).start();
new watcher(tv).start();
}
}



//生产者:演员
class actor extends Thread{
TV tv;
public actor(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 { // T:(演员表演,观众等待) F:(观众观看,演员等待)
//演员表演
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;
}
}

10. 线程池

ExecutorService:线程池的接口

线程池示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//测试线程池
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());
service.execute(new Mythread());

//2. 关闭连接
service.shutdownNow();
}
}


class Mythread implements Runnable{

public void run() {
System.out.println(Thread.currentThread().getName());
}
}

结果如下:

1
2
3
4
5
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2019-2022 Zhuuu
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信