java 线程基本使用

创建线程的两种方式

继承 Thread 类,重写 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.hspedu.threaduse;

public class Thread01 {
public static void main(String[] args) {
//创建Cat对象,可以当线程使用
Cat cat = new Cat();
cat.start();//启动线程=>最终会执行cat的run方法
// cat.run();//run方法就是一个普通的方法,没有真正的启动一个线程,就会把run方法执行完毕,才向下执行
//说明:当main线程启动一个子线程 Thread-0,主线程不会阻塞,会继续执行
//这时 主线程和子线程是交替执行..
System.out.println("主线程继续执行"+Thread.currentThread().getName());//名字main
for (int i = 0; i <40; i++) {
System.out.println("主线程 i="+i+Thread.currentThread().getName());
//让主线程休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

}

}
}
//1、当一个类继承了 Thread累,该类就可以当做线程了
//2、我们会重写run方法,写上自己的业务代码
//3、run Thread 类 实现了 Runnable 接口的run方法
class Cat extends Thread{
int times = 0;
@Override
public void run() { //重写run类,写上自己的业务逻辑
while (true){
//该线程每隔1秒。在控制台输出
System.out.println("test,线程输出"+(++times)+Thread.currentThread().getName());
//让该线程休眠1秒 Ctrl+alt+t
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(times ==80){
break;
}

}

}
}

实现 Runnable 接口,重写 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
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
package com.hspedu.threaduse;

public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
new Thread(t1).start();
new Thread(t2).start();



}
}
class T1 implements Runnable{
int count =0;


@Override
public void run() {
while (true) {
System.out.println("hiT1~~~"+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(count == 60){
break;

}

}


}
}

class T2 implements Runnable{
int count =0;
@Override
public void run() {
while (true) {
System.out.println("hiT2~~~-------------"+(++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if(count == 50){
break;

}


}


}
}


继承 Thread vs 实现 Runnable 的区别

  1. 从 java 的设计来看,通过击沉 Thread 或者实现 Runnable 接口来创建线程本质上没有区别,从 jdk 帮助文档我们可以看到 Thread 类本身就实现了 Runnable 接口 start() => start0() 。

  2. 实现 Runnable 接口方式更加适合多个线程共享一个资源,并且避免了单继承的限制。

  3. [售票系统],编程模拟三个售票窗口售票 100,分别使用 继承 Thread 和实现 Runnable 方式,并分析有什么问题?

    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
    package com.hspedu.ticket;

    /**
    * 使用多线程,模拟三个窗口同时售票
    */
    public class SellTicket {
    public static void main(String[] args) {

    // 测试
    // SellTicket01 sellTicket01 = new SellTicket01();
    // SellTicket01 sellTicket02 = new SellTicket01();
    // SellTicket01 sellTicket03 = new SellTicket01();
    // sellTicket01.start();
    // sellTicket02.start();
    // sellTicket03.start();


    SellTicket02 sellTicket02 = new SellTicket02();
    new Thread(sellTicket02).start();//第1个线程-窗口
    new Thread(sellTicket02).start();//第2个线程-窗口
    new Thread(sellTicket02).start();//第3个线程-窗口


    }
    }
    class SellTicket01 extends Thread{
    private static int ticketNum = 100; //让多个线程共享 ticketNum
    @Override
    public void run() {
    while (true){
    if (ticketNum <= 0){
    System.out.println("售票结束...");
    break;
    }

    try {
    //休眠50毫秒
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }

    System.out.println("窗口"+Thread.currentThread().getName() + "售出一张票" + "剩余票数="+(--ticketNum));


    }

    }
    }
    class SellTicket02 extends Thread{
    private static int ticketNum = 100; //让多个线程共享 ticketNum
    @Override
    public void run() {
    while (true){
    if (ticketNum <= 0){
    System.out.println("售票结束...");
    break;
    }

    try {
    //休眠50毫秒
    Thread.sleep(50);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }

    System.out.println("窗口"+Thread.currentThread().getName() + "售出一张票" + "剩余票数="+(--ticketNum));


    }

    }
    }

线程终止

  1. 当线程完成任务后,会自动退出。

  2. 还可以通过**使用变量来控制 un 方法退出的方式停止线程,即通知方式**

    案例:启动一个线程 t,要求在 main 线程中去停止线程 t。

    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
    package com.hspedu.exit;

    import javax.swing.plaf.TableHeaderUI;

    public class ThreadExit {
    public static void main(String[] args) throws InterruptedException {
    T t1 = new T();
    new Thread(t1).start();

    //如果希望main线程去控制t1 线程的终止,必须可以修改 loop
    //让t1 退出run方法,从而终止 t1线程 -> 通知方式

    //让主线程休眠 10 秒,再通知 t1线程退出
    System.out.println("main线程休眠10s");
    Thread.sleep(10*1000);
    t1.setLoop(false);



    }
    }
    class T extends Thread{
    private int count = 0;
    private boolean loop = true;
    @Override
    public void run() {
    while (loop){
    try {
    Thread.sleep(50);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    System.out.println("hi~"+(++count));
    }

    }
    public void setLoop(boolean loop) {
    this.loop = loop;
    }
    }

线程常用方法

常用方法第一组

  1. setName: 设置线程名称,使之与参数 name 相同

  2. getName:返回该线程的名称

  3. start: 使该线程开始执行;java 虚拟机底层调用该线程的 start0 方法

  4. run: 调用线程对象的 run 方法;

  5. setPriority: 更改线程的优先级

  6. getPriority: 获取线程的优先级

  7. sleep: 在指定的毫秒数内让当前执行的线程休眠(暂停执行)

  8. interrupt: 中断线程

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.hspedu.method;

public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.setName("zao_an");
t.setPriority(Thread.MIN_PRIORITY);
t.start();// 启动子线程

System.out.println(t.getName());

// 主线程打印5 hi ,然后中断,子线程休眠
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi~"+i);
}
System.out.println(t.getName()+"线程的优先级 ="+t.getPriority());
t.interrupt();

}
}
class T extends Thread{
@Override
public void run() {
while (true){
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+" 吃包子"+i);
}
System.out.println(Thread.currentThread().getName()+"休眠中~~~");
try {
Thread.sleep(20000);//5秒
} catch (InterruptedException e) {
//当该线程执行到一个interrupt 方法时,就会catch 一个异常, 可以加入自己的业务代码
//InterruptedException 是捕获到一个中断异常.
System.out.println(Thread.currentThread().getName()+"被 interrupt了");
}

}

}
}

