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源创计划”,欢迎正在阅读的你也加入,一起分享。