黑马程序员——JAVA基础——线程---概述,创建、生命周期,控制,同步,线程通信

时间:2022-12-03 10:59:25

-----------android培训java培训、java学习型技术博客、期待与您交流!------------

第一讲.概述

  1. 操作系统可以同时处理多个进程,而进程可以并发处理多个线程。
  2. 并发指的是CPU在多个线程之间高速切换。要明白它根并行(多核CPU并行处理)的区别。
  3. 线程是进程内部的并发模块,线程可以共享数据,进程间不共享数据。
  4. 正因为线程间可以共享所在进程的资源,所以才需要进行线程间的同步与通信。
第二讲.创建线程及生命周期
  1. 继承Thread类。覆写父类public void run()方法
    class MyThread extends Thread 
    {
    public void run(){
    for (int i=0;i<100 ;i++ )
    {
    //this.getName()得到当前线程名
    System.out.println(currentThread().getName()+"...."+i);
    }
    }
    public static void main(String[] args){
    new MyThread().start();//需要start()以开启线程
    //new MyThread().run();//仅仅是调用run()方法,并没有创建线程。
    }
    }
  2. 实现Runnable接口,实现run方法。利用Thread.currentThread().getName()得到当前线程名。
    //简单买票程序,多个窗口同时卖票class Ticket implements Runnable  {private int tick=100;public void run(){while(true){if(tick>0)System.out.println(Thread.currentThread().getName()+"..."+tick--);}}public static void main(String[] args){Ticket ticket = new Ticket();new Thread(ticket).start();new Thread(ticket).start();new Thread(ticket).start();}}
  3. 因为Java只允许继承一个父类,而可以同时实现多个接口。所以Runnable接口比Thread类相对灵活。共享数据的话,继承Thread类方法需要static成员变量,而static声明周期长,这点也没有runnable接口好。
  4. 从创建开始线程就进入了它的生命历程,如下图。可以看到线程的生命周期共四个主要状态:就绪,运行,阻塞(冻结),死亡。
黑马程序员——JAVA基础——线程---概述,创建、生命周期,控制,同步,线程通信
第三讲.线程控制
  1. 停止线程——interrupt()方法,停止线程的其他方法stop和挂起suspend()已经遭到反对了。JDK上的说法是:"stop有固有的不安全性,suspend()有固有的死锁倾向"。"stop 的许多使用都应由只修改某些变量以指示目标线程应该停止运行的代码来取代。目标线程应定期检查该变量,并且如果该变量指示它要停止运行,则从其运行方法依次返回。如果目标线程等待很长时间(例如基于一个条件变量),则应使用interrupt 方法来中断该等待"。interrupt()方法会强制清除线程的中断(冻结)状态,如果线程在调用Object.wait()方法,或者Thread.sleep(),Thead.join()方法过程中受阻,可以用interrupt令其恢复到运行状态,上述方法所抛的InterruptedException异常就是特指这种时刻。用法:在处理InterruptedException异常的catch块中,执行终止该线程的语句。  
  2. 线程等待——join()方法,在线程A中调用另一个线程B的join方法,A会等到B死亡后再继续执行。
  3. 后台线程——Daemon Thread,又被称作守护线程,JVM的GC就是典型的后台线程。当所有前台线程死亡是,后台线程自动死亡。可以调用Thread对象的setDaemon(true)方法将线程设置为后台线程。
  4. 线程让步——yield()方法,某线程调用yield方法后会主动失去执行权,转换到就绪状态。如果想交替执行两个线程的话会用到。
  5. 改变线程优先级——setPriority(ine priority),优先级从1~10的正数,越大优先级越高。也可以使用Thread的三个静态常量:MAX_PRIORITY:10;MIN_PRIORITY:1;NORM_PRIORITY:5。高优先级的线程会得到更多的执行机会。
