前言

b站java课程学习笔记整理。

b站视频: 黑马程序员全套Java教程_Java基础入门视频教程,零基础小白自学Java必备教程

324. 进程和线程

线程是依赖于进程而存在。

进程:

  • 正在运行的应用程序
  • 是系统进行资源分配和调用的独立单位
  • 每一个进程都有它自己的内存空间和系统资源

线程:

  • 是进程中单个顺序控制流,是一条执行路径。
  • 单线程: 一个进程如果只有一条执行路径,则称为单线程程序(例如记事本设置时不能接着打字)
  • 多线程:一个进程如果有多条执行路径,则称为多线程程序(例如扫雷游戏进行时,计时器不受鼠标影响正常工作)

325. 继承Thread类的方式实现多线程

线程时程序中执行的线程。Java虚拟机允许应用程序同时执行多个线程。

多线程的实现方式:

方式1:继承Thread

  • 定义一个类MyThread继承hread
  • MyThread类中重写run()方法
  • 创建MyThread类的对象
  • 启动线程
1
2
3
4
5
6
7
8
9
10
11
package 线程;

public class MyThread extends Thread{
@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println(i);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package 线程;

public class Demo{
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();


// run方法的调用并没有启用线程。还是单线程按顺序执行。
// m1.run();
// m2.run();

m1.start();
m2.start();
}
}

326. 设置和获取线程名称

Thread类中设置和获取线程名称的方法:

  • void setName(String name): 将线程的名称设置为name
  • String getName()返回此线程的名称

类要继承Thread

可以无参构造或者带参构造线程。Thread.currentThread()返回当前正在执行的的线程对象的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package 线程;

public class MyThread extends Thread{

public MyThread() {
}

public MyThread(String name) {
super(name);
}

@Override
public void run() {
for(int i = 0; i < 100; i++){
System.out.println(getName()+","+ i);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package 线程;

public class SetGetDemo {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();


public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.setName("线程1");
m2.setName("线程2");
MyThread m3 = new MyThread("线程3");

m1.start();
m2.start();
m3.start();

System.out.println(Thread.currentThread().getName()); //main
}
}

327. 线程优先级

线程有两种调度模型:

  • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用cpu的时间片
  • 抢占式调度模型: 优先让优先级高的线程使用cpu,如果线程的时间级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些。

Java使用的式抢占式调度模型。

cpu在某一时刻只能执行一条指令,线程只有得到cpu时间片,也就是使用权,才可以执行指令。所以说相同优先级的多线程执行具有随机性。

设置和获取线程的优先级:

  • public final int getPriority(): 返回此线程的s优先级。
  • public final void setPriority(int newPriority): 更改此线程的优先级。
  • 线程范围的优先级最小值和最大值,默认值(可以设置的范围)为:MAX_PRIORITY = 10;MIN_PRIORITY = 1; ,NORM_PRIORITY = 5.

优先级高只是代表获取时间片的几率高,并不意味着每次都要跑到最前面。要在次数比较多时才能看到效果

328. 线程控制

方法名 说明
static void sleep(long millies) 使当前正在执行的线程停留(暂停执行)指定的毫秒数
void join() 等待这个线程死亡(必须等待这个线程执行完毕,别得线程才能执行)
void setDaemon(boolean on) 将此线程标记为守护线程,主线程执行完毕后,这些守护线程应该立即结束。
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
package 线程;

public class SetGetDemo {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();


public static void main(String[] args){
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.setName("线程1");
m2.setName("线程2");
MyThread m3 = new MyThread("线程3");

Thread.currentThread().setName("主线程");

System.out.println(m1.getPriority());
System.out.println(m2.getPriority());
System.out.println(m3.getPriority());

m1.start();
try {
m1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
m2.setDaemon(true);
m3.setDaemon(true);

m2.start();
m3.start();


for(int i = 0; i<10; i++){
System.out.println(Thread.currentThread().getName()+","+i);

}
}
}

329. 线程的生命周期

线程生命周期

330. 实现Runnable接口的方式实现多线程

  1. 定义一个类MyRunnable实现Runnable接口。
  2. MyRunnable类中重写run()方法。
  3. 创建MyRunnable类的对象。
  4. 创建Thread类的对象,把MyRunnable对象作为构造方法的参数。
  5. 启动线程。
1
2
3
4
5
6
7
8
9
10
11
12
package Runnable实现多线程;

public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0; i< 100; i++){
//不能直接使用getname方法。
System.out.println(Thread.currentThread().getName()+","+i);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package Runnable实现多线程;

public class Demo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();

Thread t1 = new Thread(mr,"高铁");
Thread t2 = new Thread(mr,"飞机");

t1.start();
t2.start();
}
}

这种方法并没有继承Thread类,这样他就可以有自己的父类了。

多线程的实现方案:

