多线程总结(初阶)

目录

1、认识线程(Thread)

1.1概念

1)线程是什么

2)线程相比于进程的优势

3)进程和线程的区别

4)java中线程与操作系统中线程的关系

1.2创建第一个多线程

1.3使用Runnable接口方法创建线程

2、Thread类及其常见方法

2.1构造方法

2.2Thread的几个常见属性

2.3启动一个线程start

2.4中断一个线程

2.5等待一个线程

2.6获取当前线程的引用

2.7休眠当前线程

3、线程的状态

3.1观察线程的所有状态

4、多线程带来的风险-线程安全(重点)

4.1发现线程不安全现象

4.2线程安全的概念

4.3线程不安全的原因

4.4解决之前的线程不安全问题

5、synchronized关键字

5.1 synchronized 的特性

5.2synchronized使用示例

6、volatile关键字

7、wait和notify

7.1wait()方法

7.2notify()方法

7.3notifyAll() 方法

7.4wait和sleep的对比

8、多线程案例

8.1单例模式

8.2阻塞式队列

8.3定时器

8.4线程池

9、总结-保证线程安全的思路

10、对比线程和进程

10.1线程的优点

10.2线程与进程的区别


1、认识线程(Thread)

1.1概念

1)线程是什么

一个线程就是一个执行流,每个线程之间都可以按照顺序执行自己的代码,多个线程之间同时执行着多份代码。

2)线程相比于进程的优势

创建线程比创建进程更快。

销毁线程比销毁进程更快。

调度线程比调度进程更快。

3)进程和线程的区别

进程是包含线程的,每个进程中至少包含一个线程(主线程)。

进程和进程是不共享同一个内存空间的,但同一个进程中的线程,是共享同一个内存空间的。

进程是资源分配的最小单元。线程是系统调度的最小单元。

4)java中线程与操作系统中线程的关系

java为了降低程序员的学习成本,将操作系统的API外嵌套了一层java自己的API,这样就可以让程序在不同的操作系统上运行,还不用更改程序。具体方式表示为使用Thread类

1.2创建第一个多线程

多线程的核心要领,就是在于 “同时运行”。

自定义一个类,然后继承Thread,同时复写其中的run方法。

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("执行该线程");
    }
}
public class demo1{
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        myThread.run();
    }
}

1.3使用Runnable接口方法创建线程

要注意由于Runnable是接口,不可创建对象,因此将其作为Thread的参数进行传入

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("线程使用成功");
    }
}
public class demo2 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
    }
}

其他变形

public class demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("匿名内部类创建Thread子类对象");
            }
        };
        t.start();
    }
}
public class demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类创建Runnable接口");
            }
        });
        t.start();
    }
}
public class demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("匿名内部类创建lambda表达式");
        });
        t.start();
    }
}

2、Thread类及其常见方法

2.1构造方法

可以采用空参构造、传入Runnable对象构造、传入线程名字构造

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

2.2Thread的几个常见属性

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()

ID是线程的唯一表示。

名称是各种调试工具用到。

状态表示线程当前所处在的状态。

优先级高的理论上更容易被调度到。

JVM会在一个进程的所有非后台线程结束后,才会结束运行。

是否存活可以理解为run方法是否结束。

线程中断,后续说明。

2.3启动一个线程start

启动线程就必须要复写其中的run方法。

start()会调用其中的run方法来执行。若是直接使用t.run()则表示最普通的、调用对象t中的一个普通方法,并没有触发多线程。一定要通过start()去调用。

调用start方法,才真的在操作系统的底层创建出一个线程。

2.4中断一个线程

目前,有两种方法中断线程:

1.通过共享的标记来进行沟通。

2.调用interrupt()方法来通知。

public class demo4 {

