最详细的java多线程教程来了

1. 线程概述

1.1 线程和进程

  • 进程是处于运行过程当中的程序,而且具备必定的独立功能java

  • 并发性:同一个时刻只能有一条指令执行,但多个进程指令被快速轮换执行程序员

  • 并行:多条指令在多个处理器上同时执行web

  • 线程是进程的执行单元面试

1.2 多线程的优点

  • 进程之间不能共享内存,但线程之间很是容易编程

  • 系统建立进程时须要为该进程从新分配系统资源,但建立线程则代价小得多,所以使用多线程效率更高安全

  • Java语言内置了多线程功能多线程

2. 线程建立与启动

2.1 继承Thread

 

 public class FirstThread extends Thread {
     private int i;
 ​
     @Override
     public void run() {
         for(i = 0; i < 50; i ++){
             System.out.println(this.getName() + "" + i);
         }
     }
 ​
     public static void main(String[] args){
         FirstThread ft = new FirstThread();
         for(int i =0; i < 100;i ++){
             System.out.println(Thread.currentThread().getName() + "" + i);
             if(i == 20) {
                 ft.run();
             }
         }
     }
 }

 

2.2 实现Runnable接口

 

 
public class FirstThread implements java.lang.Runnable {
     private int i;
 ​
     public void run() {
         for(i = 0; i < 50; i ++){
             System.out.println(Thread.currentThread().getName()+ "" + i);
         }
     }
 ​
     public static void main(String[] args){
         FirstThread ft = new FirstThread();
         for(int i =0; i < 100;i ++){
             System.out.println(Thread.currentThread().getName() + "" + i);
             if(i == 20) {
                 ft.run();
             }
         }
     }
 }

 

2.3 使用Callable和Future

  • Callable接口提供了一个call()方法能够做为线程执行体,call()方法有返回值且能够声明抛出异常并发

  • Java5提供了Future接口来表明Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类ide

  • Future接口定义的方法:学习

方法名 做用
boolean cancel(boolean mayInterruptIfRunning) 试图取消该Future里关联的Callable任务
V get() 返回Callable任务里call方法的返回值,该方法会形成线程阻塞,等子线程执行完才能得到
V get(long timeout, TimeUnit unit) 返回Callable任务里call方法的返回值。该方法让程序最多阻塞timeoutunit指定的时间,若是通过指定时间Callable任务尚未返回值则抛出TimeoutException异常
boolean isCancelled() Callable中的任务是否取消
boolean isDone() Callable中的任务是否完成

 

 public class CallableDemo {
     public static void main(String[] args){
         FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
             int i = 0;
             for( ; i < 100; i++){
                 System.out.println(i);
             }
             return i;
         });
         new Thread(task).start();
         try {
             System.out.println(task.get());
         } catch (InterruptedException e) {
             e.printStackTrace();
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
     }
 }

 

2.4 建立线程的三种方式对比

RunnableCallable优劣势:

  • 线程类只是实现了RunnableCallable接口,还能够继承其余类

  • Runnable和Callable状况下,多个线程能够共享同一个target对象,因此很是适合多个相同线程来处理同一份资源的状况

  • 编程稍稍复杂,若是须要访问当前线程,则必须使用Thread.currentThread()

Thread优劣势:

  • 线程类已经继承了Thread类,因此不能再继承其余父类

  • 编写简单,若是须要访问当前线程,用this使用

3. 线程生命周期

3.1 新建和就绪状态

  • new语句仅仅由Java虚拟机为其分配内存,并无表现出任何线程的动态特征

  • 若是直接调用继承类的run方法,则只会有MainActivity,并且不能经过getName得到当前执行线程的名字,而需用Thread.currentThread().getName()

  • 调用了run方法后,该线程已经再也不处于新建状态

3.2 运行和阻塞状态

  • 当线程数大于处理器数时,存在多个线程在同一个CPU上轮换的现象

  • 协做式调度策略:只有当一个线程调用了sleep()yield()方法才会放弃所占用的资源——即必须线程主动放弃所占用的资源

  • 抢占式调度策略:系统给每一个可执行的线程分配一个小的时间段来处理任务,当任务完成后,系统会剥夺该线程所占用的资源

  • 被阻塞的线程会在合适的时候从新进入就绪状态

img

线程状态转换图

3.3 死亡状态

  • 测试线程死亡可用isAlive()

  • 处于死亡的线程没法再次运行,不然引起IllegalThreadStateException异常

 

