有关并发的一切(almost)

最近重新复习了一边java 线程方面的基础知识,于是做了这篇归纳整理。

大纲

  1. java线程基础概念
  2. 两种创建线程的方式
  3. 线程的生命周期
  4. 线程死亡的几种情况
  5. 控制线程
  6. 线程同步:synchronized Lock
  7. 线程通信
  8. 线程池
  9. 线程安全的集合类

 

1,java线程基础概念

进程,正在运行的程序。是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
在同一个进程内又可以执行多个任务,而每一个任务我们就可以看成是一个线程。
多线程的存在,不是提高程序的执行速度。其实是为了提高应用程序的使用率。

2,两种创建线程的方式
Thread 类代表线程,所有线程对象都必须是Thread类或其子类的实例。 

new Thread() {
            @Override
            public void run() {
                super.run();
            }
        }.start();

new Thread(new Runnable() {
            @Override
            public void run() {
                
            }
        }).start();

3,线程的生命周期

线程启动后不一定就进入执行状态,和处于执行状态。经过,新建(New),就绪(Runnale),运行(Running),阻塞(blocked),死亡(Dead)5种状态。
调用start()方法后,线程就进入就绪状态。何时运行取决于JVM里线程调度器。当线程运行后,它不可能一直霸占CPU独立运行,于是线程状态会多次在运行,阻塞之间切换。

下面的情况也会进入阻塞状态
1,sleep();
2,调用了阻塞式IO方法,在该方法返回之前,该线程被阻塞
3,线程试图获得一个同步监视器,但该监视器正被其他线程持有。
4,线程在等待某个通知(notify)
5,suspend方法将该线程挂起,此方法易导致死锁,避免使用。
被阻塞后,其他线程就可以获得执行机会,被阻塞的线程会在合适的时候重新进入就绪状态。

4,线程死亡的几种情况

1,run()方法执行完成,线程正常结束
2,线程抛出一个未捕获异常Exception,Error
3,调用stop方法,该方法容易导致死锁,避免使用
判断时候死亡,调用isAlive()方法,就绪运动阻塞返回true,新建死亡返回false。
不要试图对一个已经死亡的线程调用start()方法使它重新启动,死亡就是死亡。

5,控制线程

  • 后台线程
  • join线程
  • sleep线程睡眠
  • yield线程让步
  • 线程优先级

后台线程

有一种线程被称为后台线程(Daemon Thread),又称守护线程。调用对象thread.setDaemon(true),指定为后台线程。该线程的特征是,所有前台线程都死亡,后台线程会自动死亡。

join线程
让一个线程等待另一个线程完成的方法,当某线程调用join()方法后,该线程将被阻塞,直到被join()方法加入的线程执行完成

public static void main(String[] args)throws Exception{

         JoinThread jt = new JoinThread("被Join的线程");
         jt.start();
         //main线程调用了jt线程的join()方法,main线程必须等jt执行结束才会向下执行
         jt.join();
         System.out.print(Thread.currentThread().getName());
     }

sleep线程睡眠
调用Thread静态方法sleep(),使线程暂停一段时间,并进入阻塞状态。

yield线程让步
与sleep()有些类似,Thread静态方法,可以让当前线程暂停,但不会阻塞,而是让其转入就绪状态,yield方法只是让当前线程暂停一些,让系统线程调度器重新调度一次,完全可能的是调用后调度器又将其调度出来执行。
实际上,当某线程调用yield方法暂停后,只有优先级与当前线程相同,或者更高的线程处于就绪状态才能获得执行机会。

线程优先级
每个线程都具有一定的优先级,优先级高的线程活得跟多执行机会,setPriority()参数可以是个整数,范围是1~10,也可以是Thread类下的三个静态常量
MIN_PRIORITY = 1;
NORM_PRIORITY = 5;
MAX_PRIORITY = 10;

 

6,线程同步:synchronized Lock

java的多线程支持引入了同步监视器,来解决线程安全的问题。

同步代码块
 synchronized (obj){

            //此处的代码就是同步代码块
        }

上面语法格式中synchronized后括号里的obj就是同步监视器,含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。
任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完后,该线程就会释放对该同步监视器的锁定。

//经典的银行存钱取钱的案例,下面为简化的取钱线程

    class DrawThread extends Thread {
        //模拟用户账户
        Account account;
        //当前取钱线程希望取钱数
        double drawAount;

        @Override
        public void run() {

            //使用account 作为同步监视器,任何线程进入下面同步代码块之前。必须先获得对account的锁定
            //这种做法符合: 加锁-修改-释放锁 的逻辑
            synchronized (account) {
                //账户余额大于取钱数目
                if (account.Balance >= drawAount) {
                    System.out.print("取钱成功");
                    //修改金额
                    account.Balance -= drawAount;
                    System.out.print("余额为" + account.Balance);
                } else {
                    System.out.print("余额不足");
                }
            }
        }
    }

同步方法
java线程安全还提供了同步方法,对于synchronized修饰的实例方法(非static),无须显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。
通过同步方法可以非常方便的实现线程安全类,每个线程调用该对象的同步方法后,都能得到安全的结果。
把上面的例子中的Account 对象中,把修改Balance 的方法变成同步方法即可。

class Account {
        //封装账户余额,不能直接访问
        private double Balance;