    public static boolean isInterrupt = false;

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (true){
                if (!isInterrupt){
                    System.out.println("运行线程t");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    System.out.println("程序中断");
                }
            }
        });

        Thread i = new Thread(() -> {
            try {
                Thread.sleep(5000);
                isInterrupt = true;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t.start();
        i.start();


    }
}

public class demo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            //当前线程是否发生中断
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Thread.sleep(1000);
                    System.out.println("线程正常运行");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }
            System.out.println("中断已经发生");
        });
        t.start();
        Thread.sleep(5000);
        t.interrupt();      //开始中断
    }
}

Thread收到通知的方法主要有两种:

1.如果线程因为调用wait / join / sleep等方法而阻塞挂起,则以interruptedException异常的形式通知,清除中断标志。值得注意的是,当出现interruptedException的时候,要不要结束线程取决于catch中的写法。

2.如果catch中没有break,就只是内部的一个中断标志被设置,thread可以通过Thread.interrupted()判断当前线程的中断标志被设置,清除中断标志,Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志。

这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。

// 只有一开始是 true,后边都是 false,因为标志位被清
public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.interrupted());
           }
       }
   }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        thread.start();
        thread.interrupt();
   }
}
// 全部是 true,因为标志位没有被清
public class ThreadDemo {
    private static class MyRunnable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
 System.out.println(Thread.currentThread().isInterrupted());
           }

       }
   }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        thread.start();
        thread.interrupt();
   }
}

2.5等待一个线程

join方法会让该线程执行完以后

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() 
                                       + ": 我还在工作!");
                    Thread.sleep(1000);
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
            System.out.println(Thread.currentThread().getName() + ": 我结束了!");
       };
        Thread thread1 = new Thread(target, "李四");
        Thread thread2 = new Thread(target, "王五");
        System.out.println("先让李四开始工作");
        thread1.start();
        thread1.join();
        System.out.println("李四工作结束了,让王五开始工作");
        thread2.start();
        thread2.join();
        System.out.println("王五工作结束了");
   }
}

2.6获取当前线程的引用

public static Thread currentThread();
public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
   }
}

2.7休眠当前线程

public static void sleep(long millis) throws InterruptedException, 休眠当前线程 millis 毫秒。
public static void sleep(long millis, int nanos) throws InterruptedException,可以更高精度的休眠
public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(System.currentTimeMillis());
        Thread.sleep(3 * 1000);
        System.out.println(System.currentTimeMillis());
   }
}

3、线程的状态

3.1观察线程的所有状态

public class ThreadState {
    public static void main(String[] args) {
        for (Thread.State state : Thread.State.values()) {
            System.out.println(state);
       }
   }
}

NEW:安排了工作,还未开始行动。

RUNNABLE:可工作的,又可以分成正在工作中和即将开始工作。

BLOCKED:这几个都表示排队等着其他事情。

WAITING:这几个都表示排队等着其他事情。

TIMED_WAITING:这几个都表示排队等着其他事情。

TERMINATED: 工作完成了。
public class ThreadStateTransfer {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 1000_0000; i++) {
           }
       }, "李四");
        System.out.println(t.getName() + ": " + t.getState());
        t.start();
        while (t.isAlive()) {
            System.out.println(t.getName() + ": " + t.getState());
       
        System.out.println(t.getName() + ": " + t.getState());
   }
}
public static void main(String[] args) {
    final Object object = new Object();
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (object) {
                while (true) {
                    try {
                        Thread.sleep(1000);
                   } catch (InterruptedException e) {
                        e.printStackTrace();
                   }
               }
           }
       }
   }, "t1");
    t1.start();
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (object) {
                System.out.println("hehe");
           }
       }
   }, "t2");
    t2.start();
}
1. 不使用 yield 的时候 , 张三李四大概五五开
2. 使用 yield , 张三的数量远远少于李四
Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        while (true) {
            System.out.println("张三");
            // 先注释掉, 再放开
            // Thread.yield();
       }
   }
}, "t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        while (true) {
            System.out.println("李四");
       }
   }
}, "t2");
t2.start();
结论:yield 不改变线程的状态 , 但是会重新去排队

