介绍:Java提供了非常优秀的多线程支持,程序可以通过非常简单的方式来启动多线程。本章主要内容为:多线程的创建、启动、控制以及同步操作,并介绍JDK 5新增的线程创建方式。

一、线程的创建与使用

  1. 继承Thread类创建线程

  • 建立一个继承Thread的子类
  • 重写Thread类的run()--把所执行的操作写在该方法中
  • 创建一个子类对象
  • 通过该对象调用start()方法
1 //1.1. 建立一个继承Thread的子类
 2 class MyThread1 extends Thread{
 3     @Override
 4     //1.2. 重写Thread类的run()
 5     public void run() {
 6         for(int i=0;i<100;i++){
 7             if(i%2==0){
 8                 System.out.println(getName() + ":" + i);//Thread.currentThread().getName()
 9             }
10         }
11     }
12 }
13 
14 //1.3. 创建一个子类对象
15 MyThread1 t1 = new MyThread1();
16 t1.setName("线程1");
17 
18 //1.4. 通过该对象调用start()方法
19 t1.start();//注意,一个线程对象不能同时两次start,需要重新创建一个对象来start

  2. 实现Runnable接口创建线程

  • 建立一个Runnable接口的实现类
  • 实现Runnable中的抽象方法run()
  • 创建实现类对象
  • 将此对象作为参数传递到Thread类的构造器中,创建一个Thread类的对象
  • 通过Thread类的对象调用start()方法
1 //2.1 建立一个Runnable接口的实现类
 2 class MyThread2 implements Runnable{
 3     //2.2. 实现Runnable中的抽象方法run()
 4     @Override
 5     public void run() {
 6         for(int i=0;i<100;i++){
 7             if(i%2==0){
 8                 System.out.println(Thread.currentThread().getName() + ":" + i);
 9             }
10         }
11     }
12 }
13 
14 //2.3 2.4 创建实现类对象,将此对象作为参数传递到Thread类的构造器中,创建一个Thread类的对象
15 Thread t2 = new Thread(new MyThread2());
16 t2.setName("线程2");
17 
18 //2.5. 通过Thread类的对象调用start()方法
19 t2.start();

  开发中优先选择后者:既没有类的单继承性的限制,而且实现的方式更适合来处理多个线程有共享数据的情况。

  3. 实现Callable接口创建线程

  • 创建一个Callable接口的实现类
  • 实现call方法,将此线程需要执行的操作写在方法体中
  • 创建Callable接口实现类的对象
  • 将上面的实现类对象作为参数创建FutureTask的对象
  • 将上面的FutureTask的对象作为参数创建Thread的对象
  • 利用Thread的对象调用start方法
  • 通过 FutureTask的对象.get() 方法获取call方法的返回值
1 //3.1 创建一个Callable接口的实现类
 2 class NumThread implements Callable{
 3     @Override
 4     //3.2 实现call方法,将此线程需要执行的操作写在方法体中
 5     public Object call() throws Exception {
 6         int sum = 0;
 7         for(int i=1;i<=100;i++){
 8             if(i%2==0){
 9                 System.out.println(i);
10                 sum += i;
11             }
12         }
13         return sum;
14     }
15 }
16 
17 //3.3 创建Callable接口实现类的对象
18 NumThread num = new NumThread();
19 //3.4 将上面的实现类对象作为参数创建FutureTask的对象
20 FutureTask futureTask = new FutureTask(num);
21 //3.5 将上面的FutureTask的对象作为参数创建Thread的对象
22 Thread t = new Thread(futureTask);
23 t.setName("线程3");
24 //3.6 利用Thread的对象调用start方法
25 t.start();
26 
27 try {
28     //3.7 通过 FutureTask的对象.get() 方法获取call方法的返回值
29     Object sum = futureTask.get();//get方法的返回值即为NumThread.call的返回值
30     System.out.println("共计:" + sum);
31 } catch (InterruptedException e) {
32     e.printStackTrace();
33 } catch (ExecutionException e) {
34     e.printStackTrace();
35 }

  相较于Runnable接口更强大、可以抛出异常、支持泛型,但是需要借助FutureTask类

  4. 使用线程池创建线程

  • 创建指定线程数量的线程池
  • 执行指定的线程的操作,需要提供一个实现Runnable获Callable接口的对象
  • 关闭连接池
