1 线程概述
1.1 线程和进程
进程三大特性:独立性、动态性、并发性;
线程是进程的执行单元,就像进程是系统的执行单元一样;
线程可以有自己的堆栈,自己的程序计数器,但是它不独立拥有系统资源;
多个线程共享系统资源,因此在线程操作系统资源的时候必须要小心,是不是会对其他线程构成影响。
线程的执行时抢占式的,一个线程可以创建和销毁另一个线程,同一个进程中的多个线程时并发执行的。
1.2 多线程的优势
当系统要创建一个新的进程的时候要为该进程独立分配内存空间,和大量相关资源,成本很高;
但是当一个进程中创建一个新的线程的时候,就不需要这样做。因此利用多线程实现并发并多进程要性能优越;
2 线程的创建和启动
每个线程的作用就是完成一定的任务,这个任务就是执行一段程序流;
2.1 继承Thread类创建线程
1:定义Thread子类,重写run()方法:线程执行体
2:创建Thread子类实例
3:调用start()方法开启线程
2.2 实现Runnabl接口创建线程类
两个子线程的i将是连续的,那是因为多个线程共享了一个target,所以多个线程可以共享同一个线程类(实际上时target类)的实例属性
public static void main(String[] args) {
System.out.println("Thread Name In Main ==>"+Thread.currentThread().getName());
MyRunnable runnable=new MyRunnable();
new Thread(runnable, "线程一").start();
new Thread(runnable, "线程二").start();
}
static class MyRunnable implements Runnable{
private int i;
@Override
public void run() {
for (; i <100; i++) {
System.out.println("Thread Name==>"+Thread.currentThread().getName()+"==>"+i);
}
}
}
1:实现Runnable接口,实现里面的run()方法,线程执行体
2:创建Runnable实现类的实例,并以此作为Thread的target创建Thread对象,该Thread对象才是真正的线程对象
2.3 使用Callable和Future创建线程
public class CallableTest {
public static void main(String[] args) {
MyCallable callable=new MyCallable();
FutureTask<Integer> task=new FutureTask<>(callable);
for(int i=0;i<100;i++){
System.out.println("Thread Name In Main==>"+Thread.currentThread().getName());
if(i==20){
new Thread(task, "有返回值的线程").start();
}
}
try {
//将导致主线程被阻塞
System.out.println("child Thread return ==>"+task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 100; i++) {
System.out.println("Thread Name==>"
+ Thread.currentThread().getName() + "==>" + i);
}
return i;
}
}
}
1:创建Callable接口实现类
实现其call()方法,call方法将会作为线程执行体,并且该call方法有返回值
2:创建Callable实现类实例,使用FutureTask类来包装Callable对象
FutrueTask对象封装了该Callable对象call方法的返回值
3:使用FutureTask对象作为Thread的target创建线程
4:调用FutureTask的get方法获取子线程执行结束之后的返回值
2.4 注意
1:java的main方法就是主线程的执行体
2:通过set,get方法可以设置Thread的Name
3:使用继承Thread的方法创建线程,多个线程之间无法共享线程类的实例变量
3 线程的生命周期
3.1 新建和就绪状态
new==》新建
start==》就绪
3.2 运行和阻塞状态
3.3 线程死亡
1:run或者call方法执行结束,正常结束
2:抛出一个未捕获的Exception或者Error
3 :直接调用线程的stop方法(或造成死锁,不推荐)
3.4 注意
1.启动线程时start方法,千万不能调用run方法,否则系统会把线程对象当做一个普通的对象
2.调用主线程的sleep(1)方法,可以让创建的子线程立即运行
3.不要试图对已经死亡的线程调用start()
4.可以调用isAlive获取线程是否活着:
就绪,运行,阻塞返回ture;
新建,死亡返回false;
4 控制线程
4.1 join线程
当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join方法加入的线程执行完毕为止。
join()
join(long millis):在millis时间内,如果join的线程没有执行结束,则不再等待
join(long millis,int nanos)很少用
4.2 后台线程
通过setDaemon(true)方法 将线程设置为后台线程
特征:如果所有的前台线程都死亡,后台线程会自动死亡。
注意:并不是所有的线程默认都是创建前台线程。
4.3 线程睡眠Sleep
4.4 线程让步yield
调用了yield方法之后,只有优先级比当前线程高或者相同的线程才会获得执行的机会。
建议不要使用yield方法来控制并发线程的执行。
4.5 改变线程的优先级
级别:
MAX_PRIORITY:10
MIN_PRIORITY:0
NORM_PRIORITY:5
5 线程同步
5.1 线程安全问题
public class DrawThread extends Thread {
private Account account;
private double drawAmount;
public DrawThread(String name, Account account, double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
@Override
public void run() {
// synchronized (account) {
if (account.getBalance() >= drawAmount) {
System.out.println(getName() + "取钱成功:" + drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setBalance(account.getBalance() - drawAmount);
System.out.println("余额:" + account.getBalance());
} else {
System.out.println(getName() + "余额不足!");
}
// }
}
}
5.2 同步代码块
任何时刻只能有一个线程可以获得对同步监听器的锁定,当同步代码块执行完毕之后,该线程会释放对该同步监听器的锁定。
加锁-》修改-》释放锁(设计逻辑)
5.3 同步方法
public synchronized void updataAccount(double drawAmount){
if (this.getBalance() >= drawAmount) {
System.out.println(Thread.currentThread().getName() + "取钱成功:" + drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(this.getBalance() - drawAmount);
System.out.println("余额:" + this.getBalance());
} else {
System.out.println(Thread.currentThread().getName() + "余额不足!");
}
}
同步方法的监听器:同步方法的监听器为this。
5.4 释放同步监听器的锁定
会释放同步监听器:
1.当前线程同步代码块,同步方法执行结束,当前线程即释放同步监听器;
2.当前线程在同步代码块,同步方法中遇到了break;return终止了代码块、方法。那么当前线程会释放同步监听器;
3.遇到了Error或者Exception,导致异常结束;
4.程序执行了同步监听器的wait()方法,当前线程暂停,并释放同步监听器;
不会释放同步监听器:
1.程序调用sleep,yield方法来暂停当前线程,不会释放;
2.其他线程调用了该线程的suspend方法将该线程挂起,不会释放;
5.5 同步锁
相比于synchronied关键字实现同步方法,Lock更加的显示,更加清晰;
Lock、ReentrantLock
public class BankAccount
{
private ReentrantLock lock = new ReentrantLock();
private double account = 1000;
private String name;
public BankAccount(String name)
{
this.name = name;
}
public double getAccount()
{
return account;
}
public void setAccount(double account)
{
this.account = account;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
// 模拟取钱
public void newLockTest(double money, String getMan)
{
lock.lock();
try
{
if(account > money)
{
Thread.sleep(1);
// 可以取钱
account = account - money;
System.out.println(getMan + " get money=>" + money + "**new account==>" + account);
}
else
{
System.out.println(getMan + " can't get money!");
}
}
catch(Exception e)
{
e.printStackTrace();
}
//使用finnally确保释放
finally
{
lock.unlock();
}
}
}
可以实现重复加锁,但要按对应锁显式的释放
ReadWriteLock、ReentrantReadWriteLock
5.6 死锁
当两个线程互相等待对方释放同步监听器的时候就会发生死锁;
死锁不会发生异常,不会报错,只是所有的线程处于阻塞状态,无法继续;
由于执行Thread类的suspend()方法很容易导致死锁,所以不推荐使用此方法来暂停线程;
5.7 注意
synchronized关键字可以修饰方法,可以修饰代码块,但是不能修饰Field和构造方法
5.8 StringBuilder和StringBuffer
StringBuilder在单线程情况下使用可以提供很好的性能;
StringBuffer在多线程情况下使用可以提供线程安全;
6 线程通信
6.1 传统线程通信
wait():导致当前线程等待。
notify():唤醒在此同步监听器上等待的单个线程;选择是任意的。只有当前线程放弃对该同步监听器的锁定后(调用了wait方法或者方法已经执行完毕),才能执行被唤醒的线程;
notifyAll():
6.2 使用Condition控制线程通信
如果用了Lock显式的充当同步监听器,就需要使用Condition来进行对象暂停、唤醒指定的线程;
创建:Lock对象的newCondition方法可以获取
方法:
await():类似于隐式同步监听器上的wait()方法;
signal()
signalAll()
6.3 使用阻塞队列(BlockingQueue)控制线程通信
特征
当生产者线程视图往BlockingQueue中放入元素的时候,如果队列已经满了,则该线程阻塞;
当消费者线程试图往BlockingQueue中取出原色的时候,如果队列已经空了,则该线程阻塞;
方法
Put(E e)
take()
实现类
ArrayBlockingQueue
LinkedBlockingQueue
PriorityBlockingQueue
SynchronousQueue
7 线程组和未处理异常
7.1 定义
如果创建线程时没有显式的指定线程属于哪个线程组,那么子线程和父线程将会是在同一个线程组里面;
一旦某个线程加入了指定的线程组之后,该线程将一直属于该线程组,知道线程死亡,中途不能改变线程组;
7.2 Thread几个指定线程组的方法
Thread(ThreadGroup group ,Runnable target)
Thread(ThreadGroup group ,Runnable target,String name)
Thread(ThreadGroup group ,String name)
7.3 ThreadGroup
ThreadGroup的构造方法
ThreadGroup(String name)
ThreadGroup(ThreadGroup parent,String name)
常用方法:
int activeCount():返回此线程组中活动线程的数目
interrupt():中断线程组里面所有线程
isDaemon():判断线程组是不是后台线程组
setDaemon(boolean daemon):将线程组设置为后台线程组:
特征:当后台线程组最后一个线程执行结束或者被销毁,后台线程组将会销毁;
setMaxPriority(int pr):设置线程组的最高优先级
Thread.UncaughtExceptionHandler
public class MyUncatchHandler implements Thread.UncaughtExceptionHandler
{
@Override
public void uncaughtException(Thread t, Throwable e)
{
System.out.println(t.getName() + "发生了异常:" + e);
}
public static void main(String[] args)
{
// 虽然捕捉了异常,但是程序不会正常退出
// Thread.currentThread().setUncaughtExceptionHandler(new
// MyUncatchHandler());
// try catch 模块可以使程序正常的退出;
try
{
int i = 5 / 0;
}
catch(Exception e)
{
e.printStackTrace();
}
System.out.println("程序正常结束!");
}
}
8 线程池
利用线程池可以有效控制系统并发的线程数,防止JVM因为线程数过高而崩溃
8.1 JAVA5实现线程池
Executors:
newCachedThreadPool():创建一个具有缓存功能的线程池,这些线程将会被缓存在线程池中
newFixedThreadPool(int nThreads):创建一个可以重用,有固定线程数的线程池
newSingleThreadExecutor()创建一个只有一个线程的线程池,相当于调用了newFixedThreadPool(1);
newScheduledThreadPool(int corePoolSize)创建具有制定线程数的线程池,它可以制定延迟后执行线程任务,即使线程空闲,也会被保存在线程池内;
newSingleScheduledThreadPool()创建只有一个线程的线程池,它可以制定在特定的延迟之后再执行线程任务
ExecutorService
Future <?> submit(Runnable task)
T Future<T> submit(Runnable task ,T result)
T Future<T> submit(Callable<T> task)
ScheduleExecutorService
ScheduleFuture<V> schedule(Callable<V> callable,long delay,TimeUnit unit)
ScheduleFuture<?> schedule(Runnable command ,long delay ,TimeUnit unit)
ScheduleFuture<?> scheduleAtFixedRate(Runnable command,long initalDelay,long period ,TimeUnit unit)
ScheduleFuture<?> scheduleWithFixedRate(Runnable command,long initalDelay,long period ,TimeUnit unit)
如何使用线程池
利用Executors生成ExecutorService对象
创建Runnable或者Callable对象作为线程执行任务
调用ExecutorService的submit方法提交Runnable或者Callable对象
不想提交任务的时候,调用ExecutorService对象的shutdown()方法关闭线程池
8.2 Java7新增的ForkJoinPool
为多核CPU准备
创建
ForkJoinPool(int parallelism)
ForkJoinPool( )
执行方法
submit(ForkJoinTask task)
invoke(ForkJoinTask task)
ForkJoinTask
RecursiveTask:有返回值
RecursiveAction:无返回值
9 线程相关类
9.1 ThreadLocal类
这个工具类可以方便的实现人们对多线程竞争资源的隔离