4、多线程带来的风险-线程安全(重点)

4.1发现线程不安全现象

static class Counter {
    public int count = 0;
    void increase() {
        count++;
   }
}
public static void main(String[] args) throws InterruptedException {
    final Counter counter = new Counter();
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 50000; i++) {
            counter.increase();
       }
   });
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 50000; i++) {
            counter.increase();
       }
   });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(counter.count);
}

4.2线程安全的概念

如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线 程安全的。

4.3线程不安全的原因

修改共享数据

上面线程不安全的代码中,涉及多个线程针对counter.count变量进行修改。

此时这个counter.count是一个多线程都能访问到的 “共享数据”。

counter.count变量是在堆上的,因此可以被多个线程访问到。

原子性

简单来说就是把一点代码当成一个整体,不允许其他的代码与他交叉实行。
如果没有保证原子性,那么当我执行代码1的时候,可能会有其他的代码穿插进来,影响代码1的执行结果。
可见性
可见性指 , 一个线程对共享变量值的修改,能够及时地被其他线程看到 .

=>线程之间的共享变量存在主内存(main memory)

=>每一个线程都有自己的工作内存(working memory)

=>当线程要读取一个共享变量的时候, 会先把变量从主内存拷贝到工作内存, 再从工作内存读取数据

=>当线程要修改一个共享变量的时候, 也会先修改工作内存中的副本, 再同步回主内存.

由于每个线程有自己的工作内存 , 这些工作内存中的内容相当于同一个共享变量的 " 副本 ". 此时修改线程 1 的工作内存中的值 , 线程 2 的工作内存不一定会及时变化。

之所以这么大费周章的划分这么多内存,原因有二:

1.实际并没有这么多 " 内存 ". 这只是 Java 规范中的一个术语 , 是属于 " 抽象 " 的叫法 .
所谓的 " 主内存 " 才是真正硬件角度的 " 内存 ". 而所谓的 " 工作内存 ", 则是指 CPU 的寄存器和高速缓存 .
2.因为 CPU 访问自身寄存器的速度以及高速缓存的速度 , 远远超过访问内存的速度 ( 快了 3 - 4 个数量级 , 也就是几千倍, 上万倍 ).

代码顺序性

代码重排序是指JVM会保持逻辑不变的情况下,对代码执行顺序进行调整,方便更快运行,但此时可能会使得代码出现bug。

4.4解决之前的线程不安全问题

其实只需要把increase这个方法加上synchronized锁,锁住即可,即表示当我执行该方法时,其他线程就不可以操作该方法了。

static class Counter {
    public int count = 0;
    synchronized void increase() {
        count++;
   }
}

5、synchronized关键字

5.1 synchronized 的特性

1)互斥性

synchronized 会起到互斥效果 , 某个线程执行到某个对象的 synchronized 中时 , 其他线程如果也执行到同一个对象 synchronized 就会 阻塞等待。
2)刷新内存
synchronized 的工作过程 :
1. 获得互斥锁
2. 从主内存拷贝变量的最新副本到工作的内存
3. 执行代码
4. 将更改后的共享变量的值刷新到主内存
5. 释放互斥锁
所以 synchronized 也能保证内存可见性 . 具体代码参见后面 volatile 部分 .

3)可重入(锁套娃)

synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;
static class Counter {
    public int count = 0;
    synchronized void increase() {
        count++;
   }
    synchronized void increase2() {
        increase();
   }
}
在可重入锁的内部 , 包含了 " 线程持有者 " " 计数器 " 两个信息 .

=>如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取到锁, 并让计数器自增.

=>解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)

5.2synchronized使用示例

synchronized 本质上要修改指定对象的 " 对象头 ". 从使用角度来看 , synchronized 也势必要搭配一个具体的对象来使用。

1)直接修饰普通方法