1 class MyThread2 implements Runnable{
 2     @Override
 3     public void run() {
 4         for(int i=0;i<100;i++){
 5             if(i%2==0){
 6                 System.out.println(Thread.currentThread().getName() + ":" + i);
 7             }
 8         }
 9     }
10 }
11 
12 //4.1 创建指定线程数量的线程池
13 ExecutorService service = Executors.newFixedThreadPool(10);//创建确定个数的线程池
14 // 4.2 执行指定的线程的操作,需要提供一个实现Runnable获Callable接口的对象
15 service.execute(new MyThread2());//参数只能是Runnable
16 service.execute(new MyThread3());//参数只能是Runnable
17 //4.3 关闭连接池
18 service.shutdown();

   5. Thread类中常见的方法

  • void start(): 启动线程,并执行对象的run()方法
  • run(): 线程在被调度时执行的操作
  • String getName(): 返回线程的名称
  • void setName(String name):设置该线程名称
  • static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
  • static void yield(): 线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法
  • join() : 当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止,使得低优先级的线程也可以获得执行
  • static void sleep(long millis):(指定时间:毫秒)令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。(抛出InterruptedException异常)
  • stop(): 强制线程生命期结束,不推荐使用
  • boolean isAlive(): 返回boolean,判断线程是否还活着
//给线程命名--方式一:通过setName()
t1.setName("线程1");                             //设置该线程名称

//给线程命名--方式二:通过构造器
ThreadMethodTest2 t2 = new ThreadMethodTest2("*线程2");

  6. 线程优先级

  • MAX_PRIORITY:10
  • MIN _PRIORITY:1
  • NORM_PRIORITY:5

  涉及的方法

  • getPriority() :返回线程优先值
  • setPriority(int newPriority) :改变线程的优先级

  说明

  • 线程创建时继承父线程的优先级
  • 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

二、线程的同步

  1. 同步代码块

synchronized (同步监视器){
    // 需要被同步的代码;
}

  同步监视器:俗称锁。任何一个类的对象都可以充当锁,要求多个线程必须共用同一个锁

  •  Runnable接口的方式的所可以使用this,因为它自始至终只用了一个对象
  • 继承Thread类的方式不能用this,可以使用 类.class 来充当

  2. 同步方法:synchronized放在方法声明中,表示整个方法为同步方法。同步方法仍然涉及同步监视器,但无需显示说明,例如:

public synchronized void show (String name){
    ....
}

  3. lock锁

//1. 实例化ReentrantLock
try{
    //2. 调用lock
    //需要被同步的代码
}finally{
    //3. 调用unlock
}

  4. 案例

java多线程教程那些事 java多线程入门_ide

java多线程教程那些事 java多线程入门_同步方法_02

1 /**
 2  * 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
 3  * 存在线程的安全问题,采用 同步代码块方式 解决
 4  * synchronized(同步监视器){
 5  *      //需要被同步的代码
 6  * }
 7  *      > 同步监视器:俗称锁。任何一个类的对象都可以充当锁,
 8  *                  要求多个线程必须共用同一个锁。
 9  *      > Runnable接口的方式的所可以使用this,因为它自始至终只用了一个对象
10  *      > 继承Thread类的方式不能用this,可以使用 类.class 来充当
11  * @author shkstart
12  * @create 2019-02-13 下午 4:47
13  */
14 class Window1 implements Runnable{
15 
16     private int ticket = 100;   //共享数据
17     private Object obj = new Object();
18 
19     @Override
20     public void run() {
21         while(true){
22             synchronized (obj){     //synchronized(this)
23                 if(ticket > 0){
24                     System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
25                     ticket--;
26                 }else{
27                     break;
28                 }
29             }
30         }
31     }
32 }
33 
34 
35 public class TicketSafeBR {
36     public static void main(String[] args) {
37         Window1 w = new Window1();
38 
39         Thread t1 = new Thread(w);
40         Thread t2 = new Thread(w);
41         Thread t3 = new Thread(w);
42 
43         t1.setName("窗口1");
44         t2.setName("窗口2");
45         t3.setName("窗口3");
46 
47         t1.start();
48         t2.start();
49         t3.start();
50     }
51 
52 }