“大清亡于闭关锁国,学习技术须要交流和资料”。 在这里我给你们准备了不少的学习资料免费获取,包括但不限于java进阶学习资料、技术干货、大厂面试题系列、技术动向、职业生涯等一切有关程序员的分享.java进阶方法笔记,学习资料,面试题,电子书籍免费领取,让你成为java大神,追到本身的女神,走向人生巅峰

4. 控制线程

4.1 join线程

  • MainActivity调用了A.join(),则MainActivity被阻塞,A线程执行完后MainActivity才执行

4.2 后台线程(Daemon Thread)

  • 若是全部的前台线程都死亡,后台线程会自动死亡

 

 

img

运行结果

4.3 线程睡眠sleep

 

 
 try {
       Thread.sleep(200);
 } catch (InterruptedException e) {
        e.printStackTrace();
 }

 

  • sleep方法暂停当前线程后,会给其余线程执行机会,不会理会其余线程优先级;但yield方法只会给优先级相同或更高的线程

  • sleep方法将转入阻塞状态,直到通过阻塞时间才会转入就绪;yield强制当前线程转入就绪状态

  • sleep方法抛出了InterruptedException,yield方法没抛出异常

4.4 改变线程优先级

  • 优先级高的线程得到较多的执行机会,优先级低的线程得到较少的执行机会

  • setPrioritygetPriority方法来设置和返回指定线程的优先级

5. 线程同步

  • run()方法不具备同步安全性

  • *java*引入了同步监视器来解决多线程同步问题,sychronized(obj)obj就是共享资源

5.1 同步方法

  • 同步方法就是使用synchronized来修饰某个方法

  • 实例方法的同步监视器默认是this

  • *Java*中不可变类老是线程安全的,可变类对象须要额外的方法来保证其线程安全

 

 public class DaemonThread extends Thread {
 ​
     static int balance = 100;
     int drawAmount;
     String name;
 ​
     public DaemonThread(int drawAmount, String name){
         this.drawAmount = drawAmount;
         this.name = name;
     }
 ​
     @Override
     public void run() {
         this.draw(drawAmount);
     }
 ​
     public synchronized void draw(int amount){
         if(balance  >= amount){
             System.out.println(this.name + "取出了" + amount);
             try{
                 Thread.sleep(1);
             } catch (InterruptedException e){
                 e.printStackTrace();
             }
             balance -= amount;
             System.out.println("\t余额为" + balance);
 ​
         } else{
             System.out.println(this.name + "取现失败");
         }
     }
     public static void main(String[] args){
         new DaemonThread(50, "A").start();
         new DaemonThread(100, "B").start();
     }
 }

 

5.2 释放同步监视器的锁定

下列状况下,线程会释放对同步监视器的锁定

  • 当前线程的同步方法、同步代码块执行结束

  • 遇到了breakreturn

  • 遇到异常

  • 程序执行了同步监视器对象的wait()方法

下列状况下不会释放:

  • 执行同步方法时,程序调用Thread.sleep() Thread.yield()方法

  • 其余线程调用了该线程的suspend方法

5.3 同步锁

  • *Java5*开始,提供了一种功能更强大的同步锁机制,能够经过显式定义同步锁对象来实现同步

  • Lock提供了比synchronized更普遍的锁定操做,而且支持多个相关的Condition对象

  • Lock类型: Lock ReadWriteLock ReentrantLock:经常使用,能够对一个加锁的对象从新加锁 ReentrantReadWriteLock StampedLock

方法名 做用
lock 加锁
unlock 解锁

5.4 死锁

A等B,B等A

5.5 线程通讯

5.5.1 传统的线程通讯

方法名 做用
wait 致使当前线程等待,直到其余线程调用该同步监视器的notify()notifyAll()方法
notify 唤醒在此同步监视器等待的单个线程
notifyAll 唤醒在此同步监视器等待的全部线程
  • wait()必须在加锁的状况下执行

5.5.2 使用Condition

  • 若是系统中不适用synchronized来保证线程同步,而使用Lock对象来保证同步,那么没法使用waitnotifynotifyAll()来进行线程通讯

  • 当使用Lock对象,*Java*提供Condition保证线程协调

  • Condition方法以下

方法名 做用
await 致使当前线程等待,直到其余线程调用该同步监视器的signal()signalAll()方法
signal 唤醒在此Lock对象的单个线程
signalAll 唤醒在此Lock对象的全部线程

