Java之多线程

1.基本概念

1.1程序、进程、线程的区分:

程序(program)

  本质是一段静态的代码、静态对象。

进程(process)

  程序的一次执行过程,或正在运行的一个程序,是动态的过程。

  进程是资源分配的单位,系统在运行时会为每个分配不同的内存区域。

线程(thread)

  线程是调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC)。

  一个进程中的多个线程共享相同的内存单元/内存地址空间。

  一个进程中的线程可以访问相同的变量和对象。

注:Java的应用程序java.exe至少有三个线程:main主线程、gc垃圾回收线程、异常处理线程。

1.2并行与并发

  并行:多个CPU同时执行多个任务

  并发:一个CPU(分割时间片)同时执行多个任务

2.线程的创建与启动

2.1Thread类(java.lang.Thread)

构造器:

Thread():创建Thread对象

Thread(String threadname):创建进程并指定进程名称

Thread(Runable target):创建指定目标对象的进程,它实现了Runnable接口的run方法

Thrad(Runnable target,String name):创建指定对象的进程并指定名称

属性

static int MAX _PRIORITY  属性值:10  最大优先级

static int MIN _PRIORITY  属性值:1    最小优先级

static int NORM _PRIORITY  属性值:5    普通优先级

方法名

static Thread currentThread():返回当前线程

static void yield():线程让步,让步给优先级更高的线程

static void sleep(long mills):线程等待,但会抛出InterruptedException异常

void stop():已过时,强制停止当前线程

void run():需被子类重写的方法

void start():启动并调用run()方法

void join():在线程A中调用线程B的join(),则会阻塞线程A直到线程B执行完成

boolean isAlive():判断线程是否还在

boolean islnterruopted():判断线程是否被中断

int getPriority():返回线程优先级

viod setPriority(int priority):设置线程优先级

String getName():返回线程名称

void setName(String name):设置名称

 

2.3线程分类

  Java中的线程分为用户线程和守护线程。main线程就是用户线程,gc线程就是守护线程,

守护线程会依赖用户线程而存在,当用户线程执行完毕时,守护线程也会停止执行。

用户线程可以通过setDaemon(true)来设置为守护线程,当JVM中都是守护线程时,当前JVM将退出。

2.4创建线程的方式

  线程创建方式一共有4种,后两种在JDK5.0之后新增。

 

2.4.1继承Thread类(java.lang.Thread)

  定义子类继承Thread类

  子类中重写Thread类的run方法

  创建线程对象,调用对象的start方法来启动线程(执行run方法)

注:

  若手动调用run方法,则只是普通方法,而不是多线程模式

  实际中的方法由JVM调用

  一个线程对象只能调用一次start方法,若重复调用会抛出异常“Iegal Thread State Exception”

 

2.4.2实现Runnable接口(java.lang.runnable)

问:为什么要实现Runnable接口?

-Java不支持多继承;-不打算重写Thread类的其它方法。

  定义类实现Runnable接口

 1 package com.imooc.runnable;
 2 class PrintRunnable implements Runnable{
 3 /*
 4  * (non-Javadoc)
 5  * @see java.lang.Runnable#run()
 6  * 核心操作
 7  * 1.定义一类A去实现Runnable接口
 8  * 2.实现Runnable中的run方法
 9  * 3.Runnable无法直接用getName()方法
10  * 需要调用Thread的一个静态方法为Thread currentThread()表示当前线程,进而调用gatName()方法
11  * Thread.currentThread().getName();
12  * 4.在主方法,先去定义类A的对象B,类A不能直接调用start()方法,启动线程只能是Thread和它的子类
13  *所以先创建Thread对象C,用B作为参数 Thread t1 = new Thread(pr);
14  */
15     @Override
16     public void run() {
17         int i=1;
18         while(i <= 10)
19         System.out.println(Thread.currentThread().getName()+"正在运行!"+(i++));
20         
21     }
22     
23 }
24 public class Test {
25 
26     public static void main(String[] args) {
27          PrintRunnable pr = new PrintRunnable();
28          Thread t1 = new Thread(pr);
29          t1.start();
30          PrintRunnable pr1 = new PrintRunnable();
31          Thread t2 = new Thread(pr1);
32          t2.start();
33     }
34 
35 }

 

  实现类需要重写Runnable接口的run方法

  将实现类的对象作为参数传递给Thread类的构造器创建线程对象。

  调用线程对象的start方法(启动线程、调用当前线程的run方法)

2.4.3实现Callable接口(java.util.concurrent.Callable)

  Callable接口

     Callable接口相比Runnable接口,功能更加强大。实现Callable接口的类需要重写call()方法。

     call()方法支持泛型的返回值,可以抛出异常,同时可以借助Future Task来获取返回结果。

  Future接口(java.util.concurrent.Future)

    可以对具体的Runnable,Callable任务的执行结果进行取消、查询是否完成、获取结果等操作

    FutureTask类是Future接口的唯一实现类。

    FutureTask类是Future接口唯一实现类。

    FutureTask类同时实现了Runnable接口和Future接口。它可以作为Runnable被线程执行,也可以作为Future得到Callable.call()的返回值。

2.4.4使用线程池

  背景

         经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

  思路

        提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建

销毁、实现重复利用。类似生活中的公共交通工具。

  好处

    提高重复利用。类似生活中的公共交通工具。

    降低资源消耗(重复利用线程池中线程,不需要每次都创建)

    便于线程管理

 

ExecutorService:线程池接口,常用实现类ThreadPoolExecutor可用于设置线程池属性

方法

void exeute(Runnable command):执行任务或命令,一般用来执行Runnable

