知识点AndroidJava-多线程

(1)线程系列 - 线程、多线程

2021-05-23  本文已影响0人  zhongjh

我们经常用的okhttp和rxjava等,都是基于线程进行封装,我们从java最基础上了解线程对于以后是有帮助的,那么直接进入主题

相关概念

在说线程之前,我们先了解一下进程。

什么是进程
我们平日里打开的微信、简书App,都是一个进程。
什么是线程
线程是比进程更小的执行单位。一个程序只可以有一个进程,但这个进程可以包含多个线程
什么是多线程
这些线程可以同时存在,同时运行,一个进程可能包含多个同时执行的线程
并发和并行
并发:并发是指一个处理器同时处理多个任务
并行:并行是指多个处理器或者是多核的处理器同时处理多个不同的任务
打比方:并发是一个人同时吃三个包子,而并行是三个人同时吃三个包子
什么是线程池
创建并销毁线程的过程势必会消耗内存,如果创建多个线程对于Java来说是不合适的,Java的内存资源是极其宝贵的,所有就有了这个线程池重复利用线程

1. 线程

自定义Thread
/**
 * 自定义线程类
 * @author zhongjh
 * @date 2021/5/7
 */
public class MyThread extends Thread {

    private static final int COUNT = 10;

    /**
     * 线程名称
     */
    private final String threadName;

    public MyThread(String name) {
        this.threadName = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < COUNT; i++) {
            System.out.println(threadName + ": " + i);
        }
    }

}
                // 并行执行多个线程
                MyThread myThread1 = new MyThread("线程1");
                MyThread myThread2 = new MyThread("线程2");
                myThread1.start();
                myThread2.start();

打印日志


image.png
实现Runnable接口
/**
 * @author zhongjh
 * @date 2021/5/7
 */
public class MyThreadImpl implements Runnable {

    private static final int COUNT = 10;

    /**
     * 线程名称
     */
    private final String threadName;

    public MyThreadImpl(String name) {
        this.threadName = name;
    }

    @Override
    public void run() {
        for (int i = 0; i < COUNT; i++) {
            System.out.println(threadName + ": " + i);
        }
    }

}
                // 并行执行多个线程
                MyThreadImpl myThreadImpl1 = new MyThreadImpl("线程1");
                MyThreadImpl myThreadImpl2 = new MyThreadImpl("线程2");
                Thread myThreadOne = new Thread(myThreadImpl1);
                Thread myThreadTwo = new Thread(myThreadImpl2);
                myThreadOne.start();
                myThreadTwo.start();

打印日志的内容跟继承Thread是一样的,Thread的源码也是实现Runnable接口,两者区别就是接口跟继承的区别,在很多场景中接口比继承灵活多了,当然,这是接口继承的另一篇文章了。

线程流程

创建
new Thread()创建线程后,此时已经有了相应的内存空间和其他资源。
准备
调用线程的start()方法后,线程将进入线程队列排队,等待 CPU 服务,此时的线程已经具备了运行条件。
运行
当就绪状态被调用并获得处理器资源时,线程就进入了运行状态。此时该线程自动它的 run() 方法。
阻塞
线程在运行过程中,如果人为调用sleep(),suspend(),wait() 等方法或者别的因素,线程将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
运行
线程调用 stop() 方法时或 run() 方法执行结束后,即处于死亡状态。处于死亡状态的线程将不会有继续运行的能力。

定义线程名称

Thread.currentThread().getName(); // 取得当前线程的名称
也可以通过new Thread(Runnable, "线程1");这种方式自定义赋值名称

join

join方法的功能就是使异步执行的线程变成同步执行。
平常的调用线程实例的start方法后,这个方法会立即返回,如果后面的代码想得到这个线程返回的值才能计算,那么就必须使用join方法。

public class MyThreadJoin extends Thread {

    int m = (int) (Math.random() * 10000);