第四讲.线程同步
  1. 当多个线程操作同一个资源的时候,需要同步,Java提供了同步语句块来解决此问题。上面的买票小程序加入synchronized后如下:利用Object对象作为同步锁,只有持同一个锁的线程间才会同步。
    Object obj = new Object();
    public void run(){
    while(true)
    {
    synchronized(obj)
    {
    if(tick>0)
    {
    //让异步问题更容易发生
    try{Thread.sleep(100);}catch(InterruptedException e){}
    System.out.println(Thread.currentThread().getName()+"..."+tick--);
    }
    }
    }
    }
  2. 同步函数:在函数名前加synchronized关键字修饰即可。锁为this,静态同步函数的锁为所在类的Class对象。
    //有一个银行,两个储户,每个人存300,分三次,每次存100class Bank{int sum;public synchronized void add(int n){sum +=n;}}class Cus implements Runnable{Bank bank=new Bank();public void run(){for (int i=0;i<3 i++; ){add(100);try{Thread.sleep(100);}catch(Exception e){}System.out.println("sum="+sum);}}}class BankDemo{public static void main(String[] args){Cus cus = new Cus();new Thread(cus).start();new Thread(cus).start();}}
  3. 懒汉式单例设计模式的同步写法:(有考题:请写出一个延迟加载的单例模式示例)
    //懒汉式,只有唯一实例,且只能由类中方法得到。class Single{private static Single s = null;private Single(){}public static Single getSingle(){if(s==null)//提高效率,若已经初始化了,就不必再去判断锁了{synchronized(Single.class){if(s==null)s = new Single();}}return s;}}
  4. 死锁:指两个线程彼此持有对方的锁,而相互等待对方释放锁的情况。下面程序通过嵌套同步块模拟了一个死锁。
    class DeadLockTest implements Runnable{private boolean flag;DeadLockTest(boolean flag){this.flag = flag;}public void run(){while(true){if(flag)synchronized(MyLock.locka){synchronized(MyLock.lockb){}}elsesynchronized(MyLock.lockb){synchronized(MyLock.locka){}}}}public static void main(String[] args){new Thread(new DeadLockTest(true)).start();new Thread(new DeadLockTest(false)).start();}}class MyLock{static Object locka = new Object();static Object lockb = new Object();}

第五讲.线程通信

  1. 提线程通信,最典型的就是生产消费或者读写问题了,当运行两段代码的线程想同时访问(读写)共享数据是,就涉及到了线程通信。例如:读写同一段内存的时候,写线程写完了应该通知读线程去读,反之亦然。读写通过通信保持协调。
  2. 传统的线程通信:Object类的wait()、notify()、notifyAll(),用法见下面的生产消费(没有缓冲区,生产一个马上消费一个)例子。
    class Res
    {
    private int count =1;
    private boolean flag = false;
    public synchronized void set(){
    while(flag)//循环判断,让线程每次运行时都检查flag
    try{wait();}catch(Exception e){}
    System.out.println(Thread.currentThread().getName()+"...生产商品"+count++);
    flag = true;
    notifyAll();//唤醒线程池中的全部线程,确保有输出线程被唤醒。
    }
    public synchronized void out(){
    while(!flag)
    try{wait();}catch(Exception e){}
    System.out.println(Thread.currentThread().getName()+"...消费商品"+count);
    flag = false;
    notifyAll();
    }
    public static void main(String[] args)
    {
    Res r=new Res();
    new Thread(new Input(r)).start();
    new Thread(new Output(r)).start();
    }
    }
    class Input implements Runnable
    {
    private Res r;
    Input(Res r){
    this.r=r;
    }
    public void run(){
    while(true)
    r.set();
    }
    }
    class Output implements Runnable
    {
    private Res r;
    Output(Res r){this.r=r;}
    public void run(){
    while(true)
    r.out();
    }
    }
  3. JDK5.0之后出现的新线程通信方法:Lock、Condition ,JDK里面对一个缓冲区进行读写的例子:可以发现,通过notFull.await()方法等待的线程只能通过对应的notFull.signal方法唤醒,相当于读写的线程分别处于不同的线程集中,不会出现前面的“由于无法区分唤醒的是读线程还是写线程,只能用notifyAll唤醒全部”情况。
     class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition();

    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    public void put(Object x) throws InterruptedException {
    lock.lock();
    try {

    while (count == items.length)
    notFull.await();
    items[putptr] = x;
    if (++putptr == items.length) putptr = 0;
    ++count;
    notEmpty.signal();
    } finally {
    lock.unlock();
    }

    }

    public Object take() throws InterruptedException {
    lock.lock();
    try {

    while (count == 0)
    notEmpty.await();
    Object x = items[takeptr];
    if (++takeptr == items.length) takeptr = 0;
    --count;
    notFull.signal();
    return x;
    } finally {
    lock.unlock();
    }

    }
    }