1.1 线程的基本概念
    什么是线程?
    一个线程是一个程序内部的顺序控制流
    线程和进程
    每个进程都有独立的代码和数据空间(进程上下文),进程之间切换开销大
    线程:轻量的进程,同一类线程共享代码和数据空间,每个线程有独立运行的栈和程序计数器(PC),线程切换开销小。
    多进程:在操作系统中能够同时运行多个任务(程序)。
    多线程:在同一个应用程序中有多个顺序流同时执行。

    1.2 Thread类详解

    名称 说明
    public Thread() 构造一个新的线程对象,默认名称是Thread-n,n是从0开始递增的整数
    public Thread(Runnable target) 构造一个新的线程对象,以一个实现Runnable接口的类的对象为参数。默认名称是Thread-n,n是从0开始递增的整数
    public static Thread currentThread() 返回当前正在运行的线程对象
    public static void yield() 使当前线程对象暂停,允许别的线程开始运行
    public static void sleep(long millis) 使当前线程睡眠指定毫秒数,但此线程并不失去已获得的锁
    public void start() 启动线程,JVM将调用此线程的run方法,结果是将同时运行两个线程,当前线程和执行run方法的线程
    public void run() Thread子类重写此方法,内容应为该线程应该执行的任务
    public final void stop() 停止线程运行,释放该线程占用的对象锁
    public void interrupt() 中断此线程
    public final void join() 如果此前启动了线程A,调用join方法将等待线程A死亡才能继续执行当前线程。
    public final join(long millis) 如果此前启动了线程A,调用join方法将等待指定毫秒数或线程A死亡才能继续执行当前线程
    public final void setPriority(int newPriority) 设置线程优先级
    public final void setDaemon(Boolean on) 设置是否为后台线程,如果当前运行线程均为后台线程则JVM停止运行。这个方法必须在start()方法前使用
    public final void checkAccess() 判断当前线程是否有权利修改调用此方法的线程
    public void setName(String name) 更改本线程的名称为指定参数
    public final boolean isAlive() 测试线程是否处于活动状态,如果线程启动并没有死亡则返回true

    1.3 Runnable接口

    • 只有一个run()方法
    • Thread类实现了Runnable接口
    • 便于多个线程之间共享资源
    • Java不支持多继承,如果已经继承了某个积累,实现Runnable接口来生成多线程
    • 以实现Runnable的对象为参数建立新的线程

    1.4 线程内部数据共享

    • 用同一个实现了Runnable接口的对象作为参数创建多个线程
    • 多个线程共享同一对象中的相同数据 ```java public class ShareTargetTester { public static void main(String[] args) {
      1. RunnableThread runnableThread = new RunnableThread();
      2. System.out.println("starting threads");
      3. new Thread(runnableThread, "thread-1").start();
      4. new Thread(runnableThread, "thread-2").start();
      5. new Thread(runnableThread, "thread-3").start();
      6. System.out.println("Threads started,main ends\n");
      } }

    class RunnableThread implements Runnable { private final int sleepTime;

    1. public RunnableThread() {
    2. sleepTime = (int) (Math.random() * 6000);
    3. }
    4. @Override
    5. public void run() {
    6. System.out.println(Thread.currentThread().getName() + " going to sleep for " + sleepTime);
    7. }

    }

    1. 运行结果:

    Starting threads Threads started,main ends

    thread-1 going to sleep for 5601 thread-3 going to sleep for 5601 thread-2 going to sleep for 5601

    1. 说明:<br />因为是用一个Runnable类型对象创建的3个新线程,这三个线程就共享了这个对象的私有成员sleepTime,在本次运行中,三个线程都休眠了5601毫秒。<br />独立且同时运行的线程有时需要共享一些数据,并且考虑到彼此的状态和动作
    2. 模拟售票窗口:
    3. ```java
    4. /**
    5. * 模拟三个窗口同时出售200张车票
    6. */
    7. public class SellTicketsTester {
    8. public static void main(String[] args) {
    9. SellTickets t = new SellTickets();
    10. new Thread(t).start();
    11. new Thread(t).start();
    12. new Thread(t).start();
    13. }
    14. }
    15. class SellTickets implements Runnable {
    16. private int tickets = 200;
    17. @Override
    18. public void run() {
    19. while (tickets > 0) {
    20. System.out.println(Thread.currentThread().getName() + " is selling ticket " + tickets--);
    21. }
    22. }
    23. }

    运行结果(最后五行):

    1. Thread-1 is selling ticket 5
    2. Thread-0 is selling ticket 6
    3. Thread-1 is selling ticket 2
    4. Thread-2 is selling ticket 3
    5. Thread-0 is selling ticket 1

    说明:
    在这个例子中,创建三个线程,每个线程调用的是同一个SellTickets对象中的run()方法,访问的是同一个对象中的变量(tickets)
    如果是通过创建Thread类的子类来模拟售票过程,再创建3个新线程,则每个线程都会有各自的方法和变量,虽然方法相同,但变量却各有200张票,因而结果将会是各卖出200张票。

    1.5 多线程的同步控制
    问题
    多线程是如何实现同步的?
    多线程中如何避免死锁问题?
    线程的生命周期是如何的?
    每个线程之间优先级如何控制?

    • 有时线程之间彼此不独立、需要同步
      • 线程间的互斥
        • 同时运行的几个线程需要共享一个(些)数据
        • 共享的数据,在某一时刻只允许一个线程对其进行操作
      • 生产者/消费者问题
        • 假设有一个线程负责往数据区写数据,另一个线程从统一数据取读数据,两个线程可以并行执行
        • 如果数据区已满,生产者要等消费者读取一些数据后才能写
        • 当数据区空时,消费者要等生产者写入一些数据后再取
    • 用两个线程模拟存票、售票过程
      • 假定开始售票处并没有票,一个线程往里存票,另一个线程则往出卖票
      • 新建一个票类对象,让存票和售票线程都访问他。本例采用两个线程共享同一个数据对象来实现对同一份数据的操作。

    1.6 同步和锁的要点

    1. 只能同步方法,不能同步变量;
    2. 每个对象只有一个锁;当提到同步时,应该清楚在什么上同步,也就是说在那个对象上同步。
    3. 类可以拥有同步和非同步方法,非同步方法可以被多个线程自由访问而不受锁的限制
    4. 如果两个线程使用相同的实例来访问synchronized方法,那么一次只能有一个线程执行方法,另一个需要等待锁
    5. 线程休眠时,所持有的任何锁都不会释放
    6. 线程可以获得多个锁,比如,在一个对象的同步方法中调用另外一个对象的同步方法,获得两个对象的同步锁
    7. 同步损害并发性,应该尽可能的缩小同步范围,同步不仅可以同步整个方法,还可以同步方法中的代码块
    8. 在使用同步代码块时,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁

    并发问题:
    作者:LeetCode
    链接:https://leetcode-cn.com/problems/print-in-order/solution/an-xu-da-yin-by-leetcode/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    1.7 Semaphore关键字(信号量)
    用来控制同时访问某个资源的线程数,使用semaphore对象,acquire()方法会减少一个许可,release()会增加一个许可

    1. import java.util.concurrent.Semaphore;
    2. import java.util.concurrent.TimeUnit;
    3. public class SemaphoreTest {
    4. static Semaphore semaphore = new Semaphore(3);//限制访问资源的线程数,并不实现同步。三个打饭窗口
    5. public static void main(String[] args) {
    6. for (int i = 0; i < 10; i++) {
    7. new Student(semaphore, "学生" + i).start();
    8. }
    9. }
    10. }
    11. class Student extends Thread {
    12. private final Semaphore semaphore;
    13. private final String name;
    14. public Student(Semaphore semaphore, String name) {
    15. this.semaphore = semaphore;
    16. this.name = name;
    17. }
    18. @Override
    19. public void run() {
    20. try {
    21. System.out.println(name + "进入餐厅");
    22. semaphore.acquire(); //获得许可
    23. System.out.println(name + "获得许可");
    24. TimeUnit.SECONDS.sleep(3);
    25. } catch (InterruptedException e) {
    26. e.printStackTrace();
    27. } finally {
    28. System.out.println(name + "释放许可");
    29. semaphore.release();
    30. }
    31. }
    32. }

    Semaphore常见方法:

    • acquire(int permits)
      • 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。就好比是一个学生占两个窗口。这同时也对应了相应的release方法。
    • release(int permits)
      • 释放给定数目的许可,将其返回到信号量。这个是对应于上面的方法,一个学生占几个窗口完事之后还要释放多少
    • availablePermits()
      • 返回此信号量中当前可用的许可数。也就是返回当前还有多少个窗口可用。
    • reducePermits(int reduction)
      • 根据指定的缩减量减小可用许可的数目。
    • hasQueuedThreads()
      • 查询是否有线程正在等待获取资源。
    • getQueueLength()
      • 返回正在等待获取的线程的估计数目。该值仅是估计的数字。
    • tryAcquire(int permits, long timeout, TimeUnit unit)
      • 如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。
    • acquireUninterruptibly(int permits)
      • 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。

    使用信号量控制线程执行顺序:

    1. class Foo {
    2. public Foo() {
    3. }
    4. Semaphore first=new Semaphore(0);
    5. Semaphore second=new Semaphore(0);
    6. public void first(Runnable printFirst) throws InterruptedException {
    7. printFirst.run();
    8. first.release();
    9. }
    10. public void second(Runnable printSecond) throws InterruptedException {
    11. first.acquire();
    12. printSecond.run();
    13. second.release();
    14. }
    15. public void third(Runnable printThird) throws InterruptedException {
    16. second.acquire();
    17. printThird.run();
    18. }
    19. }

    线程同步控制打印foobar

    1. class FooBar {
    2. private final int n;
    3. private boolean isFoo;
    4. public FooBar(int n) {
    5. this.n = n;
    6. }
    7. public synchronized void foo(Runnable printFoo) throws InterruptedException {
    8. /**
    9. * 先执行 printFoo
    10. * 然后让这个线程阻塞,另一个线程调用 printBar
    11. *
    12. */
    13. for (int i = 0; i < n; i++) {
    14. printFoo.run();
    15. isFoo = true;
    16. this.notify();
    17. if (i < n - 1) {//n=3
    18. this.wait();
    19. }
    20. }
    21. }
    22. public synchronized void bar(Runnable printBar) throws InterruptedException {
    23. if (!isFoo) {
    24. this.wait();
    25. }
    26. for (int i = 0; i < n; i++) {
    27. printBar.run();
    28. //执行完毕,让这个线程等待
    29. this.notify();
    30. if (i < n - 1) {//否则最后一次线程仍然再阻塞
    31. this.wait();
    32. }
    33. }
    34. }
    35. }