Android葵花宝典Android面试相关Android开发

带你通俗易懂的理解——线程、多线程与线程池

2017-10-05  本文已影响156人  wildma

进程与线程

关于进程与线程的讲解,这篇文章讲的挺好的-->进程与线程的一个简单解释

创建线程的三种方式

一、继承Thread

1、定义一个类MyThread继承Thread,并重写run方法。
2、将要执行的代码写在run方法中。
3、创建该类的实例,并调用start()方法开启线程。
代码如下:

public class MainActivity extends AppCompatActivity {

    private final String TAG = this.getClass().getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //3、创建该类的实例,并调用start()方法开启线程。
        MyThread myThread = new MyThread();
        myThread.start();
    }

    //1、定义一个类MyThread继承Thread,并重写run方法。
    class MyThread extends Thread {
        public void run() {
            //2、将执行的代码写在run方法中。
            for (int i = 0; i < 100; i++) {
                Log.d(TAG, "线程名字:" + Thread.currentThread().getName()  + "  i=" + i);
            }
        }
    }
}
二、实现Runnable接口

1、定义一个类MyRunnable实现Runnable接口,并重写run方法。
2、将要执行的代码写在run方法中。
3、创建Thread对象, 传入MyRunnable的实例,并调用start()方法开启线程。
代码如下:

public class MainActivity extends AppCompatActivity {

    private final String TAG = this.getClass().getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //3、创建Thread对象, 传入MyRunnable的实例,并调用start()方法开启线程。
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }


    //1、定义一个类MyRunnable实现Runnable接口,并重写run方法。
    class MyRunnable implements Runnable {
        public void run() {
            //2、将执行的代码写在run方法中。
            for (int i = 0; i < 100; i++) {
                Log.d(TAG, "线程名字:" + Thread.currentThread().getName() + "  i=" + i);
            }
        }
    }
}
三、实现Callable接口

Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。

1、自定义一个类MyCallable实现Callable接口,并重写call()方法
2、将要执行的代码写在call()方法中
3、创建线程池对象,调用submit()方法执行MyCallable任务,并返回Future对象
4、调用Future对象的get()方法获取call()方法执行完后的值
代码如下:

public class MainActivity extends AppCompatActivity {

    private final String TAG = this.getClass().getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //3、创建线程池对象,调用submit()方法执行MyCallable任务,并返回Future对象
        ExecutorService pool = Executors.newSingleThreadExecutor();
        Future<Integer> f1 = pool.submit(new MyCallable());

        //4、调用Future对象的get()方法获取call()方法执行完后的值
        try {
            Log.d(TAG, "sum=" + f1.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //关闭线程池
        pool.shutdown();
    }


    //1、自定义一个类MyCallable实现Callable接口,并重写call()方法
    public class MyCallable implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            //2、将要执行的代码写在call()方法中
            int sum = 0;
            for (int i = 0; i <= 100; i++) {
                sum += i;
            }
            return sum;
        }

    }
}

创建线程的三种方式对比

一、继承Thread类与实现Runnable接口的区别

我们都知道java支持单继承,多实现。实现Runnable接口还可以继承其他类,而使用继承Thread就不能继承其他类了。所以当你想创建一个线程又希望继承其他类的时候就该选择实现Runnable接口的方式。

二、实现Callable接口与Runnable接口的区别

Callable执行的方法是call() ,而Runnable执行的方法是run()。
call() 方法有返回值还能抛出异常, run() 方法则没有没有返回值,也不能抛出异常。

多线程

一、概念

一个进程中开启了不止一个线程。

二、多线程的优缺点
三、多线程并行和并发的区别
public class MainActivity extends AppCompatActivity {

    private final String TAG = this.getClass().getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //开启2条线程上传图片
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable, "线程1").start();
        new Thread(myRunnable, "线程2").start();

    }


    public class MyRunnable implements Runnable {
        private int imgNum = 9;//图片数量

        @Override
        public void run() {
            while (true) {
                if (imgNum == 0) {
                    break;
                }
                try {
                    Thread.sleep(1000);//模拟上传一张图片需要1秒钟的时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.d(TAG, Thread.currentThread().getName() + "正在上传图片...,还剩" + imgNum-- + "张图片未上传");
            }
        }
    }
}