        //提供一个线程安全的改变Balance变量的取钱操作
        public synchronized void draw(double drawAount) {
            //账户余额大于取钱数目
            if (Balance >= drawAount) {
                System.out.print("取钱成功");
                //修改金额
                Balance -= drawAount;
                System.out.print("余额为" + Balance);
            } else {
                System.out.print("余额不足");
            }
        }
    }


使用account的draw方法就会获得this就是account的同步锁
synchronized 可以修饰代码块,修饰方法,但不能修饰构造器,成员变量等。

释放同步监视器的锁定有以下几种情况
1,当前线程的同步方法 同步代码块执行结束,释放同步监视器
2,当前线程的同步方法 同步代码中遇到 break,return终止了继续执行,释放同步监视器
3,当前线程的同步方法 同步代码出现未处理的Error Exception,释放同步监视器
4, 当前线程执行同步方法 同步代码时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。


wait() 与  notify/notifyAll() 的执行过程
由于 wait()与notify/notifyAll() 是放在同步代码中的,因此线程在执行它们时,该线程肯定是获得了锁的。
当线程执行wait()时,会把当前的锁释放,然后让出CPU,进入等待状态。
当执行notify/notifyAll方法时,会唤醒一个处于等待该 对象锁 的线程,然后继续往下执行然后继续往下执行,直到执行完退出synchronized修饰的代码块)后再释放锁。
从这里可以看出,notify/notifyAll()执行后,并不立即释放锁,而是要等到执行代码块后,再释放。
在实际编程中,我们应该尽量在线程调用notify/notifyAll()后,立即退出代码块。即不要在notify/notifyAll()后面再写一些耗时的代码。
  

   public class Service {

        public void testMethod(Object account) {
            try {
                synchronized (account) {
                    System.out.println("begin wait() ThreadName="
                            + Thread.currentThread().getName());

                    account.wait();

                    System.out.println("  end wait() ThreadName="
                            + Thread.currentThread().getName());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public void synNotifyMethod(Object account) {
            try {
                synchronized (account) {
                    System.out.println("begin notify() ThreadName="
                            + Thread.currentThread().getName() + " time="
                            + System.currentTimeMillis());
                    account.notify();
                    Thread.sleep(5000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

同步锁(Lock)
java提供了一个功能更强大的线程同步机制-通过显式定义同步锁对象来实现同步。这种机制下,同步锁有Lock对象来充当
Lock比synchronized方法和代码块有更广泛的锁定操作,更灵活的结构。

Lock是readwriteLock提供了两个根接口

1.可重入锁(常用) ReentranLock 
2.可中断锁  ReentranReadWriteLock ReentranLock
3.公平锁 ReentranReadWriteLock ReentranLock
4.读写锁  ReentranReadWriteLock


ReentranLock 锁具有可冲入性,也就是一个线程可以对已加锁的ReentranLock锁再次加锁,ReentranLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()枷锁后,必须显式调用unlock()来释放锁。

 class Account {
        //封装账户余额,不能直接访问
        private double Balance;

        //定义锁对象
        private final ReentrantLock lock = new ReentrantLock();

        //提供一个线程安全的改变Balance变量的取钱操作
        public synchronized void draw(double drawAount) {

            //加锁
            lock.lock();
            try {

                //账户余额大于取钱数目
                if (Balance >= drawAount) {
                    System.out.print("取钱成功");
                    //修改金额
                    Balance -= drawAount;
                    System.out.print("余额为" + Balance);
                } else {
                    System.out.print("余额不足");
                }

            } finally {
                //释放锁
                lock.unlock();
            }
        }
    }


死锁的情况
当两个线程相互等待对方释放同步监视器时就会发生死锁,多线程编程应该采取措施避免死锁出现。一旦出现程序不会有异常,也不会有提示,只是所有线程处于阻塞状态,无法继续。

Lock和synchronized的选择

总结来说,Lock和synchronized有以下几点不同:

  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

  5)Lock可以提高多个线程进行读操作的效率。
 

7,线程通信

  • synchronized同步 通过wait/notify机制,利用共享内存的方式通信,
  • 管道通信:管道流主要用来实现两个线程之间的二进制数据的传播。比如通过以PipedInputStream类和PipedOutputStream类为例,通过一边写入,一边读取来通信。
  • android 多线程提供了handler 通信

8,线程池

Executors工厂类来生成线程池。
newFixedThreadPool 该模式全部由核心线程去实现,并不会被回收,没有超时限制和任务队列的限制,会创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newCachedThreadPool 该模式下线程数量不定的线程池,只有非核心线程,最大值为Integer.MAX_VALUE,会创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newScheduledThreadPool  该模式下核心线程是固定的,非核心线程没有限制,非核心线程闲置时会被回收。会创建一个定长线程池,执行定时任务和固定周期的任务。
newSingleThreadExecutor   该模式下线程池内部只有一个线程,所有的任务都在一个线程中执行,会创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
newSingleThreadScheduledExecutor 创建只有一个线程池,它可以指定延迟后执行
newWorkStealingPool() 创建持有足够的线程的线程池来支持给定的并行级别,该方法使用多个队列来减少竞争
 

9,线程安全的集合类
Collections类中提供了多个synchronizedXxx,该方法返回指定集合对象对应的同步对象,从而可以解决多线程并发访问集合时的线程安全问题. 
线程安全的集合类
java.util.concurrent包下提供了大量支持高效并发访问集合,
如ConcurrentHashMap、CopyOnWriteArrayList 和CopyOnWriteArraySet

拓展
Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值