JavaSE之多线程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaSE之多线程相关的知识,希望对你有一定的参考价值。

今天打算重新学习一遍多线程,通过条理的的梳理,更加巩固基础知识。

谈起多线程,我们需要分清楚一些概念,什么是程序、进程和线程?

  • 程序(program):是为了完成特定任务,用某种语言编写的一组指令的集合,指的是一段静态的代码,静态对象
  • 进程(process):是程序的一段执行过程,或者是正在运行的一个程序。动态过程:有它自身的产生,存在和消亡的过程
  • 线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径

线程与进程的关系:

  这里一个进程可以分为单线程进程多线程进程,它们之间的关系如图所示:

技术分享图片

 

 

 

 

 

 

 

什么时候需要多线程?

  • 程序需要同时执行两个或多个任务时
  • 程序需要实现一些需要等待的任务时
  • 需要一些后台运行的程序时

多线程程序的优点:

  • 提高程序相应速度,增强用户体验
  • 提高CPU的利用率
  • 改善程序结构,将既长又复杂的进程分成多个线程,有助于修改和理解

如何创建线程呢,创建线程的方法有两种:

  • 一是将该类声明为Thread的子类,该子类要重写Thread的run()方法
  • 二是将该类声明实现Runnable接口,然后实现接口的run()方法
 1 //1.创建一个继承于Thread类的子类
 2 class SubThread extends Thread {
 3 
 4     // 2.重写Thread类的run()方法,此方法内为线程要执行的任务
 5     public void run() {
 6         for (int i = 0; i < 100; i++) {
 7             System.out.println(Thread.currentThread().getName() + ":" + i);
 8         }
 9     }
10 }
11 
12 // 3.实现Runnable接口
13 class InstRunnable implements Runnable {
14 
15     // 4. 实现Runnable接口的run()方法
16     public void run() {
17         for (int i = 0; i < 100; i++) {
18             System.out.println(Thread.currentThread().getName() + ":" + i);
19         }
20     }
21 }
22 
23 public class ThreadMain {
24 
25     public static void main(String[] args) {
26 
27         // 5.实例化一个线程实例
28         SubThread st = new SubThread();
29 
30         // 6.调用start()方法启动线程,然后调用相应的run()方法
31         st.start();
32 
33         // 线程只能执行一次start(),多次执行报错
34         // st.start();
35 
36         // run()方法不能直接启动线程,调用run()方法无变化
37         // st.run();
38 
39         // 7.实例化InstRunnable类
40         InstRunnable ir = new InstRunnable();
41 
42         // 8.启动线程,并调用run()方法
43         new Thread(ir).start();
44     }
45 
46 }

 

实现Runnable接口创建线程的优点:

  • Thread本质上也是实现Runnable接口 class Thread implements Runnable{……} 
  • 使用Runnable接口可以避免Java的单继承的局限性
  • 若多个线程操作同一份数据,只需实现一次,然后多次生成Thread实例即可,所以实现更适合
  •  1 class Window implements Runnable {
     2     int ticket = 100;
     3     public void run() {
     4         while(true) {
     5             if(ticket>0) {
     6                 System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
     7             }else {
     8                 break;
     9             }
    10         } 
    11     }
    12 }
    13 
    14 public class TestWindow1 {
    15 
    16     public static void main(String[] args) {
    17         Window w = new Window();
    18         
    19         Thread w1 = new Thread(w);
    20         Thread w2 = new Thread(w);
    21         Thread w3 = new Thread(w);
    22         
    23         w1.setName("窗口1");
    24         w2.setName("窗口2");
    25         w3.setName("窗口3");
    26         
    27         w1.start();
    28         w2.start();
    29         w3.start();
    30     }
    31 
    32 }

     

Thread类常用的几个方法:

  • start():启动线程并执行相应的run()方法
  • run():子线程要执行的代码放入run()方法中
  • currentThread():静态方法,调取当前的线程
  • getName():获取此线程的名字
  • setName():设置此线程的名字
  • yield():调用此方法的线程释放当前CPU的执行权
  • join():在A线程中调用B线程的join()方法,表示执行到B.join()方法时,A停止执行,直到B执行完毕,A继续执行
  • sleep(long x):显式的让线程睡眠x毫秒
  • getPriority():获取线程的优先级
  • setPriority(int n):设置线程的优先级(n的范围是1到10)
 1 package cn.lyog.javase;
 2 
 3 class SubThread extends Thread {
 4 
 5     // 子线程要执行的代码放入run()方法中
 6     public void run() {
 7         for (int i = 0; i < 100; i++) {
 8             try {
 9                 // 静态方法,调取当前的线程
10                 Thread.currentThread();
11 
12                 // 显式的让线程睡眠100毫秒
13                 Thread.sleep(100);
14             } catch (InterruptedException e) {
15                 e.printStackTrace();
16             }
17 
18             // getName()用来获取此线程的名字
19             System.out.println(Thread.currentThread().getName() + ":" + i);
20         }
21     }
22 }
23 
24 public class ThreadMain {
25 
26     public static void main(String[] args) {
27 
28         SubThread st = new SubThread();
29 
30         // 设置st线程的名字
31         st.setName("子线程");
32 
33         // 启动线程并执行相应的run()方法
34         st.start();
35 
36         // 设置此线程的名字
37         Thread.currentThread().setName("主线程");
38 
39         /*
40          * 设置线程的优先级(n的范围是1到10) 
41          * MAX_PRIORITY的值为10 
42          * NORM_PRIORITY的值为5 
43          * MIN_PRIORITY的值为1
44          */
45         Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
46 
47         for (int i = 0; i < 100; i++) {
48 
49             // 获取线程的名字
50             System.out.println(Thread.currentThread().getName() + ":" + i);
51 
52             // 获取线程的优先级
53             System.out.println(Thread.currentThread().getPriority() + ":" + i);
54 
55             if (i > 10) {
56                 try {
57                     // 当i>10时,执行st,直到st执行完毕,继续执行main
58                     st.join(10);
59                 } catch (InterruptedException e) {
60                     e.printStackTrace();
61                 }
62             }
63         }
64     }
65 }

 线程分为两类:守护线程用户线程

  • 守护线程与用户线程本质上是相同的,区别区别就是判断JVM何时离开。
  • 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)方法,可以把用户线程变成守护线程。
  • Java垃圾回收就是一个守护线程。
  • 若JVM中全为守护线程,意味着用户线程都没了,所以当前JVM退出

