介绍: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 }
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 }
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 }
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 }
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 }
三、线程的通信:
- 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 }