打印结果如下:


由图可知,图片数量出现了负数,显然是错误的。

原因:
出现这种错误的原因是有多个线程在操作共享的数据。即一个线程在操作共享数据的过程中CPU切换到其他线程又对该数据进行操作,这就是所谓的多线程并发。

解决:
把操作数据的那段代码用synchronized进行同步, 这样就能保证在同一时刻只能有一个线程能够访问。
代码如下:

public class MainActivity extends AppCompatActivity {

    private final String TAG = this.getClass().getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //开启2条线程上传图片
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable, "线程1").start();
        new Thread(myRunnable, "线程2").start();

    }


    public class MyRunnable implements Runnable {
        private int imgNum = 9;//图片数量

        @Override
        public void run() {
            while (true) {
                synchronized (MyRunnable.class) { //加上synchronized进行同步,保证在同一时刻只能有一个线程能够访问。
                    if (imgNum == 0) {
                        break;
                    }
                    try {
                        Thread.sleep(1000);//模拟上传一张图片需要1秒钟的时间
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.d(TAG, Thread.currentThread().getName() + "正在上传图片...,还剩" + imgNum-- + "张图片未上传");
                }
            }
        }
    }
}

打印结果如下:

线程池

关于线程池

前面举的朋友圈发表图片的多线程例子中,为了提高CPU的使用率和程序的工作效率就需要创建9个线程来上传图片。但是线程的创建和销毁是非常耗CPU和内存的,因为它涉及到要与操作系统进行交互。这样就可能导致创建与销毁线程的开销比实际业务还大,而线程池就能很好的解决这些问题。线程池里的每一个线程结束后,并不会销毁(可以设置超时销毁),而是回到线程池中成为空闲状态,等待下一个对象来使用。

使用线程池的优点

1、减少创建与销毁线程带来的性能开销。
2、可控制最大并发线程数,避免过多资源竞争而导致系统内存消耗完。
3、能更好的控制线程的开启与回收,并且能定时执行任务。

线程池中重要的几个类
public interface Executor {
    void execute(Runnable command);
}
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

点击newCachedThreadPool()进去,里面确实调用了ThreadPoolExecutor的构造方法,如下:
【Executor.java】

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
ThreadPoolExecutor构造函数参数说明

下面从源码中拿一个参数最完整的来讲解,如下:
【ThreadPoolExecutor.java】

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
    }
四种线程池
        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            final int finalI = i;
            newFixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        Log.d(TAG, "线程名字:" + Thread.currentThread().getName() + "  i=" + finalI);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

打印结果:


由打印结果可知,10个任务始终在3个线程中执行。

        ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            final int finalI = i;
            newSingleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        Log.d(TAG, "线程名字:" + Thread.currentThread().getName() + "  i=" + finalI);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }

打印结果:



由打印结果可知,10个任务始终在3个线程中执行。

        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int finalI = i;
            newCachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG, "线程名字:" + Thread.currentThread().getName() + "  i=" + finalI);
                }
            });
        }

由打印结果可知,线程1出现了很多次,说明有重用之前创建的线程。

        ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(3);
        /**
         * 延迟2秒执行任务
         */
        newScheduledThreadPool.schedule(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "线程名字:" + Thread.currentThread().getName() + "定时任务");
            }
        }, 2, TimeUnit.SECONDS);

        /**
         * 延迟1秒后每2秒执行一次任务
         */
        newScheduledThreadPool.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "线程名字:" + Thread.currentThread().getName() + "周期性任务");
            }
        }, 1, 2, TimeUnit.SECONDS);

由打印结果可知,定时任务是2秒后执行任务,周期性任务是延迟1秒后每2秒执行一次任务。

上一篇下一篇

猜你喜欢

热点阅读