public class SynchronizedDemo {
    public synchronized void methond() {
   }
}

2)修饰静态方法

public class SynchronizedDemo {
    public synchronized static void method() {
   }
}

3)修饰代码块

//锁当前对象,this
public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            
       }
   }
}
//锁类对象
public class SynchronizedDemo {
    public void method() {
        synchronized (SynchronizedDemo.class) {
       }
   }
}

6、volatile关键字

volatile 修饰的变量 , 能够保证 " 内存可见性 ".
代码在写入 volatile 修饰的变量的时候。
        =>改变线程工作内存中volatile 变量副本的值。
        =>将改变后的副本的值从工作内存刷新到主内存。
代码在读取 volatile 修饰的变量的时候。
        =>从主内存中读取volatile 变量的最新值到线程的工作内存中。
        =>从工作内存中读取volatile 变量的副本。

举例说明:

实际上就是可以理解为,如果我不加volatile,那么当我在运行线程1的时候,线程2过来插了一脚,线程2把值修改了,但此时线程1还在运行,此时就会产生bug,我的理想情况应该是线程2一修改,线程1就停止。当我加上volatile以后,线程2一变,线程1就停了。

原因就是因为(通俗理解)程序是从内存中取出flag值存在一个临时内存上,线程2修改的是原内存上的,临时内存并没有变。

static class Counter {
    public int flag = 0;
}
public static void main(String[] args) {
    Counter counter = new Counter();
    Thread t1 = new Thread(() -> {
        while (counter.flag == 0) {
            // do nothing
       }
        System.out.println("循环结束!");
   });
    Thread t2 = new Thread(() -> {
        Scanner scanner = new Scanner(System.in);
        System.out.println("输入一个整数:");
        counter.flag = scanner.nextInt();
   });
    t1.start();
    t2.start();
}
// 执行效果
// 当用户输入非0值时, t1 线程循环不会结束. (这显然是一个 bug)
static class Counter {
    public volatile int flag = 0;
}
// 执行效果
// 当用户输入非0值时, t1 线程循环能够立即结束.
volatile 不保证原子性
volatile synchronized 有着本质的区别 . synchronized 能够保证原子性 , volatile 保证的是内存可见性.
//此时可以看到, 最终 count 的值仍然无法保证是 100000.
static class Counter {
    volatile public int count = 0;
    void increase() {
        count++;
   }
}
public static void main(String[] args) throws InterruptedException {
    final Counter counter = new Counter();
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 50000; i++) {
            counter.increase();
       }
   });
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 50000; i++) {
            counter.increase();
       }
   });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(counter.count);
}
synchronized 也能保证内存可见性
synchronized 既能保证原子性 , 也能保证内存可见性。
对上面的代码进行调整  : 去掉 flag volatile,给 t1 的循环内部加上 synchronized, 并借助 counter 对象加锁。
static class Counter {
    public int flag = 0;
}
public static void main(String[] args) {
    Counter counter = new Counter();
    Thread t1 = new Thread(() -> {
        while (true) {
            synchronized (counter) {
                if (counter.flag != 0) {
                    break;
               }
           }
            // do nothing
       }
        System.out.println("循环结束!");
   });
    Thread t2 = new Thread(() -> {
        Scanner scanner = new Scanner(System.in);
        System.out.println("输入一个整数:");
        counter.flag = scanner.nextInt();
   });
    t1.start();
    t2.start();
}

7、wait和notify

使用wait()让某些线程阻塞,使用notify()唤醒某些阻塞线程。通过这种方式来调节线程执行顺序。

完成这个协调工作 , 主要涉及到三个方法
wait() / wait(long timeout): 让当前线程进入等待状态 .
notify() / notifyAll(): 唤醒在当前对象上等待的线程 .

7.1wait()方法

wait()必须搭配synchronized使用,若未使用synchronized则会爆出异常。

