1. 线程概述
1.1 线程和进程
- 进程是处于运行过程中的程序,并且具有一定的独立功能 
- 并发性:同一个时刻只能有一条指令执行,但多个进程指令被快速轮换执行 
- 并行:多条指令在多个处理器上同时执行 
- 线程是进程的执行单元 
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实现类
- Future接口定义的方法:
方法名
作用
boolean cancel(boolean mayInterruptIfRunning)
试图取消该Future里关联的Callable任务
V get()
返回Callable任务里call方法的返回值,该方法会造成线程阻塞,等子线程执行完才能获得
V get(long timeout, TimeUnit unit)
返回Callable任务里call方法的返回值。该方法让程序最多阻塞timeout和unit指定的时间,如果经过指定时间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 创建线程的三种方式对比
Runnable和Callable优劣势:
- 线程类只是实现了 - Runnable、- Callable接口,还可以继承其他类
- 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()方法才会放弃所占用的资源——即必须线程主动放弃所占用的资源
- 抢占式调度策略:系统给每个可执行的线程分配一个小的时间段来处理任务,当任务完成后,系统会剥夺该线程所占用的资源 
- 被阻塞的线程会在合适的时候重新进入就绪状态 
线程状态转换图
3.3 死亡状态
- 测试线程死亡可用 - isAlive()
- 处于死亡的线程无法再次运行,否则引发 - IllegalThreadStateException异常
4. 控制线程
4.1 join线程
- 在MainActivity调用了A.join(),则MainActivity被阻塞,A线程执行完后MainActivity才执行
4.2 后台线程(Daemon Thread)
- 如果所有的前台线程都死亡,后台线程会自动死亡 - public class DaemonThread extends Thread { @Override public void run() { for(int i = 0; i< 1000; i++){ System.out.println("DaemonActivity" + i); } } public static void main(String[] args){ DaemonThread thread = new DaemonThread(); thread.setDaemon(true); thread.start(); for(int i = 0; i < 10; i ++ ){ System.out.println("MainActivity" + i); } }} 
运行结果
4.3 线程睡眠sleep
 try {      Thread.sleep(200);} catch (InterruptedException e) {       e.printStackTrace();}
- sleep方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程优先级;但- yield方法只会给优先级相同或更高的线程
- sleep方法将转入阻塞状态,直到经过阻塞时间才会转入就绪;- yield强制当前线程转入就绪状态
- sleep方法抛出了- InterruptedException,yield方法没抛出异常
4.4 改变线程优先级
- 优先级高的线程获得较多的执行机会,优先级低的线程获得较少的执行机会 
- setPriority和- getPriority方法来设置和返回指定线程的优先级
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 释放同步监视器的锁定
下列情况下,线程会释放对同步监视器的锁定
- 当前线程的同步方法、同步代码块执行结束 
- 遇到了break、return 
- 遇到异常 
- 程序执行了同步监视器对象的 - 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对象来保证同步,那么无法使用 - wait,- notify,- notifyAll()来进行线程通信
- 当使用 - 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();    }}
结果展示
可以看到,当Thread-0运行到第6次时就已经被阻塞,不能往里添加内容
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();  
}  
}`
结果展示
7.2 ForkJoinPool
- 将一个任务拆分成多个小任务并行计算,再把多个小任务的结果合并成总的计算结果 
- ForkJoinPool是- ExecutorService的实现类
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();  
}  
}`
本文分享自微信公众号 - java宝典(java_bible)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
 
  
  
  
 
 
  
 