java多线程笔记

    一 杂谈

     线程分为4个状态:

    1 新建(new) :当线程被创建时,它会短暂地处于这种状态。

    2 就绪 (Runnable) :这种状态下只要调度器把时间片分配给线程,线程就可以运行。

    3 阻塞 (Blocked):线程处于阻塞状态,调度器将忽略线程,不会分配给线程任何CPU时间,直到线程重新进入就绪状态,它才可能执行。

    4 死亡 (Dead):任务死亡的是从run()方法返回。

     要实现一个线程就要通过Thread类,可以直接继承这个类,实现run方法,也可以通过实现Runnable接口。在android的开发中有时候会直接通过内部类来运行线程,例如:

 new Thread (new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
      但有时应该注意线程run中的操作,例如例子中线程睡眠100秒,对于非静态内部类有一个对外部类的引用,假设这是一个在Activity中的内部线程类,也就是说这个线程在页面退出时并没有死亡,并且引用这一个Activity的实例,重新进入这个页面,又会重新实例化一个Activity多次之后就会产生多个Activity的实例,就会出现内存问题。解决方法可以用一个静态内部类,要是需要外部类可以用一个弱引用。例如:

 private static class MyThread<T> extends Thread {
         private WeakReference<T>  weakReference;
         public MyThread (T t) {
             weakReference = new WeakReference<T>(t);
         }
         @Override
         public void run() {
         }
     }
      除了Runnable外,javaSE5引入了了Callable它可以从call中返回值,当然调用的时候要用到线程池,返回的会产生一个Future对象,这个对象通过get方法可以得到该线程返回的值,这个类还可以用来中断特定的线程。

 private class MyCallabel implements Callable<String> {
        private String str;

        public MyCallabel(String str) {
            this.str = str;
        }

        @Override
        public String call() throws Exception {
            return str;
        }
    }

 ExecutorService exec = Executors.newCachedThreadPool();
        Future<String> future = exec.submit(new MyCallabel("Z_arze"));
   通过通过future.get()可以获取返回值,这是一个阻塞操作,直至结果准备就绪。

    线程的优先级通过setPriority来实现,在thread.start()之前要设置调度器会优先让优先级高的线程运行。

    后台线程是程序运行的时候在后台提供的一种服务,当所有非后台线程结束时会杀死所有的后台线程。通过thread.setDaemon()来设置,在后台线程开启的线程都会被默认地设置为后台线程。


     二 中断

     有时需要考虑结束一条线程,stop方法已经被抛弃,因为它不会释放获得的锁,可以用Thread类内的iterrupt()方法,调用这个方法将线程设置为中断状态,一但线程进入阻塞状态或者已经在阻塞状态,中断状态就会抛出异常,从而中断任务。这个方法要在线程中通过Thread.currentThread.iterrupt()才有效果,在外部持有thread对象直接调用没有效果。这个问题也在困扰我,可能是让大家不对持有的线程对象直接操作吧,要是有人知道私聊我。在这可以用线程组来中断,具体可以看下面代码:

  private Thread thread = new Thread(new Runnable() {
        int i = 0;

        public void run() {
            try {
                while (true) {
                    i++;
                    Log.w("ARZE", "run------->" + i);
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                Log.w("ARZE", "InterruptedException");
            }

        }
    });

     这个简单的线程可以知道要是不出意外打印的会从1递增的过程,在看下面:

      ExecutorService exce = Executors.newCachedThreadPool();
        exce.execute(thread);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        exce.shutdownNow();

   先看运行结果

   

    递增被中断了,线程组执行shutdownNow()方法后,会设置所有的线程为中断状态,中断状态中的线程一但进入阻塞操作例如sleep就会抛出异常,参照图中打印的一样,这里强调中断状态设置不会立马中断线程,需要线程进入阻塞或者已经在阻塞中。要注意的地方是线程组所有的线程都会被设置为中断状态,要是想中断特定的线程可以用:

  Future f = exce.submit(thread);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        f.cancel(true);
     通过泛型类Future来中断特定的线程。

     不过有一些特殊的情况通过上述方法是中断不了线程的,比如一些不可中断的阻塞操作例如一些i/o操作和synchronized块上等待,又或者在设置中断状态时,线程就入了个无限循环,循坏内没有阻塞操作,线程也是中断不了的。iterrupted()可以获取到线程的中断状态,用这个可以解决循环问题:

  private Thread thread = new Thread(new Runnable() {
        int i = 0;
        public void run() {

                while (!Thread.interrupted()) {
                    i++;
                    Log.w("ARZE", "run------->" + i);
                }
        }
    });
    对于不可中断的阻塞操作如何去中断,在新的IO类中,被阻塞的通道会自动响应中断。至于其它的也不是很了解,找机会去了解下。还有无论是中断状态的抛出异常还是interrupted都会让中断状态复位。

      三 同步

     在进行多线程编程的时候,还要注意的地方是共享资源的竞争,比如说一条线程准备获取一个对象的时候被挂起,另一条线程运行将这个对象改变或者销毁就会出现问题。可以采用在给定的时刻这有一个任务访问共享资源,这个可以通过锁语句来实现。

     synchronized关键字为防止资源冲突提供一个内置支持,当任务执行被synchronized保护的代码片段时,它将检查锁是否可用,然后获取锁,执行代码,释放锁。在同一个任务中可以多次获取同一个对象的锁,获取锁时计数会递增,等到完成释放锁时,计数会变为0。

     synchronized 的用法:

 private synchronized  void  text1 () {
        //
    }

private synchronized  static void  text () {
        //
    }
      上面两个例子都是在方法上加锁,不同的是第二个例子加的是静态方法,区别在于第一个例子的时候每一个对象实例调用者个方法锁时不互相影响的,而第二个例子的实例调用这个方法是有影响的,只要有一个实例调用了这个方法,其它实例调用时就要等待前一个实例释放锁,可以这样理解锁放在不是静态的方法上监视的是this,放在静态方法上监视的是class。当然锁还可以放在代码块中。例如:   

   private  void  text () {
        synchronized (class){
            //
        }
    }
     或者  

   private  void  text () {
        synchronized (new A){
            //
        }
    }
     还前面一样一个监视的是class,一个监视的是一个实例。

     当然还有一种比较灵活的加锁,Lock对象,只有在特殊的情况下才使用,比如尝试在一段时间上获得锁事件一到就会放弃并返回false,又或者尝试获得一个对象的锁,无论是否获得这个锁都会立即返回并且不会等待。

      Lock是一个接口:  

   public interface Lock {
        void lock();
        //获取锁,如果锁被别的线程获取就等待
        void lockInterruptibly() throws InterruptedException;
        //获取锁,在中断中有讲到不可中断阻塞,synchronized 的等待是不可中断的,
        //lock();tryLock();都是不可中断的,但这个方法可以被中断,即是中断状态能抛出异常。
        boolean tryLock();
        //  尝试获取锁,无论成不成功都返回,不会等待
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
        // 尝试获取锁,在设置的时间内等待,时间过后返回结果
        void unlock();
        //释放锁,为了防止死锁都会在finally{}语句中执行释放锁
    }
}
      Lock的用法

   private void textlock() {
        boolean isSucc;
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        try {
            //
        } finally {
            lock.unlock();
        }
    }

     一般推荐用synchronized,因为比较不容易犯错,Lock在特殊情况下才会用,不过Lock在竞争比较激烈的线程中性能要好过synchronized。


      四 通信

    多线程中还有就是线程中的通信,比如在多个线程完成的任务中一个线程需要知道另一个线程完结后才能执行,又或者线程的生产消费问题,一个线程负责生产东西,另一个线程负责在东西有库存的时候消费,这也需要知道线程的通信。 线程中的通信可分为wait,notify,notifyall,还有使用同步队列和利用管道通信。

     wait和notify,可以看一个例子:     

    private Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            while (!Thread.interrupted()) {
                synchronized (str) {
                    while (0 == count) {
                        try {
                            str.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    count--;
                    str.notifyAll();
                    Log.w("ARZE", "run----->" + "消费-1");

                }
            }
        }
    });

    private Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            while (!Thread.interrupted()) {
                synchronized (str) {
                    while (1 == count) {
                        try {
                            str.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    count++;
                    str.notifyAll();
                    Log.w("ARZE", "run----->" + "生产+1");
                }
            }
        }
    });
     

     第一个线程当count为0是就等待,不为0时就消费,第二个线程当count为1时就等待为0时就生产。锁对象执行了wait语句后,线程被挂起,释放锁,执行notify后等待这个锁对象的其中一条线程从wait状态中醒来,重新竞争获取锁,它没有任何的优势和劣势,也就是说是公平的。执行notifyall,等待这个锁对象的所有线程从wait状态醒来,重新竞争锁获取锁。

      利用同步队列,一个例子:一条线程将值为count加1后另一条线程将count -1,BlockingQueue接口提供的队列,线程可以从队列中获取对象,如果队列为空,这线程则阻塞,直到获取到可用的对象。

   private class Thread3 extends Thread {
        private BlockingQueue<Integer> blockingQueue;
        private int count = 0;
        public Thread3(BlockingQueue<Integer> blockingQueue) {
            this.blockingQueue = blockingQueue;
        }

        @Override
        public void run() {
              count++;
            try {
                sleep(5 * 1000);
                blockingQueue.put(count);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private class Thread4 extends Thread {
        private BlockingQueue<Integer> blockingQueue;
        public Thread4 (BlockingQueue<Integer> blockingQueue) {
              this.blockingQueue = blockingQueue;
        }

        @Override
        public void run() {
            try {
                int count = blockingQueue.take();
                Log.w("ARZE","run count ---->" + count);
                count--;
                Log.w("ARZE","run count ---->" + count);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

     BlockingQueue blockingQueue = new LinkedBlockingQueue();
        exce.execute(new Thread4(blockingQueue));
        exce.execute(new Thread3(blockingQueue));
 

     Thread4实例先加入线程组,并且Thread3线程中睡眠5秒证明在同步队列为空时,线程获取对象会出现阻塞,直到有可获取的对象。可方便地解决线程的通信问题。

       线程间还可以通过管道进行任务间的输入和输出。PiPedWriter类可让线程向管道写入,PiPedReader可让线程从管道读,当PiPedReader向管道读取数据时没有更多的数据,就会阻塞直到有数据读到,很像同步队列。  

  private class Thread5 extends Thread {
        private PipedWriter writer;

        public Thread5(PipedWriter writer) {
            this.writer = writer;
        }

        @Override
        public void run() {

            try {
                sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                writer.write("ARZE");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private class Thread6 extends Thread {
        private PipedReader reader;

        public Thread6(PipedWriter writer) {
            try {
                reader = new PipedReader(writer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            char[] buffer = new char[4];
            try {
              reader.read(buffer,0,4);
            } catch (IOException e) {
                e.printStackTrace();
            }
            Log.w("ARZE","run---->" + String.valueOf(buffer));
        }
    }
    PipedWriter writer = new PipedWriter();
        exce.execute(new Thread5(writer));
        exce.execute(new Thread6(writer));
         

    PipedReader需要与PipedWriter相关联,Thread6从管道读字符串,开始因为Thread5线程在睡眠,导致管道没有可读的数据,Thread6阻塞,直到Thread5从睡眠醒来并往管道写入字符串,Thread6读到字符串并打印。




   

    

      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值