FKJAVA读书笔记--第十六章--多线程(修改)

这里写图片描述

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类

这个工具类可以方便的实现人们对多线程竞争资源的隔离
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值