    @Override
    public void run() {
        try {
            System.out.println("我在子线程中会随机睡上0-9秒,时间为="+m);
            Thread.sleep(m);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
    /**
     * join方法示例
     */
    private void testJoin() {
        MyThreadJoin myThread =new MyThreadJoin();
        myThread.start();
        try {
            myThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("正常情况下肯定是我先执行完,但是加入join后,main主线程会等待子线程执行完毕后才执行");
    }

打印日志,可以看到6秒后才显示


image.png
sleep

线程常用方法之一,sleep(xx毫秒),线程的休眠。顾名思义,暂停线程xx毫秒之后继续执行,直接看示例代码

public class MyThreadSleep extends Thread {
    private static final int COUNT = 3;

    @Override
    public void run() {
        for (int i = 0; i < COUNT; i++) {
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}
    /**
     * Sleep示例
     */
    private void testSleep() {
        MyThreadSleep myThread1 = new MyThreadSleep();
        myThread1.start();
    }

打印日志,可以看到相隔1秒才显示一句日志


image.png

sleep还有一种写法,Thread.sleep(long millis),这是针对所有线程的睡眠

yield

yield()会礼让给相同优先级的或者是优先级更高的线程执行,yield()这个方法只是把线程的状态打回准备状态,他会继续跑起来,可以看到代码例子有个停住1秒的,可以尝试把1秒暂停看看打印出来的文字

    /**
     * Yield示例
     */
    private void testYield() {
        MyThreadYield myThread1 = new MyThreadYield("线程一");
        MyThreadYield myThread2 = new MyThreadYield("线程二");
        myThread1.start();
        myThread2.start();
    }

/**
 * 礼让线程
 * @author zhongjh
 * @date 2021/5/14
 */
public class MyThreadYield extends Thread {

    public MyThreadYield(String name) {
        super(name);
    }

    @Override
    public synchronized void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + "在运行,i的值为:" + i + " 优先级为:" + getPriority());
            if (i == 2) {
                System.out.println(getName() + "礼让");
                Thread.yield();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

打印文字


image.png
synchronized

在更深入的讲解线程其他机制前,我们先讲另一个关键字,synchronized。
这是一个同步关键字,不管多少个线程调用该关键字修饰的方法,都是一个一个的按照顺序执行完。假设我们多个线程(人)买火车票

    private int ticket = 10;

    /**
     * Synchronized购买火车票的示例
     */
    private void testSynchronized() {
        for (int i = 0; i < 10; i++) {
            new Thread() {
                @Override
                public void run() {
                    // 买票
                    sellTicket();
                }
            }.start();
        }
    }

    /**
     * 减少票,同步synchronized
     */
    public synchronized void sellTicket() {
        ticket--;
        System.out.println("剩余的票数:" + ticket);
        if (ticket == 0) {
            // 重新填充票数用于测试
            ticket = 10;
        }
    }

打印日志,可以看到票数顺序减少,如果去掉synchronized,可以发现乱序的


image.png
synchronized 对象锁和类锁

不同对象之间的对象锁是互不影响的,而类锁只有一个。但是同时对象锁和类锁又不互不影响,接着会通过代码分别加深锁的印象
首先创建一个实体类,包含了对象锁和类锁的方法

public class SynchronizedEntity {

    private int ticket = 10;

    /**
     * 同步方法,对象锁
     */
    public synchronized void syncMethod() {
        for (int i = 0; i < 1000; i++) {
            if (ticket > 0) {
                ticket--;
                System.out.println(Thread.currentThread().getName() + "剩余的票数:" + ticket);
            }
        }
    }

    /**
     * 同步块,对象锁
     */
    public void syncThis() {
        synchronized (this) {
            for (int i = 0; i < 1000; i++) {
                if (ticket > 0) {
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "剩余的票数:" + ticket);
                }
            }
        }
    }

    /**
     * 同步class对象,类锁
     */
    public void syncClassMethod() {
        synchronized (SynchronizedEntity.class) {
            for (int i = 0; i < 50; i++) {
                if (ticket > 0) {
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "剩余的票数:" + ticket);
                }
            }
        }
    }

    /**
     * 同步静态方法,类锁
     */
    public static synchronized void syncStaticMethod(){
        // 暂不演示该方法
    }

}
多个线程调用同一个对象锁
    /**
     * 多个线程调用同一个对象锁
     */
    private void testSynchronized2() {
        final SynchronizedEntity synchronizedEntity = new SynchronizedEntity();

        // 线程一
        new Thread() {
            @Override
            public void run() {
                synchronizedEntity.syncMethod();
            }
        }.start();
        // 线程二
        new Thread() {
            @Override
            public void run() {
                synchronizedEntity.syncThis();
            }
        }.start();
    }

打印日志可以看到有效的顺序执行


image.png
两个线程分别调用不同对象锁
    /**
     * 两个线程分别调用不同对象锁
     */
    private void testSynchronized3() {
        final SynchronizedEntity synchronizedEntity1 = new SynchronizedEntity();
        final SynchronizedEntity synchronizedEntity2 = new SynchronizedEntity();

        // 线程一
        new Thread() {
            @Override
            public void run() {
                synchronizedEntity1.syncMethod();
            }
        }.start();
        // 线程二
        new Thread() {
            @Override
            public void run() {
                synchronizedEntity2.syncMethod();
            }
        }.start();
    }

打印日志可以看到票数顺序乱了


image.png
两个线程分别调用对象锁、类锁
    /**
     * 两个线程分别调用对象锁、类锁
     */
    private void testSynchronized4() {
        final SynchronizedEntity synchronizedDemo = new SynchronizedEntity();

        // 线程一
        new Thread() {
            @Override
            public void run() {
                synchronizedDemo.syncMethod();
            }
        }.start();

        // 线程二
        new Thread() {
            @Override
            public void run() {
                synchronizedDemo.syncClassMethod();
            }
        }.start();
    }

打印日志如图,他们互不影响,所以也是线程不安全的

image.png
总结:不同对象之间的对象锁是互不影响的,而类锁只有一个。但是同时对象锁和类锁又不互不影响
wait()、notify()、notifyAll()

wait、notify、notifyAll都必须在synchronized中执行,否则会抛出异常。所以为什么会先讲synchronized
notify跟notifyAll区别是notify会唤醒等待唤醒队列中的第一个线程,而notifyAll()方法则是唤醒整个唤醒队列中的所有线程
直接上代码,先创建一个线程,该线程是sleep自身2秒后再唤醒所有线程

public class MyThreadWait extends Thread {

    private final Object lockObject;

    public MyThreadWait(Object lockObject) {
        this.lockObject = lockObject;
    }

    @Override
    public void run() {
        synchronized (lockObject) {
            try {
                // 子线程等待了2秒钟后唤醒lockObject锁
                sleep(2000);
                System.out.println("lockObject唤醒");
                lockObject.notifyAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
    /**
     * wait示例
     */
    private void testWait() {
        // 创建子线程
        Thread thread = new MyThreadWait(lockObject);
        thread.start();

        long start = System.currentTimeMillis();
        synchronized (lockObject) {
            try {
                System.out.println("lockObject等待");
                lockObject.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("lockObject继续 --> 等待的时间:" + (System.currentTimeMillis() - start));
        }
    }
wait()、notify()、notifyAll() 进阶

接着是java经典题目,子线程循环2次,接着主线程循环3次,接着又回到子线程循环2次,接着再回到主线程又循环3次,如此循环10次

    /**
     * 锁对象
     */
    private final Object lock = new Object();
    /**
     * 是否执行子线程标志位
     */
    boolean beShouldSub = true;

    /**
     * wait和notify示例
     * 子线程循环2次,接着主线程循环3次,接着又回到子线程循环2次,接着再回到主线程又循环3次,如此循环10次
     */
    private void testWaitNotify() {
        // 子线程
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    testWaitNotifyThread();
                }
            }
        }.start();
        // 主线程
        for (int i = 0; i < 10; i++) {
            testWaitNotifyMain();
        }
    }

    /**
     * 子线程循环两次
     */
    private void testWaitNotifyThread() {
        synchronized (lock) {
            if (!beShouldSub) {
                // 等待
                try {
                    Log.d("testWaitNotify","子线程等待lock");
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            for (int j = 0; j < 2; j++) {
                Log.d("testWaitNotify","子循环第" + (j + 1) + "次");
            }
            // 子线程执行完毕,子线程标志位设为false
            beShouldSub = false;
            // 唤醒
            Log.d("testWaitNotify","子线程唤醒lock");
            lock.notify();
        }
    }

    /**
     * 主线程循环3次
     */
    private void testWaitNotifyMain() {
        synchronized (lock) {
            if (beShouldSub) {
                // 等待
                try {
                    Log.d("testWaitNotify","主线程等待lock");
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            for (int j = 0; j < 3; j++) {
                Log.d("testWaitNotify","主循环第" + (j + 1) + "次");
            }
            // 主线程执行完毕,子线程标志位设为true
            beShouldSub = true;
            // 唤醒
            Log.d("testWaitNotify","主线程唤醒lock");
            lock.notify();
        }
    }
volatile进阶

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
  1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
  2)禁止进行指令重排序。
该链接超级详细的讲解了volatile
Java并发编程:volatile关键字解析 - Matrix海子 - 博客园 (cnblogs.com)
在DEMO中我也详细的写了一个错误示范例子

    /**
     * 这是Volatile的一个错误示范
     * 事实上运行它会发现每次运行结果都不一致,都是一个小于10000的数字。
     * 可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性
     * 自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存
     * 那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:
     * 假如某个时刻变量inc的值为10
     * 线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了
     * 然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,增加1变成11,并把11写入工作内存,最后写入主存
     * 然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。
     * 那么两个线程分别进行了一次自增操作后,inc只增加了1。
     */
    private void testVolatileNo() {
        final Test test = new Test();
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread() {
                @Override
                public void run() {
                    for (int j = 0; j < 1000; j++) {
                        test.increase();
                    }
                    if (finalI ==9) {
                        System.out.println("test.inc: " + test.inc);
                    }
                }
            }.start();
        }
    }

参考学习文章:
妈妈再也不用担心你不会使用线程池了(ThreadUtils) - 简书 (jianshu.com)
Android-多线程 - 简书 (jianshu.com)
深入理解线程和线程池(图文详解)weixin_40271838的博客-CSDN博客线程池
安卓Thread的运用 Thread.join()_ruiruiddd的博客-CSDN博客
Android进阶——多线程系列之wait、notify、sleep、join、yield、synchronized关键字、ReentrantLock锁_点击置顶文章查看博客目录(全站式导航)-CSDN博客

上一篇下一篇

猜你喜欢

热点阅读