public static void main(String[] args) throws InterruptedException {
    Object object = new Object();
    synchronized (object) {
        System.out.println("等待中");
        object.wait();
        System.out.println("等待结束");
   }
}

7.2notify()方法

代码示例 : 使用 notify() 方法唤醒线程
创建 WaitTask , 对应一个线程 , run 内部循环调用 wait.
创建 NotifyTask , 对应另一个线程 , run 内部调用一次 notify
注意 , WaitTask NotifyTask 内部持有同一个 Object locker. WaitTask NotifyTask 要想配合
就需要搭配同一个 Object.
static class WaitTask implements Runnable {
    private Object locker;
    public WaitTask(Object locker) {
        this.locker = locker;
   }
    @Override
    public void run() {
        synchronized (locker) {
            while (true) {
                try {
                    System.out.println("wait 开始");
                    locker.wait();
                    System.out.println("wait 结束");
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
           }
       }
   }
}
static class NotifyTask implements Runnable {
    private Object locker;
    public NotifyTask(Object locker) {
        this.locker = locker;
   }
    @Override
    public void run() {
        synchronized (locker) {
            System.out.println("notify 开始");
            locker.notify();
            System.out.println("notify 结束");
       }
   }
}
public static void main(String[] args) throws InterruptedException {
    Object locker = new Object();
    Thread t1 = new Thread(new WaitTask(locker));
    Thread t2 = new Thread(new NotifyTask(locker));
    t1.start();
    Thread.sleep(1000);
    t2.start();
}

7.3notifyAll() 方法

//notifyAll()唤醒所有线程
public void run() {
    synchronized (locker) {
        System.out.println("notify 开始");
        locker.notifyAll();
        System.out.println("notify 结束");
   }
}
注意 : 虽然是同时唤醒 3 个线程 , 但是这 3 个线程需要竞争锁 . 所以并不是同时执行 , 而仍然是有先有后的执行。

7.4wait和sleep的对比

其实没有可比性,一个用于线程之间通信的,一个让线程阻塞一段时间。

唯一相同点就是都让程序阻塞一段时间。

1. wait 需要搭配 synchronized 使用 . sleep 不需要 .
2. wait Object 的方法 sleep Thread 的静态方法 .

8、多线程案例

8.1单例模式

单例模式能保证某个类在程序中只存在唯一一份实例 , 而不会创建出多个实例 .

单例模式分为饿汉和懒汉模式。

饿汉与懒汉的区别,就在于实例创建的时机不同。

饿汉模式
类加载的同时 , 创建实例 .
class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
   }
}
懒汉模式 - 单线程版
类加载的时候不创建实例 . 第一次使用的时候才创建实例 .
这种懒汉模式的实现是线程不安全的
加上 synchronized 可以改善这里的线程安全问题 .
class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
       }
        return instance;
   }
}
懒汉模式 - 多线程版
加上 synchronized 可以改善这里的线程安全问题 .
class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
       }
        return instance;
   }
}
懒汉模式 - 多线程版 ( 改进 )
以下代码在加锁的基础上 , 做出了进一步改动 :
使用双重 if 判定 , 降低锁竞争的频率 .
instance 加上了 volatile.
class Singleton {
    private static volatile Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
           if (instance == null) {
               instance = new Singleton();
               }
           }
       }
        return instance;
   }
}

注意理解这里两个if的使用:线程加锁开锁是意见开销比较大的事情。而懒汉模式的线程不安全,仅仅只存在于第一次创建实例的时候,后续操作都不需要进行加锁。因此,此处第一个if就是为了判断是否已经把instance实例创建出来了。

也就是说,外面的if是为了避免反复执行里面的synchronized。里面的if是正常逻辑,即有实例就不创建,没有实例就创建。

8.2阻塞式队列