Future submit(callable task):执行任务,一般用来执行Callable

void shutdown():关闭线程池

 

Executors:工具类、线程池的工厂类,应用于创建并返回不同类型的线程

方法

static ExecutorService newCachedThreadPool():创建一个可根据需要新线程的线程池

static ExecutorService newFixedThreadPool(n):创建一个可重用固定线程数的线程池

static ExecutorService newSingleThreadExecutor():创建一个只有一个线程的线程池

static ScheduledExecutorService newScheduledThreadPool(n):创建一个线程池,可安排在给定延迟后运行或定期执行

2.4.5各线程创建方式的比较

继承Thread类VS实现Runnadle接口

  在开发中更推荐使用实现Runnable接口的方式。这样可以避免类单继承性的限制,同时更适合处理多线程间的数据共享

  Thread类其实也是实现了Runnable接口的

 

 

3.线程的生命周期

  JDK中用Thread.State类定义线程的几种状态

新建

就绪

运行

阻塞

死亡

4.线程同步

4.1出现原因

多线程执行时用于共享数据时,会造成操作的不完整而破坏数据,实例见TestThreadBug..java。

 1 class TestThreaBug {
 2     public static void main(String [] args) {
 3           Ticket ticket = new Ticket();
 4 
 5         //3个线程同时售票
 6         Thread t1 = new Thead(ticket,"t1窗口“);
 7         Thread t2 = new Thead(ticket,"t2窗口”);
 8         Thread t3 = new Thead(ticket,"t3窗口");
 9   
10         t1.start();
11         t2.start();
12         t3.start();
13     }
14 }
15 
16 class Ticket implements Ruunable {
17     private int tick = 20;
18 
19      @Override
20     public void run() {
21         while(true) {
22             if(true > 0) {
23                 System.out.println(Thred.currentThread().getName() + "售出车票,剩余车票”
24     }else
25             break;
26         }
27       }
28 }

4.2解决方法:同步机制(synchronized)

4.2.1同步代码块

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

synchronized(同步监视器){
    //需要被同步的代码,即操作共享数据的代码
}

同步监视器,也成为锁。可以是任何类的对象,但多个线程必须使用相同的锁。

 

线程间通信产生的问题

wait()方法:中断方法的执行,使线程等待
notify()方法:唤醒处于等待的某一个线程,使其结束等待
notifyAll()方法:唤醒所有处于等待的线程,使它们结束等待

 1 package com.imooc.queue;
 2 
 3 public class Queue {
 4     private int n;
 5     boolean flag = false;
 6     public synchronized int get() {
 7         if(!flag) {
 8             try {
 9                 wait();
10             } catch (InterruptedException e) {
11                 // TODO Auto-generated catch block
12                 e.printStackTrace();
13             }
14         }
15         System.out.println("消费:"+n);
16         flag = false;//消费完毕,容器中没有数据。
17         notifyAll();
18         return n;
19     }
20 
21     public synchronized void set(int n) {
22         if(flag) {
23             try {
24                 wait();
25             } catch (InterruptedException e) {
26                 // TODO Auto-generated catch block
27                 e.printStackTrace();
28             }
29         }
30         System.out.println("生产:"+n);
31         this.n = n;
32         flag = true;//生产完毕,容器中没有数据。
33         notifyAll();
34     }
35     
36     
37 }
 1 package com.imooc.queue;
 2 
 3 public class Producer implements Runnable {
 4     Queue queue;
 5     Producer(Queue queue){
 6         this.queue = queue;
 7     }
 8     @Override
 9     public void run() {
10         int i = 0;
11         while(true) {
12             queue.set(i++);
13             try {
14                 Thread.sleep(1000);
15             } catch (InterruptedException e) {
16                  e.printStackTrace();
17             }
18         }
19     }
20 
21 }
 1 package com.imooc.queue;
 2 
 3 public class Consumer implements Runnable {
 4     Queue queue;
 5     Consumer(Queue queue){
 6         this.queue = queue;
 7     } 
 8     
 9     @Override
10     public void run() {
11         while (true) {
12             queue.get();
13             try {
14                 Thread.sleep(1000);
15             } catch (InterruptedException e) {
16                 // TODO Auto-generated catch block
17                 e.printStackTrace();
18             }
19         }
20     }
21     
22 
23 }
 1 package com.imooc.queue;
 2 
 3 public class Test {
 4 
 5     public static void main(String[] args) {
 6         Queue queue = new Queue();
 7         new Thread(new Producer(queue)).start();
 8         new Thread(new Consumer(queue)).start();
 9 
10     }
11 
12 }

 

1、notifyAll方法是唤醒所有线程,本线程不可能把自己唤醒,如果能执行到唤醒这一步就证明自己实在运行的状态。
2、notify()方法是随机唤醒一个线程,notifyAll()方法是唤醒所有线程,因为线程的随机性,一般用notifyAll()
3、此案例可以理解为必须生产者生产,然后消费者才能消费
可以通过一个中间布尔值变量来进行控制,
当flag=true时,说明生产者已经生产完毕,无需进行生产者方法,执行wait()方法,使线程等待,
当flag=false时,说明消费者已经消费完毕,无需进行消费者方法,执行wait()方法,使线程等待,
注:在执行消费者方法时,消费者消费完毕,此时消费者线程正处于运行状态,生产者线程处于等待阻塞状态,需要执行notifyAll方法来将生产者的线程唤醒,反之亦然。     
上一篇:C++ Primier Plus(第六版) 第十二章 类和动态内存分配 编程练习答案


下一篇:LeetCode347 前k个高频元素