跳转至

多线程

一、线程的创建和使用

1. 方式一:继承Thread类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class ThreadTest {
    public static void main(String[] args) {
        // 3. 创建Thread类子类的对象
        MyThread t = new MyThread();
        // 4. 通过此对象调用start(): ①启动当前线程;②调用当前线程的run()
        t.start();
        // 其他代码仍然在main线程中执行
    }
}

// 1. 创建继承于Thread类的子类
class MyThread extends Thread {
    // 2. 重写Thread类的run方法
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }
}

2. 方式二:实现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
package com.example.www;

public class ThreadTest {
    public static void main(String[] args) {
        // 3. 创建实现类的对象
        MyThread myThread = new MyThread();
        // 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t = new Thread(myThread);
        // 5. 通过Thread类的对象调用start()
        t.start();
    }
}

// 1. 创建一个实现了Runnable接口的类
class MyThread implements Runnable {
    // 2. 实现类去实现Runnable中的抽象方法run()
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }
}

开发中优先使用这种方式

  1. 实现的方法没有类的单继承性的局限性
  2. 实现的方法更适合来处理有共享数据的情况

3. 方式三:实现Callable接口

  1. 可以有返回值
  2. 方法可以跑出异常
  3. 支持泛型的返回值
  4. 需要借助FutureTask类,必须获取返回结果。
    1. 可以对具体RunnableCallable任务的执行结果进行取消、查询是否完成、获取结果等。
    2. FutureTask是Future接口的唯一的实现类
    3. FutureTask同时实现了RunnableFuture接口。既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
 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
/*
Callable比Runnable更强大
1. call()可以有返回值
2. call()可以跑出异常,被外面的操作捕获,获取异常的信息
3. Callable是支持泛型的
*/
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadTest {
    public static void main(String[] args) {

        // 3. 创建Callable接口实现类的对象
        MyThread t = new MyThread();
        // 4. 将此Callable接口实现类的对象传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>(t);
        // 5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
            // get() 返回值是FutureTask构造器参数Callable实现类重写的call()的返回值
            // 6. 获取Callable中call方法的返回值
            Integer sum =  futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

// 1 创建一个实现Callable的实现类
class MyThread implements Callable<Integer> {

    // 2. 实现call()方法,将此线程需要执行的操作声明在call()方法中
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i < 100; i++) {
            if (i % 2 == 0){
                sum += i;
            }
        }
        return sum; // 可以有返回值
    }
}

4. 方式四:使用线程池

 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
/*
* 线程池的好处:
* 1. 提高响应速度
* 2. 降低资源消耗
* 3. 便于线程管理
*/
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service = (ThreadPoolExecutor) executorService;
        // 设置线程池属性
        service.setCorePoolSize(15);
        //        service.setKeepAliveTime();

        //        service.execute(); // 适用于Runnable
        //        service.submit(); // 适用于Callable
        service.execute(new MyThread1());
        service.shutdown();
    }
}


class MyThread1 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }
}

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
package com.example.www;

public class ThreadMethodTest {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.setName("Thread-zero"); // 自定义线程名
        t.start();
        // 判断当前进程是否存活
        System.out.println(t.isAlive()); // true
        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
            if (i == 20) {
                try {
                    // 当前线程进入阻塞状态,直到线程t完全执行完以后
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                // currentThread() 静态方法,返回执行当前代码的线程
                // getName() 获取当前线程名
                System.out.println(Thread.currentThread().getName() + ":" + i); // Thread-zero 默认是:Thread-0
            }
//            if (i % 20 == 0) {
//                yield(); // 释放当前CPU的执行权
//            }
        }
    }
}

6. 线程优先级

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// MIN_PRIORITY = 1
// NORM_PRIORITY = 5
// MAX_PRIORITY = 10
public class ThreadMethodTest {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        // 设置线程的优先级
        t.setPriority(7);
        t.start();
        // 获取线程的优先级
        System.out.println(t.getPriority()); // 7
    }
}

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

二、线程的生命周期

三、线程同步

买票过程中出现了重票和错票 --> 出现了线程安全问题

在Java中通过同步机制来解决线程的安全问题

操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。

1. 方法一:同步代码块