  • 继承Thread
  • 实现Runnable接口

Runnable的好处:

  • 避免了单继承的局限性。
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想。

331. 卖票

有一百张票,三个窗口同时实现卖票。

思路:

  1. 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;

  2. SellTicket类中重写run()方法实现卖票,代码步骤如下:

    • 判断票数大于0,就卖票,并告知对应的卖票窗口。
    • 卖了票之后总数要减一。
    • 票没了,也可能有人来问。所以用死循环让卖票动作一直执行。
  3. 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下

    • 创建SellTicket类的对象
    • 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称。
    • 启动线程
    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 卖票;

    public class SellTicket implements Runnable{
    private int tickets = 100;

    public int getTickets() {
    return tickets;
    }

    public void setTickets(int tickets) {
    this.tickets = tickets;
    }

    @Override
    public void run() {

    while(true){
    if(getTickets()>0){
    System.out.println(Thread.currentThread().getName()+"正在出售第"+getTickets()+"张票");
    tickets--;
    }

    }
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package 卖票;

    public class SellTicketDemo {
    public static void main(String[] args) {
    SellTicket sellTicket = new SellTicket();

    Thread t1 = new Thread(sellTicket,"售票窗口1");
    Thread t2 = new Thread(sellTicket,"售票窗口2");
    Thread t3 = new Thread(sellTicket,"售票窗口3");


    t1.start();
    t2.start();
    t3.start();
    }
    }

    332. 卖票案例的思考

    在出一张票的时候需要延迟100ms。

    出现三次卖第100张票的原因(相同的票出现了多次):

    1. t1线程抢到时间片,开始执行,然后休眠进入阻塞态。
    2. t2线程抢到时间片,开始执行,然后休眠进入阻塞态。
    3. t3线程抢到时间片,开始执行,然后休眠进入阻塞态。
    4. t1休息完了,抢到时间片,开始出第100张票。他还没开始做tickets--的时候
    5. t2休息完了,抢到时间片,开始出第100张票。他还没开始做tickets--的时候
    6. t3休息完了,抢到时间片,开始出第100张票。他还没开始做tickets--
    7. 执行了三次tickets--的操作,票数到了97。但是输出了三次100.

    出现卖第负数张票的原因:其实道理是一样的。(1, 0 , -1)。

    根本原因:线程执行的随机性。

    333. 同步代码块解决数据安全问题

    为什么出现问题?

    • 是否有多线程环境
    • 是否有共享数据
    • 是否有多条语句操作共享数据

    如何解决这个安全问题?

    • 破坏掉安全问题的环境(去除三个条件其一)

    怎么实现呢?

    • 把代码锁起来,让任意时刻只有一个进程进入。
    • java使用同步代码块
    • 格式
    1
    2
    3
    synchronized(任意对象){
    多条语句操作的代码
    }

    synchronized(任意对象){}就相当于上锁了。

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 卖票;

public class SellTicket implements Runnable{
private int tickets = 100;

public int getTickets() {
return tickets;
}

public void setTickets(int tickets) {
this.tickets = tickets;
}

private Object obj = new Object();

@Override
public void run() {

while(true){
synchronized (obj) {
if (getTickets() > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + getTickets() + "张票");
tickets--;
}
}

}


}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package 卖票;

public class SellTicketDemo {
public static void main(String[] args) {
SellTicket sellTicket = new SellTicket();

Thread t1 = new Thread(sellTicket,"售票窗口1");
Thread t2 = new Thread(sellTicket,"售票窗口2");
Thread t3 = new Thread(sellTicket,"售票窗口3");


t1.start();
t2.start();
t3.start();
}
}

334. 同步方法解决数据安全问题

同步方法就是把synchronized关键字加到方法上。

  • 格式:
    • 修饰符synchronized返回值类型方法名(方法参数){}

同步方法锁的是**this对象**。

同步静态方法:synchronized关键字加到静态方法上。

  • 格式:
    • 修饰符 static synchronized 返回值类型方法名 (方法参数){}

静态同步方法的锁是:

  • 类名.class

335. 线程安全的类

StringBuffer

  • 更安全
  • StringBuilder替代(但StringBuilder不是同步方法,不安全)

Vector

  • ArrayList代替Vector(但ArrayList不是同步方法,不安全)

Hashtable

  • HashMap可以代替,但同样,HashMap不安全。

多线程环境下使用:

Collections.synchronizedList(new ArrayList<String>() )包装成一个线程安全的类。

336. Lock锁

Lock实现提供比使用synchronized方法和语句可以更广泛的锁定操作。

Lock中提供了获得锁和释放锁的方法。

  • void lock(): 获得锁
  • void unlock(): 释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。

ReentrantLock的构造方法:

  • ReentrantLock(): 创建一个ReentrantLock的实例
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 卖票;

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

public class SellTicket implements Runnable{
private int tickets = 100;

public int getTickets() {
return tickets;
}

public void setTickets(int tickets) {
this.tickets = tickets;
}

private Lock lock = new ReentrantLock();

@Override
public void run() {

while(true){
try {
lock.lock();
if (getTickets() > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + getTickets() + "张票");
tickets--;
}
}finally {
lock.unlock();
}


}
}
}

一般使用try...finally的方法来在程序结束后释放锁。

337. 生产者消费者模式概述

生产者消费者模式:

  • 一类是生产者线程用于生产数据
  • 一类是消费者线程用于消费数据

为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库。

  • 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为。
  • 消费者只需要从共享数据区中获取数据,并不需要关心生产者的行为。

为了体现等待和唤醒问题:

方法名 说明
void wait() 导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法
void nofity() 唤醒正在等待对象监视器的单个线程
void notifyAll() 唤醒正在等待对象监视器的所有线程

338. 生产者消费者案例

模拟牛奶工送奶。

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 生产者和消费者;

public class Box {
private int milk;
private boolean state = false;

public synchronized void put(int milk){
if(state){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.milk = milk;
System.out.println("送奶工将第"+this.milk+"瓶奶放入奶箱");
state = true;
notifyAll();



}

public synchronized void get(){
if(!state){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("用户拿到第"+this.milk+"瓶奶");
state = false;
notifyAll();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package 生产者和消费者;

public class Producer implements Runnable{
private Box b;
public Producer(Box b) {
this.b = b;
}

@Override
public void run() {
for(int i= 1;i<=5;i++){
b.put(i);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package 生产者和消费者;

public class Customer implements Runnable{
private Box b;
public Customer(Box b) {
this.b = b;
}

@Override
public void run() {
while(true){
b.get();
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package 生产者和消费者;

public class Demo {
public static void main(String[] args) {
Box b = new Box();

Producer producer = new Producer(b);
Customer customer = new Customer(b);

Thread t1 = new Thread(producer);
Thread t2 = new Thread(customer);

t1.start();
t2.start();
}

}