5.5.3 使用阻塞队列

  • *Java*提供了一个BlockingQueue接口

  • 当生产者线程试图向BlockingQueue放入元素时,若是该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue取出元素时,若是该队列已空,则该线程被阻塞

方法名 做用
put(E e) 尝试把E元素放入BlockingQueue
take() 尝试从BlockingQueue的头部取出元素

 

 
public class BlockingQueueThread extends Thread {
 ​
     private BlockingQueue<String> bq;
 ​
     public BlockingQueueThread(BlockingQueue<String> bq){
         this.bq = bq;
     }
 ​
     @Override
     public void run() {
         String[] strColl = new String[]{
                 "Java",
                 "Kotlin",
                 "JavaScript"
         };
 ​
 ​
 ​
         for(int i = 0; i < 1000; i ++){
             try {
                 System.out.println(getName() + "开始动工" + i);
                 bq.put(strColl[i % 3]);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
 ​
         System.out.println(getName() + "工做结束");
     }
 ​
     public static void main(String[] args){
         BlockingQueue<String> bq = new ArrayBlockingQueue<>(5);
         new BlockingQueueThread(bq).start();
     }
 }

 

img

结果展现

 

能够看到,当Thread-0运行到第6次时就已经被阻塞,不能往里添加内容

 

“大清亡于闭关锁国,学习技术须要交流和资料”。 在这里我给你们准备了不少的学习资料免费获取,包括但不限于java进阶学习资料、技术干货、大厂面试题系列、技术动向、职业生涯等一切有关程序员的分享.java进阶方法笔记,学习资料,面试题,电子书籍免费领取,让你成为java大神,追到本身的女神,走向人生巅峰

6. 线程组和未处理的异常

  • ThreadGroup表示线程组,能够对一批线程进行分类管理

  • 子线程和建立它的父线程在同一个线程组内

  • ThreadGroup方法

方法名 做用
int activeCount 返回线程组中活动线程的数目
interrupt 中断此线程组中全部活动线程的数目
isDaemon 线程组是不是后台线程组
setDaemon 设置后台线程
setMaxPriority 设置线程组的最高优先级

7. 线程池

  • 线程池在系统启动时即建立大量空闲的线程

  • 程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动一个空闲线程来执行他们

  • 线程结束不死亡,而是回到空闲状态

  • *Java8*以后新增了一个Executors工厂类来生产线程池

7.1 ThreadPool

 

 public class ThreadPoolTest {
 ​
     public static void main(String[] args){
         ExecutorService pool = Executors.newFixedThreadPool(2);
 ​
         java.lang.Runnable target = () -> {
            for (int i = 0; i < 100 ; i ++){
                System.out.println(Thread.currentThread().getName() + "的i为" +i);
            }
         };
 ​
         pool.submit(target);
         pool.submit(target);
         pool.shutdown();
     }
 }

 

img

结果展现

7.2 ForkJoinPool

  • 将一个任务拆分红多个小任务并行计算,再把多个小任务的结果合并成总的计算结果

  • ForkJoinPoolExecutorService的实现类

 

 
public class PrintTask extends RecursiveAction {
 ​
     public static int THREADSH_HOLD = 50;
 ​
     private int start;
 ​
     private int end;
 ​
     public PrintTask(int start, int end){
         this.start = start;
         this.end = end;
     }
 ​
     @Override
     protected void compute() {
         if(end - start < THREADSH_HOLD){
             for(int i = start; i < end; i ++){
                 System.out.println(Thread.currentThread().getName() + "的i为" + i);
             }
         } else {
             PrintTask left = new PrintTask(start, (start + end) / 2);
             PrintTask right = new PrintTask((start + end) / 2 , end);
             left.fork();
             right.fork();
         }
     }
 ​
     public static void main(String[] args) throws InterruptedException {
         PrintTask printTask = new PrintTask(0 , 300);
         ForkJoinPool pool = new ForkJoinPool();
         pool.submit(printTask);
         pool.awaitTermination(2, TimeUnit.SECONDS);
         pool.shutdown();
 ​
     }
 }

 

img

 

“大清亡于闭关锁国,学习技术须要交流和资料”。 在这里我给你们准备了不少的学习资料免费获取,包括但不限于java进阶学习资料、技术干货、大厂面试题系列、技术动向、职业生涯等一切有关程序员的分享.java进阶方法笔记,学习资料,面试题,电子书籍免费领取,让你成为java大神,追到本身的女神,走向人生巅峰

相关文章
相关标签/搜索