1.1 同步代码块处理实现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
/*
synchronized(同步监视器){ // 同步监视器:俗称锁,任何一个类的对象都可以充当锁
     需要被同步的代码 // 操作共享数据的代码
}
*/
public class TicketWindowTest {
    public static void main(String[] args) {
        Window window = new Window();
        Thread t1 = new Thread(window);
        Thread t2 = new Thread(window);
        Thread t3 = new Thread(window);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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


class Window implements Runnable {
    private int ticket = 100;
    // new 一个对象充当锁
    // Object obj = new Object(); // 多个线程必须要公用一把锁


    public void run() {
        while (true) {
            // synchronized(obj) {
            synchronized(this) { // 用当前对象充当锁,此时的this是唯一的Window对象
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

1.2 同步代码块处理继承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
28
29
30
31
32
33
34
35
36
37
38
public class TicketWindowTest {
    public static void main(String[] args) {
        Window t1 = new Window();
        Window t2 = new Window();
        Window t3 = new Window();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

class Window extends Thread {
    private static int ticket = 100;
//    private static Object obj = new Object();
    public void run() {
        while (true){
//            synchronized (obj) {
            synchronized (Window.class) { // Window类只会加载一次,所以可以使用当前类充当锁
                if (ticket > 0) {
                    try {
                        Thread.sleep(100); // 添加延时,增加出错概率
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + ":买票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

2. 方法一:同步方法

  1. 同步方法仍然涉及到同步监视器,只是不需要显示声明。
  2. 非静态的同步方法,同步监视器是this
  3. 静态的同步方法,同步监视器是当前类本身。

2.1 同步代码块处理实现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
package com.example.www;

public class TicketWindowTest {
    public static void main(String[] args) {
        Window window = new Window();
        Thread t1 = new Thread(window);
        Thread t2 = new Thread(window);
        Thread t3 = new Thread(window);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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


class Window implements Runnable {
    private int ticket = 100;

    public void run() {
        while (ticket > 0) {
            ticket();
        }
    }

    public synchronized void ticket() {  // 同步方法
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
            ticket--;
        }
    }
}

2.2 同步代码块处理实现继承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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.example.www;

/*
使用同步方法处理继承Thread的线程安全问题
*/
public class TicketWindowTest {
    public static void main(String[] args) {
        Window t1 = new Window();
        Window t2 = new Window();
        Window t3 = new Window();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

class Window extends Thread {
    private static int ticket = 100;

    public void run() {
        while (ticket > 0) {
            ticket();
        }
    }

    public static synchronized void ticket() { // 需要包装此方法时静态的, 此时的同步监视器是当前类
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
            ticket--;
        }
    }
}

四、死锁

1. 死锁

 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
/*
模拟线程死锁
使用同步时要避免死锁
*/

public class LockTest {
    public static void main(String[] args) {
        StringBuffer str1 = new StringBuffer();
        StringBuffer str2 = new StringBuffer();

        new Thread(){
            @Override
            public void run() {
                synchronized (str1){
                    str1.append("A");
                    str2.append("1");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (str2){
                        str1.append("B");
                        str2.append("2");
                        System.out.println(str1);
                        System.out.println(str2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (str2){
                    str1.append("C");
                    str2.append("3");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (str1){
                        str1.append("D");
                        str2.append("4");
                        System.out.println(str1);
                        System.out.println(str2);
                    }
                }
            }
        }).start();
    }
}

2. Lock锁方法解决线程安全问题

 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
package com.example.www;

import java.util.concurrent.locks.ReentrantLock;

public class TicketWindowTest {
    public static void main(String[] args) {
        Window window = new Window();
        Thread t1 = new Thread(window);
        Thread t2 = new Thread(window);
        Thread t3 = new Thread(window);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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


class Window implements Runnable {
    private int ticket = 100;
    // 1. 创建锁
    private ReentrantLock lock = new ReentrantLock();

    public void run() {
        while (true) {
            try {
                // 2. 调用锁定方法
                lock.lock();
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {
                // 3. 释放锁
                lock.unlock();
            }
        }
    }
}

五、线程通信

 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
// 线程通信示例
// 两个线程交替打印1-100
/*
涉及到的方法
wait(): 当前线程进入阻塞状态,并释放同步监视器
notify(): 唤醒一个被wait()的线程
notifyAll(): 唤醒所有被wait()的线程
注意:
1. 只能在同步代码块或同步方法中使用
2. 调用者必须是同步代码块或同步方法中的同步监视器
3. 这三个方法定义在Object中
*/
public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);
        t1.start();
        t2.start();
    }
}


class Number implements Runnable {
    private int number = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                notify();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (number <= 100){
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;
                    try {
                        wait(); // 使得调用如下wait()方法的线程进入阻塞状态
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    break;
                }
            }

        }
    }
}

sleep()wait()的异同

相同点:

  1. 都可以使当前线程进入阻塞状态

不同的:

  1. 两个方法声明的位置不同:Thread类中声明sleep(),Object中声明wait()
  2. 调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块或同步方法中。
  3. 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

六、生产者消费者

 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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package com.example.www;

/*
 * 线程通信的应用
 * 多线程:生产者 消费者
 * 数据共享
 * 线程安全问题处理
 * 线程通信
 */
public class ProductorCustomerTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Productor p1 = new Productor(clerk);
        p1.setName("生产者1");
        Customer c1 = new Customer(clerk);
        c1.setName("消费者1");
        p1.start();
        c1.start();
    }
}

class Clerk {

    private int produceCount = 0;

    public synchronized void ProduceProduct() {
        if (produceCount < 20) {
            produceCount++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + produceCount + "个产品");
            notify();
        } else {
            // 等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void CustomeProduct() {
        if (produceCount > 0) {
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + produceCount + "个产品");
            produceCount--;
            notify();
        } else {
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Productor extends Thread {
    private Clerk clerk;

    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ":开始生产产品...");

        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.ProduceProduct();
        }
    }
}

class Customer extends Thread {
    private Clerk clerk;

    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }

    public void run() {
        System.out.println(getName() + ":开始生产产品...");

        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.CustomeProduct();
        }
    }
}