线程的生命周期:

  • 新建:当一个Thread类或其子类被声明创建时
  • 就绪:具备CPU的执行资格,但没有CPU执行权
  • 运行:既具备CPU的执行资格,同时具备着CPU的执行权
  • 阻塞:释放了CPU执行权,同时释放了CPU执行资格
  • 消亡:当线程完成全部工作或被提前终止

 技术分享图片

线程安全问题的原因:由于一个线程在操作共享数据的过程中,未执行完毕的情况下,另外一个线程参与进来,导致共享数据存在安全问题。

线程安全问题的解决方案:让一个线程执行完毕后,其他线程才能参与进来。(线程同步机制

  • 同步代码块
  • 同步方法
  •  1 class Windows implements Runnable {
     2     int ticket = 100;
     3     Object obj = new Object();
     4 
     5     @Override
     6     public void run() {
     7         // 同步代码块
     8         synchronized (obj) {
     9             // 操作共享数据ticket的代码
    10         }
    11     }
    12 
    13 }
    14 
    15 class Windows1 implements Runnable {
    16     int ticket = 100;
    17 
    18     public synchronized void show() {
    19         // 操作共享数据ticket的代码
    20     }
    21 
    22     @Override
    23     public void run() {
    24         // 同步方法
    25         show();
    26     }
    27 
    28 }

 释放锁的操作有:

  • 同步方法、同步代码块执行结束
  • 同步方法、同步代码块中遇到break或return终止执行
  • 同步方法、同步代码块中遇到Error或Exception异常结束
  • 同步方法、同步代码块中执行wait()方法释放锁

不会释放锁的操作有:

  • sleep()方法
  • yield()方法

线程的死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

解决方法:

  • 专门的算法、原则
  • 尽量减少同步资源的定义

 

 1 package cn.lyog.javase;
 2 
 3 public class TestDeadLock {
 4     static StringBuffer sb1 = new StringBuffer();
 5     static StringBuffer sb2 = new StringBuffer();
 6 
 7     public static void main(String[] args) {
 8         new Thread() {
 9             public void run() {
10                 synchronized (sb1) {
11                     try {
12                         Thread.currentThread();
13                         Thread.sleep(10);
14                     } catch (InterruptedException e) {
15                         e.printStackTrace();
16                     }
17                     sb1.append("A");
18                     synchronized (sb2) {
19                         sb2.append("B");
20                         System.out.println(sb1);
21                         System.out.println(sb2);
22                     }
23                 }
24             }
25         }.start();
26         new Thread() {
27             public void run() {
28                 synchronized (sb2) {
29                     try {
30                         Thread.currentThread();
31                         Thread.sleep(100);
32                     } catch (InterruptedException e) {
33                         e.printStackTrace();
34                     }
35                     sb1.append("C");
36                     synchronized (sb1) {
37                         sb2.append("D");
38                         System.out.println(sb1);
39                         System.out.println(sb2);
40                     }
41                 }
42             }
43         }.start();
44     }
45 
46 }

线程通讯,在java.lang.Object包下面定义了三个方法:wait()、notify()和notifyAll(),它们只能在synchronized方法或者synchronized代码块中才能执行

  • wait():令当前线程挂起放弃CPU、同步资源,使别的线程可以访问并修改同步资源,而当前线程排队等候在此对资源的访问
  • notify():唤醒正在排队等待同步资源的线程中优先级最高的结束等待
  • notifyAll():唤醒所有正在排队等待同步资源的线程结束等待
 1 // 线程通讯之交替打印数字
 2 class PrintNum implements Runnable {
 3 
 4     int num = 1;
 5 
 6     @Override
 7     public void run() {
 8         while (true) {
 9             synchronized (this) {
10                 notify();
11                 if (num <= 100) {
12                     System.out.println(Thread.currentThread().getName() + ":" + num);
13                     num++;
14                 } else {
15                     break;
16                 }
17 
18                 try {
19                     wait();
20                 } catch (InterruptedException e) {
21                     e.printStackTrace();
22                 }
23             }
24 
25         }
26 
27     }
28 
29 }
30 
31 public class TestCommunication {
32 
33     public static void main(String[] args) {
34         PrintNum pn = new PrintNum();
35 
36         Thread t1 = new Thread(pn);
37         Thread t2 = new Thread(pn);
38 
39         t1.setName("甲");
40         t2.setName("乙");
41 
42         t1.start();
43         t2.start();
44 
45     }
46 }

 

以上是关于JavaSE之多线程的主要内容,如果未能解决你的问题,请参考以下文章

JavaSE线程基础

Java基础之多线程

网络编程之多线程——守护线程

python高性能代码之多线程优化

java之多线程的理解

Python之多任务编程线程