阻塞队列是一种特殊的队列 . 也遵守 " 先进先出 " 的原则 .
阻塞队列能是一种线程安全的数据结构 , 并且具有以下特性 :
当队列满的时候 , 继续入队列就会阻塞 , 直到有其他线程从队列中取走元素 .
当队列空的时候 , 继续出队列也会阻塞 , 直到有其他线程往队列中插入元素 .
生产消费者模型
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.
1) 阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力 .
2) 阻塞队列也能使生产者和消费者之间 解耦 .
标准库中的阻塞队列
Java 标准库中内置了阻塞队列 . 如果我们需要在一些程序中使用阻塞队列 , 直接使用标准库中的即可 .
BlockingQueue 是一个接口 . 真正实现的类是 LinkedBlockingQueue.
put 方法用于阻塞式的入队列 , take 用于阻塞式的出队列 .
BlockingQueue 也有 offer, poll, peek 等方法 , 但是这些方法不带有阻塞特性 .
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 入队列
queue.put("abc");
// 出队列. 如果没有 put 直接 take, 就会阻塞. 
String elem = queue.take()

生产消费者模型

public static void main(String[] args) throws InterruptedException {
    BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();
    Thread customer = new Thread(() -> {
        while (true) {
            try {
                int value = blockingQueue.take();
                System.out.println("消费元素: " + value);
           } catch (InterruptedException e) {
                e.printStackTrace();
           }
       }
   }, "消费者");
    customer.start();
    Thread producer = new Thread(() -> {
        Random random = new Random();
        while (true) {
            try {
                int num = random.nextInt(1000);
                System.out.println("生产元素: " + num);
                blockingQueue.put(num);
                Thread.sleep(1000);
           } catch (InterruptedException e) {
                e.printStackTrace();
           }
       }
   }, "生产者");
    producer.start();
    customer.join();
    producer.join();
}

8.3定时器

定时器也是软件开发中的一个重要组件 . 类似于一个 " 闹钟 ". 达到一个设定的时间之后 , 就执行某个指定好的代码.
标准库中的定时器
标准库中提供了一个 Timer . Timer 类的核心方法为 schedule .
schedule 包含两个参数 . 第一个参数指定即将要执行的任务代码 , 第二个参数指定多长时间之后
执行 ( 单位为毫秒 ).
Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("hello");
   }
}, 3000);

8.4线程池

把创建的线程都放入一个池子里

线程池最大的好处就是减少每次启动、销毁线程的损耗
标准库中的线程池
使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池 .
返回值类型为 ExecutorService
通过 ExecutorService.submit 可以注册一个任务到线程池中 .
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
   }
});
Executors 创建线程池的几种方式
        newFixedThreadPool: 创建固定线程数的线程池
        newCachedThreadPool: 创建线程数目动态增长的线程池 .
        newSingleThreadExecutor: 创建只包含单个线程的线程池 .
        newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令 . 是进阶版的 Timer.
Executors 本质上是 ThreadPoolExecutor 类的封装 .
ThreadPoolExecutor 提供了更多的可选参数 , 可以进一步细化线程池行为的设定 . ( 后面再介绍 )

9、总结-保证线程安全的思路

1. 使用没有共享资源的模型
2. 适用共享资源只读,不写的模型
        2.1 不需要写共享资源的模型
        2.2 使用不可变对象
3. 直面线程安全(重点)
        3.1 保证原子性
        3.2 保证顺序性
        3.3 保证可见性

10、对比线程和进程

10.1线程的优点

1. 创建一个新线程的代价要比创建一个新进程小得多
2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
3. 线程占用的资源要比进程少很多
4. 能充分利用多处理器的可并行数量
5. 在等待慢速 I/O 操作结束的同时,程序可执行其他的计算任务
6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
7. I/O 密集型应用,为了提高性能,将 I/O 操作重叠。线程可以同时等待不同的 I/O 操作。

10.2线程与进程的区别

1. 进程是系统进行资源分配和调度的一个独立单位,线程是程序执行的最小单位。
2. 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈。
3. 由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信。
4. 线程的创建、切换及终止效率更高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值