常用方法第二组

  1. yield: 线程的礼让。让出 cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功。

  2. join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务

    案例:main 线程创建一个子线程,每隔 1s 输出 hello,输出 20 次,主线程每隔 1 秒,输出 hi,输出 20 次。要求:两个线程同时执行,当主线程输出 5 次后,就让子线程运行完毕,主线程再继续。

    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.hspedu.method;

    public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
    T2 t2 = new T2();
    t2.start();

    for (int i = 1; i <= 20; i++) {
    Thread.sleep(1000);
    System.out.println("主线程唠嗑 "+i+"次");
    if(i == 5){
    System.out.println("主线程礼让中ing~~~");
    // System.out.println("让子线程插队先执行~");
    // join,线程插队
    // t2.join();
    // System.out.println("插队完毕,主线程继续执行");
    Thread.yield();
    System.out.println("礼让线程,主线程继续执行");

    }


    }


    }
    }
    class T2 extends Thread{
    @Override
    public void run() {
    for (int i = 1; i <= 20; i++) {

    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    System.out.println("子线程运行唠嗑 "+i+"次");

    }

    }
    }

线程常用方法

  • 用户线程和守护线程

    1. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束

    2. 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

    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
      31
      package com.hspedu.method;

      public class ThreadMethod03 {
      public static void main(String[] args) throws InterruptedException {
      MyDaemonThread myDaemonThread = new MyDaemonThread();
      //如果我们希望当main线程结束后,子线程自动结束
      //将子线程设为守护线程即可
      myDaemonThread.setDaemon(true);
      myDaemonThread.start();
      for (int i = 1; i <= 10; i++) {//main线程
      System.out.println("主线程在工作中ing~" + i);
      Thread.sleep(1000);
      }
      }
      }

      class MyDaemonThread extends Thread {
      @Override
      public void run() {
      for (; ; ) {
      try {
      Thread.sleep(1000);
      } catch (InterruptedException e) {
      throw new RuntimeException(e);
      }
      System.out.println("子线程死循环中ing~");
      }
      }
      }


线程的生命周期

  • JDK 中用 Thread.State 枚举表示了线程的几种状态

    public static enum Thread.State

    extends Enum<Thread.State>

    线程状态,线程可以处于以下状态之一

    • NEW

      尚未启动的线程处于此状态。

    • RUNNABLE

      在 Java 虚拟机中执行的线程处于此状态

    • BLOCKED

      被阻塞等待监视器锁定的线程处于此状态

    • WAITING

      正在等待另一个线程执行特定动作的线程处于此状态。

    • TIMED_WAITING

      正在等待另一个线程执行动作达到指定等待时间的线程处于此状态

    • TERMINATED

      已退出的线程处于此状态

  • 线程状态转换图

  • 查看线程状态代码

    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
    package com.hspedu.state_;

    public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
    T t = new T();
    System.out.println(t.getName() + " 状态 " + t.getState());
    t.start();
    while (Thread.State.TERMINATED != t.getState()) {
    System.out.println(t.getName() + " 状态 " + t.getState());
    Thread.sleep(500);
    }
    System.out.println(t.getName() + " 状态 " + t.getState());
    }
    }

    class T extends Thread {
    @Override
    public void run() {
    while (true) {
    for (int i = 1; i <= 10; i++) {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    System.out.println("hi~" + i);
    }
    break;
    }

    }
    }

线程同步机制

  1. 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
  2. 也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
  • 同步具体方法 - Synchronized

    1. 同步代码块

      1
      2
      3
      4
      synchronized(对象){ //得到对象的锁,才能操作同步代码
      // 需要被同步代码

      }
    2. Synchronized 还可以放在方法声明中,表示整个方法-为同步方法

      1
      2
      3
      4
      public synchronized void m (String name){
      // 需要被同步的代码

      }
    3. 使用 synchronized 解决售票问题

    package com.hspedu.syn;
    
    /**
     * 使用多线程,模拟三个窗口同时售票
     */
    public class SellTicket {
        public static void main(String[] args) {
            SellTicket03 sellTicket03 = new SellTicket03();
            new Thread(sellTicket03).start();//第1个线程-窗口
            new Thread(sellTicket03).start();//第2个线程-窗口
            new Thread(sellTicket03).start();//第3个线程-窗口
    
        }
    }
    //实现接口方式,使用synchronized实现线程同步
    class SellTicket03 extends Thread {
        private static int ticketNum = 100; //让多个线程共享 ticketNum
        private  boolean loop = true; //控制run方法变量
        public  synchronized void sell(){//同步方法,在同一时刻,只能有一个线程来执行run方法
            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                loop = false;
                return ;
            }
            try {
                //休眠50毫秒
                Thread.sleep(50);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" + "剩余票数=" + (--ticketNum));
        }
        @Override
        public  void run() {
    
            while (loop) {
                sell();//sell方法是一个同步方法
            }
    
        }
    
    
    }