介绍: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. 案例:
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-同步代码块方式
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-同步方法方式
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-同步代码块方式
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-同步方法方式
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 }