Runnable-同步代码块方式

java多线程教程那些事 java多线程入门_ide

java多线程教程那些事 java多线程入门_同步方法_02

1 /**
 2  * 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
 3  * 存在线程的安全问题,采用 同步方法方式 解决
 4  *
 5  * > 同步方法仍然涉及同步监视器,但无需显示说明
 6  * > 非静态同步方法的同步监视器是:this
 7  * > 静态同步方法的同步监视器是:类.class
 8  *
 9  *
10  * @author shkstart
11  * @create 2019-02-13 下午 4:47
12  */
13 class Window2 implements Runnable{
14 
15     private int ticket = 100;
16 
17     @Override
18     public void run() {
19         while(true){
20             show();
21         }
22     }
23 
24     private synchronized void show(){   //同步监视器为this
25         if(ticket > 0){
26             try {
27                 Thread.sleep(100);
28             } catch (InterruptedException e) {
29                 e.printStackTrace();
30             }
31             System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
32             ticket--;
33         }
34     }
35 }
36 
37 
38 public class TicketSafeMR {
39     public static void main(String[] args) {
40         Window2 w = new Window2();
41 
42         Thread t1 = new Thread(w);
43         Thread t2 = new Thread(w);
44         Thread t3 = new Thread(w);
45 
46         t1.setName("窗口1");
47         t2.setName("窗口2");
48         t3.setName("窗口3");
49 
50         t1.start();
51         t2.start();
52         t3.start();
53     }
54 
55 }

Runnable-同步方法方式

java多线程教程那些事 java多线程入门_ide

java多线程教程那些事 java多线程入门_同步方法_02

1 /**
 2  *
 3  * 例子:创建三个窗口卖票,总票数为100张.使用继承Thread类的方式
 4  *
 5  * 存在线程的安全问题,采用 同步代码块方式 解决
 6  * synchronized(同步监视器){
 7  *      //需要被同步的代码
 8  * }
 9  *      > 同步监视器:俗称锁。任何一个类的对象都可以充当锁,
10  *                  要求多个线程必须共用同一个锁。
11  *      > Runnable接口的方式的所可以使用this,因为它自始至终只用了一个对象
12  *      > 继承Thread类的方式不能用this,可以使用 类.class 来充当(类.class只会加载一次)(反射)
13  * @author shkstart
14  * @create 2019-02-13 下午 4:20
15  */
16 class Window extends Thread{
17     private static int ticket = 100;
18     //要求多个线程必须共用同一个锁,因此使用了static,
19     private static Object obj = new Object();//一定要注意,这里与Runnable接口的方式有所不同
20     @Override
21     public void run() {
22 
23         while(true){
24             synchronized (obj){  //synchronized(Window.class)
25                 if(ticket > 0){
26                     try {
27                         Thread.sleep(100);
28                     } catch (InterruptedException e) {
29                         e.printStackTrace();
30                     }
31                     System.out.println(getName() + ":卖票,票号为:" + ticket);
32                     ticket--;
33                 }else{
34                     break;
35                 }
36             }
37         }
38     }
39 }
40 
41 
42 public class TicketSafeBT {
43     public static void main(String[] args) {
44         Window t1 = new Window();
45         Window t2 = new Window();
46         Window t3 = new Window();
47 
48 
49         t1.setName("窗口1");
50         t2.setName("窗口2");
51         t3.setName("窗口3");
52 
53         t1.start();
54         t2.start();
55         t3.start();
56 
57     }
58 }

Thread-同步代码块方式

java多线程教程那些事 java多线程入门_ide

java多线程教程那些事 java多线程入门_同步方法_02

