1-线程的基本使用

一、简单定义

  1. 什么是进程

    image-20221224190211563

2.线程与进程

线程由进程创建。

3.并发与并行

image-20221224190252847

二、线程的使用

  1. 使用 继承 thread 类。重写 run 方法

  2. 实现 Runnable 接口,重写 run 方法,并将其对象传入 thread 的构造器中创建 thread 对象

1.Extend 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
public class threadsEXtend {
public static void main(String[] args) throws InterruptedException {
Dog dog = new Dog();
dog.start();
for (int i = 0; i < 20; i++) {
System.out.println("这是主线程"+Thread.currentThread().getName());
Thread.sleep(1000);
}
}
}
class Dog extends Thread{
@Override
public void run() {
int count=1;
while (true){
System.out.println("小狗汪汪"+"线程名"+currentThread().getClass()+count++);
try {
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count==30){
break;
}
}
}
}

2.implement Runnable 解决该类已经有 基继承的类

Thread 中具有代理模式的设计模式。

具有 一个 runnable 类型 的 Target

然后还有一个 构造器,可以接受一个 实现了 runnale 接口的类。

因为 runnable 接口没有 start 方法,创建 线程需要调用 start 方法,所以需要 thread 类进行代理。

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

public class ThreadProxy {
public static void main(String[] args) {
Cat cat = new Cat();
Thread thread = new Thread(cat);
thread.run();
}
}
class Cat implements Runnable{
@Override
public void run() {
int count=1;
for (int i = 0; i < 30; i++) {
System.out.println("线程名" +"第"+count++ +"此");

}
}
}

3. extend 和 implement runnable 的区别

  1. Runnable 更适合 多个线程共享一个资源的情况,并且避免了单继承的限制

    1
    2
    T3 t3=new T3();

    image-20221224195957206

4.通知线程退出

  1. 线程完成任务后,会自动退出
  2. 可以使用==控制变量==来控制 run 方法退出的方式停止进程,即通知方式。
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
安丽
package com.threadUser;

public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
exitTest exitTest = new exitTest();
exitTest.start();
System.out.println("等待五秒后手动关闭");
Thread.sleep(5*1000);
exitTest.setLoop(false);
}
}
class exitTest extends Thread{
private boolean loop=true;
int count=1;
@Override
public void run() {
while (loop){
System.out.println("正在运行"+count++);
try {
//等待500毫秒
sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void setLoop(boolean loop){
this.loop=loop;
}
}

5.常用方法

  • 1.setName //设置线程名字
  • 2.getName //返回线程名称
  • 3.start 启动该线程,jvm 底层调用该进程的 start0 方法
  • 4.run //直接调用线程对象的 run 方法
  • 5-setPriority //更改线程的优先级
  • 6-getPriority //获取线程的优先级
    • min –1
    • max –10
    • normal –5
  • 7-sleep //让线程指定休眠 xxx 毫秒 (暂停执行)
  • 8- interrupt //中断==线程的休眠==
  • 9- yield :线程的礼让,让出 CPU ,让其他线程先执行,但礼让的时间不一定,所以礼让也==不一定成功==。
    • 是否礼让由系统资源决定,操作系统内核负责处理,这一块 java 无法控制
  • 10- join:线程的插队,插队的线程一旦插队成功,则肯定==先执行完插入的线程的所有任务==。
    • 小弟让大哥先吃完包子自己再吃。

6.用户线程和守护线程

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
线程对象.setDaemon(true) --->守护进程。
setDaemon要在 start 之前设置。
案例


package com.threadUser;

public class SetDamon_ {
public static void main(String[] args) {
damon_ damon_ = new damon_();
damon_.setDaemon(true);
damon_.start();
for (int i = 0; i < 5; i++) {
System.out.println(i);
if (i==4){
System.out.println("不,你是守护进程,你要和我一起走。");
}
}
}
}
class damon_ extends Thread{
@Override
public void run() {
while (true){
System.out.println("我还要继续!!");
}
}
}

image-20221224234828119

注意点

  1. 当处于 Runnable状态时,并不是一定在进行,而是由 操作系统的线程调度器负责 调配运行。—>由操作系统内核决定。(内核态)
  2. 官方文档为 6 种状态,但一般 又将 runnable 细分为两种转态,即有7 钟转态

可以使用 jconsole 工具 查看线程

image-20221224191429302

image-20221224191421528

三、生命周期

1.生命周期

image-20221224235800248

2.线程同步机制

1)线程同步机制 Synchronizd

一个敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。

image-20221225153246140

2.synchronize 使用

四、锁

image-20221225165651218

这里的同步方法是否是 静态的 非常重要。—Synchronized

如果是 this ,则是 当前对象,如果不同对象,不会造成堵塞。

如果是==静态(stastic)==, 则是 加在该类的 Class 中(类加载的知识) ,该==类的不同对象==,会造成==堵塞==。

1.注意事项和细节

  • 1.同步方法如果没有使用 static 修饰,默认锁对象就是 this

  • 2.同步方法使用了 static 修饰,默认锁对象:当前类的 Class 对象(通常通过 类名.class获得)

  • 3.实现锁的步骤

    • 1.分析需要上锁的代码

    • 2.选择==同步代码块(优先考虑)==或同步代码

    • 3.==要求多个线程的锁对象为同一个。==

      • 什么意思?
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        class Thread1{
        public synchronized void sayhi(){
        System.out.println("hi");
        }
        }
        思考:这里的 hi 能锁住吗?
        分析:
        1.同步方法没有使用 static 修饰,那默认锁对象为 this
        2.而想要调用 sayhi 方法,就要使用 new Thread1().sayhi
        3.而第2步中每次 调用都 new 了一个新对象,即此时如果使用 不同对象的 start 方法进行创建多线程。
        4.那此时,多个线程的锁对象就是他们对应的那个对象---->即不是同一个对象。不同的线程争夺的是不同的锁,那就无法锁住。
        怎么解决?
        1.绑定同一个对象,其他的对象,例如新建一个 object 对象
        2.给方法加上 static ,使其绑定类的Class 对象

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
package com.hspedu.syn;
/**
* @author 韩顺平
* @version 1.0
* 模拟线程死锁
*/
public class DeadLock_ {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A 线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B 线程");
A.start();
B.start();
}
}
//线程
韩顺平循序渐进学 Java 零基础
810
class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑的分析
//1. 如果 flag 为 T, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
//2. 如果线程 A 得不到 o2 对象锁,就会 Blocked
//3. 如果 flag 为 F, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
//4. 如果线程 B 得不到 o1 对象锁,就会 Blocked
if (flag) {
synchronized (o1) {//对象互斥锁, 下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入 1");
synchronized (o2) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入 3");
synchronized (o1) { // 这里获得 li 对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入 4");
}
}
}
}
}

3.释放锁

释放锁的问题。

image-20221225195634274

下列操作不会释放锁

  1. sleep、yield
    太困了,但还在厕所里面。
  2. 线程被挂起.suspend()。
    image-20221225200005591

image-20221225200201260

六、一些疑问

  1. 为什么调用的是 start 方法,而不是直接调用 run 方法

A: 直接调用 run 方法,那就是相当于普通的方法调用,只会在主栈中调用,不会创建新线程。而调用 start 方法可以创建新 线程并 会自动调用 run 方法。–>看源码分析

1
2
3
4
5
6
7
8

public synchronized void start() {
start0();
}
//start0() 是本地方法,是 JVM 调用, 底层是 c/c++实现
//真正实现多线程的效果, 是 start0(), 而不是 run
private native void start0();