1 /**
 2  *
 3  * 例子:创建三个窗口卖票,总票数为100张.使用继承Thread类的方式
 4  *
 5  * 存在线程的安全问题,使用 同步方法方式 来解决
 6  *
 7  * > 同步方法仍然涉及同步监视器,但无需显示说明
 8  * > 非静态同步方法的同步监视器是:this
 9  * > 静态同步方法的同步监视器是:类.class
10  *
11  *
12  * @author shkstart
13  * @create 2019-02-13 下午 4:20
14  */
15 class Window3 extends Thread{
16 
17 
18     private static int ticket = 100;
19     @Override
20     public void run() {
21         while(true){
22             show();
23         }
24     }
25 
26     private static synchronized void show(){   //同步监视器:Window3.class
27     //private static synchronized void show(){   //此种方法错误,同步监视器:t1、t2、t3(不唯一)
28         if(ticket > 0){
29             try {
30                 Thread.sleep(100);
31             } catch (InterruptedException e) {
32                 e.printStackTrace();
33             }
34             System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
35             ticket--;
36         }
37     }
38 }
39 
40 
41 public class TicketSafeMT {
42     public static void main(String[] args) {
43         Window3 t1 = new Window3();
44         Window3 t2 = new Window3();
45         Window3 t3 = new Window3();
46 
47 
48         t1.setName("窗口1");
49         t2.setName("窗口2");
50         t3.setName("窗口3");
51 
52         t1.start();
53         t2.start();
54         t3.start();
55 
56     }
57 }

Thread-同步方法方式

java多线程教程那些事 java多线程入门_ide

java多线程教程那些事 java多线程入门_同步方法_02

1 public class LockTest {
 2     public static void main(String[] args) {
 3         Window4 w = new Window4();
 4 
 5         Thread t1 = new Thread(w);
 6         Thread t2 = new Thread(w);
 7         Thread t3 = new Thread(w);
 8 
 9         t1.setName("窗口1");
10         t2.setName("窗口2");
11         t3.setName("窗口3");
12 
13         t1.start();
14         t2.start();
15         t3.start();
16 
17     }
18 
19 }
20 
21 
22 class Window4 implements Runnable{
23 
24     private int ticket = 100;
25 
26     //1. 实例化ReentrantLock
27     private ReentrantLock lock = new ReentrantLock();
28 
29     @Override
30     public void run() {
31         while (true){
32             try{
33                 //2. 调用lock
34                 lock.lock();
35                 if(ticket>0){
36                     try {
37                         Thread.sleep(100);
38                     } catch (InterruptedException e) {
39                         e.printStackTrace();
40                     }
41                     System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
42                     ticket--;
43                 }else
44                     break;
45             }finally {
46                 //3. 调用unlock
47                 lock.unlock();
48             }
49         }
50     }
51 }

Lock锁方式

 

三、线程的通信

  • wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
  • notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
  • notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
1 /**
 2  * 线程的通信:
 3  *      两个线程交替打印1-100:
 4  *          > wait()
 5  *          > notify()/notifyAll()
 6  *          > 注意:wait和notify的调用者必须是同一个对象
 7  *          > wait和notify必须使用在同步代码块或者同步方法中
 8  *
 9  * @author marc
10  * @creat 2020-02-07 上午9:22
11  */
12 public class MsgTest {
13     public static void main(String[] args) {
14         Number n1 = new Number();
15 
16         Thread t1 = new Thread(n1);
17         Thread t2 = new Thread(n1);
18 
19         t1.setName("线程1");
20         t2.setName("线程2");
21 
22         t1.start();
23         t2.start();
24 
25     }
26 }
27 
28 class Number implements Runnable{
29     private int i = 1;
30     @Override
31     public void run() {
32         while (true){
33 
34             synchronized (this) {
35 
36                 notify();         //this.notify()
37 
38                 if(i<=100){
39                     System.out.println(Thread.currentThread().getName() + ":" + i);
40                     i++;
41 
42                     try {
43                         wait();    //this.wait()
44                     } catch (InterruptedException e) {
45                         e.printStackTrace();
46                     }
47 
48                 }else
49                     break;
50             }
51 
52         }
53     }
54 }