美文网首页
多线程基础篇

多线程基础篇

作者: 聽見下雨的_聲音 | 来源:发表于2019-07-29 14:41 被阅读0次

    多线程

    多线程概念

    进程:正在进行中的程序

    线程:是进程中一个负责程序执行的控制单元(执行路径)一个进行中可以有多个执行线程,称之为多线程。

    一个进程中至少要有一个线程

    开启多个线程是为了同时运行多部份代码

    每一个线程都有自己运行的内容,这个内容可以称之为线程要执行的任务

    好处:解决了多部分同时运行的问题

    弊端:线程太多会导致效率降低

    其实应用程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的。

    Jvm启动时就启动了多个线程,至少有两个小城可以分析出来。

    1. 执行main函数的线程,该小城的任务代码都定义在main函数中
    2. 负责垃圾回收的线程

    如何创建一个线程

    1)继承Thread类创建线程

    2)实现Runnable接口创建线程

    3)使用Callable和Future创建线程

    继承Thread类创建

    步骤:

    1. 定义一个类继承Thread
    2. 覆写Thread类中的run方法。
    3. 直接创建Thread的子类对象创建线程
    4. 调用start方法开启线程并调用小城的任务run方法执行
    public class Demo1 extends Thread{
        @Override
        public void run() {
            // 覆写run方法用于执行线程任务,将要执行的任务定义在run方法中
            for (int i=0; i<10; i++) {
                System.out.println("副线程中执行的代码-->" + i);
            }
        }
    
        public static void main(String[] args) {
            //创建Thread的线程对象
            Demo1 demo = new Demo1();
            // run方法由Jvm去调用执行,我们只需要调用父类Thread的start方法开启线程即可
            demo.start();
            System.out.println("主线程结束");
        }
    }
    

    还可以调用父类ThreadgetName()方法获取线程名称,获取当前正在执行的线程名称则是Thread.currentThread.getName()

    https://www.cnblogs.com/3s540/p/7172146.html
    

    实现Runnable接口

    通过实现Runnable接口创建并启动线程一般步骤如下:

    1. 定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
    2. 创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
    3. 第三部依然是通过调用线程对象的start()方法来启动线程
    public class Demo2 implements Runnable{
        @Override
        public void run() {
            // 覆写run方法用于执行线程任务,将要执行的任务定义在run方法中
            for (int i=0; i<10; i++) {
                System.out.println("副线程中执行的代码-->" + i);
            }
        }
    
        public static void main(String[] args) {
            Demo2 demo2 = new Demo2();
            Thread t1 = new Thread(demo2);
            t1.start();
    
            System.out.println("主线程结束......");
        }
    }
    

    相比继承Thread类的好处:

    1. 将线程的任务从线程的子类中分离出来,进行了单独的封装。按照面向对象的思想将任务封装成对象。
    2. 避免了java单继承的局限性

    实现Callable接口

    Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大:

    call()方法可以有返回值

    call()方法可以声明抛出异常

    Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的任务。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

    boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务

    V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

    V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException

    boolean isDone():若Callable任务完成,返回True

    boolean isCancelled():如果在Callable任务正常完成前被取消,返回True

    介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:

    1. 创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
    2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
    3. 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
    4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

    代码实例

    public class Demo3 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            // 也可以传一个Callable接口的实现类
            FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int sum = 0;
                    for (int i=0; i<5; i++) {
                        System.out.println("副线程执行计算任务" + i);
                        sum = sum + i;
                    }
                    return sum;
                }
            });
    
            Thread thread = new Thread(task);
            thread.start();
            System.out.println("主线程结束......");
            // 获取线程的返回结果
            System.out.println("副线程计算任务执行结果" + task.get());
        }
    }
    

    三种创建线程方法对比

    实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:

    1、线程只是实现Runnable或实现Callable接口,还可以继承其他类。

    2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

    3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。

    4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。

    注:一般推荐采用实现接口的方式来创建多线程

    线程的生命周期

    thread-life-circle.png

    线程安全问题

    产生原因:

    1. 多个线程在操作共享的数据
    2. 操作共享数据的线程代码有多条

    当一个线程在操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题

    解决线程安全问题的思路:

    就是将多条操作共享数据的代码封装起来,当线程 在执行这些代码的时候,必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算

    在Java中,用同步代码块就可以解决这个问题

    同步代码块的格式:

    synchronized(对象){
        需要被同步的代码;
    }
    

    同步的好处:

    解决了线程安全问题

    弊端:

    相对降低了效率,因为同步外的线程都会判断同步锁,消耗资源

    同步的前提:

    同步中必须有多个线程并使用同一个锁

    案例分析:

    package xyz.guqing.thread;
    
    /**
     * 两个储户,每个都到银行存钱每次存100块,共存三次
     */
    class Bank {
        // 银行总账户余额
        private int account;
        // 存钱方法
        public void add(int money) {
            account = account + money;
            System.out.println("account=" + account);
        }
    }
    
    class Customer implements Runnable {
        // 共享数据
        private Bank bank = new Bank();
    
        @Override
        public void run() {
            for(int i=0; i<3; i++) {
                bank.add(100);
            }
        }
    }
    
    class CustomerTest {
        public static void main(String[] args) {
            Customer customer = new Customer();
            // 两个线程表示两个储户
            Thread t1 = new Thread(customer);
            Thread t2 = new Thread(customer);
            t1.start();
            t2.start();
        }
    }
    

    分析上面代码是否存在线程安全问题,从产生线程安全问题的原因分析:

    1. 首先需要判断是否存在共享数据
    2. 操作共享数据的线程代码是否有多条

    对于上面的代码Bank被线程任务执行

    class Bank {
        // 共享数据
        private int account;
        
        public void add(int money) {
            // account共享数据被两条语句操作
            account = account + money;
            System.out.println("account=" + account);
        }
    }
    

    所以会产生线程安全问题,解决办法:加锁

    1. 同步代码块
    //锁需要一个对象
    private Object object = new Object();
    public void add(int money) {
        synchronized(object) {
            account = account + money;
            System.out.println("account=" + account);
        }
    }
    
    1. 同步函数(同步函数用的锁时this对象)
    public synchronized void add(int money) {
        account = account + money;
        System.out.println("account=" + account);
    }
    
    

    同步函数和同步代码块的区别:

    1. 同步函数的锁是固定的this,也就是当前的对象
    2. 同步代码块使用的锁是任意对象

    建议使用同步代码块

    静态同步函数的锁:

    public static synchronized void add(int money) {
        account = account + money;
        System.out.println("account=" + account);
    }
    
    

    还是以上的实例,这样也是可以的,这说明静态同步函数的锁绝对不是this,因为静态函数根本就没有this,那锁是什么呢?

    同步函数有所属的对象this,静态之后就没有所属对象了。锁是有对象的,那么静态函数被加载进内存时有对象吗?,函数随着类的加载而被加载进内存但是该对象还没有通过new创建对象,而java的特点是字节码文件进内存先封装对象,所有的对象建立都有自己所属的字节码文件对象,通过getClass()方法获取,,所以函数被加载进内存时有对象,这个对象就是当前class文件所属的对象。而这个对象也就是同步静态函数所使用的锁

    总结:静态的同步函数使用的锁是该函数所属的字节码文件对象,可以用getClass()获取也可以用当前类名.class形式表示

    多线程下的单类

    饿汉式:

    /**
     * 饿汉式单类
     */
    public class Single {
        private static final Single single = new Single();
    
        private Single() {
    
        }
    
        public static Single getInstance() {
            return single;
        }
    }
    
    

    懒汉式(延迟加载):

    //懒汉式,延迟加载
    public class Single {
        private static Single single = null;
    
        private Single(){}
    
        public static Single getInstance() {
            if(single == null) {
                single = new Single();
            }
            return single;
        }
    }
    
    

    分析懒汉式单类在多线程下是否存在线程安全问题

    // 共享数据
     private static Single single = null;
    
    //多条语句操作共享数据
    // 1.线程0判断为空进入
    // 3.线程1判断还是为空
    if(single == null) {
        // 2.线程0执行到此没有创建对象,cpu执行权被切换走了,等待
        // 4.线程1执行到此,cpu执行权被切换走了,等待
        // 5.等待的线程0获得执行权,创建对象
        // 6.线程1获得执行权创建对象,造成了对象不唯一
        single = new Single();
    }
    
    

    通过上述分析存在线程安全问题,加同步

    1. 同步函数,每次判断锁进入后都需判断if,但是不管存不存在对象都需要判断锁
    public static synchronized Single getInstance() {
        if(single == null) {
            single = new Single();
        }
        return single;
    }
    
    
    1. 同步代码块(静态函数同步代码块,没有this),但是和同步代码函数没什么区别
    public static Single getInstance() {
        // 不能用getClass()方法,因为该方法非静态
        synchronized(Single.class) {
            if(single == null) {
                single = new Single();
            }
        }
    
        return single;
    }
    
    

    改进:

    public static Single getInstance() {
        // 多加一个判断解决效率问题当single!=null时不在需要判断锁
       if(single == null) {
           // 解决同步问题
            synchronized(Single.class) {
                if(single == null) {
                    single = new Single();
                }
            }
        }
    
        return single;
    }
    
    

    死锁

    常见情景之一:同步的嵌套,锁不一致

    public class DeadLock 
    {    
        public static void main(String[] args) 
        {
            byte[] lock1 = new byte[0];
            byte[] lock2 = new byte[0];
    
            Thread th1=new Thread(new Processer1(lock1,lock2));
            Thread th2=new Thread(new Processer2(lock1,lock2));
            th1.setName("th1");
            th2.setName("th2");
    
            th1.start();
            th2.start();
        }
    }
    
    class Processer1 implements Runnable
    {
        private byte[] lock1;
        private byte[] lock2;
        Processer1(byte[] lock1,byte[] lock2){
            this.lock1=lock1;
            this.lock2=lock2;
        }
    
        public void run(){
            synchronized(lock1){
                System.out.println(Thread.currentThread().getName()+" get lock1,and is waiting for lock2.");
                try{
                    Thread.sleep(5000);
                }catch(InterruptedException e){
                        e.printStackTrace();
                }
    
                synchronized(lock2){
                    System.out.println(Thread.currentThread().getName()+" has get lock2.");
                }
            }
        }
    }
    
    class Processer2 implements Runnable
    {
        private byte[] lock1;
        private byte[] lock2;
        Processer2(byte[] lock1,byte[] lock2){
            this.lock1=lock1;
            this.lock2=lock2;
        }
    
        public void run(){
            synchronized(lock2){
                System.out.println(Thread.currentThread().getName()+" get lock2,and is waiting for lock1.");
                
                try{
                    Thread.sleep(5000);
                }catch(InterruptedException e){
                        e.printStackTrace();
                }
    
                synchronized(lock1){
                    System.out.println(Thread.currentThread().getName()+" has get lock1.");
                }
            }
        }
    }
    
    

    线程间通信

    多个线程在处理同一资源,但是任务却不同

    package xyz.guqing.thread2;
    //资源
    class Resource {
        String name;
        String sex;
    }
    
    // 输入
    class Input implements Runnable{
        Resource resource;
    
        public Input(Resource resource) {
            this.resource = resource;
        }
    
        @Override
        public void run() {
            int x = 0;
            while(true) {
                // 加同步
                synchronized (resource) {
                    if(x == 0){
                        resource.name = "张三";
                        resource.sex = "男";
                    }else {
                        resource.name = "lucy lucy lucy";
                        resource.sex = "女";
                    }
                }
    
                x = (x+1)%2;
            }
        }
    }
    
    //输出
    class Output implements Runnable{
        Resource resource;
    
        public Output(Resource resource) {
            this.resource = resource;
        }
    
        @Override
        public void run() {
            while(true) {
                // 输出也需要加同步
                synchronized (resource) {
                    System.out.println(resource.name + "...." + resource.sex);
                }
            }
        }
    }
    class ResourceDemo{
        public static void main(String[] args) {
            // 创建资源传入Input和Output确保两个类操作的是同一个资源
            Resource resource = new Resource();
    
            //对资源进行输入和输入的两个类
            Input in = new Input(resource);
            Output out = new Output(resource);
    
            //开启线程任务
            Thread t1 = new Thread(in);
            Thread t2 = new Thread(out);
            t1.start();
            t2.start();
    
            /**
             * 出现线程安全问题:
             * lucy lucy lucy....男
             * 张三....女
             * lucy lucy lucy....男
             *
             * 共享数据:
             * resource
             * 有多条语句操作共享数据:
             * resource.name = "张三";
             * resource.sex = "男";
             *
             * 加同步也需要注意:
             * 锁该用什么需要保证两个线程锁是同一个
             * 1. this不行是两个不同的类
             * 2. 创建一个Object不行不唯一
             * 可以两个锁都用Resource.class或者resource
             */
        }
    }
    
    

    虽然改造完了加了锁,但是输出是一片一片的,我们希望赋值一个输出一个,交替出现。

    在资源中加一个flag标记,默认没有值,如果有值flag=true输出,没有flag=true存值,但是如果线程赋值完还持有执行权此时flag=false,那么输出线程此时就需要等待,等着让输出线程输出后在赋值,这就是线程之前的等待唤醒机制

    等待唤醒机制涉及的方法:

    1. wait():让线程处于冻结状态,被wait的线程会被存储到线程池中,失去cpu的执行权
    2. notify():唤醒线程池中的一个线程(任意),让线程具备执行资格
    3. notifyAll():唤醒线程池中的所有线程

    这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法,必须要明确到底操作的是那个锁上的线程,所以wait()notify()在同步里面还需要用锁调用该方法标识出锁,例如resource.wait()代表resource这个锁在调用wait()方法,线程进入到resource锁后resource锁中的线程被wait()方法改变状态,也就是等待和唤醒必须要有所属,不能乱唤醒。

    参考文档:

    public final void wait()
                 throws InterruptedException
    
    

    导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法。换句话说,这个方法的行为就好像简单地执行呼叫wait(0)

    当前的线程必须拥有该对象的监视器。 该线程释放此监视器的所有权,并等待另一个线程通知等待该对象监视器的线程通过调用notify方法或notifyAll方法notifyAll 。 然后线程等待,直到它可以重新获得监视器的所有权并恢复执行。

    像在一个参数版本中,中断和虚假唤醒是可能的,并且该方法应该始终在循环中使用:

    synchronized (obj) {
     while (<condition does not hold>)
         obj.wait();
     ... // Perform action appropriate to condition
    } 
    
    

    该方法只能由作为该对象的监视器的所有者的线程调用。

    为什么操作线程的方法wait notify notifyAll定义在Object类中:

    因为这些方法是监视器的方法,监视器其实就是锁。

    锁可以是任意的对象,任意的对象调用的方法一定定义在Object类中

    线程通信wait、notify例子:

    //资源
    class Resource {
        String name;
        String sex;
        boolean flag = false;
    }
    
    // 输入
    class Input implements Runnable{
        Resource resource;
    
        public Input(Resource resource) {
            this.resource = resource;
        }
    
        @Override
        public void run() {
            int x = 0;
            while(true) {
                // 加同步
                synchronized (resource) {
                    if(resource.flag) {
                        //如果flag为true说明要赋值但是已经有了线程等待
                        try {
                            resource.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    // 赋完值后设置flag和notify
                    if(x == 0){
                        resource.name = "张三";
                        resource.sex = "男";
                    }else {
                        resource.name = "lucy lucy lucy";
                        resource.sex = "女";
                    }
                    // 赋值完置为true
                    resource.flag = true;
                    //唤醒线程,取值
                    resource.notify();
                }
    
                x = (x+1)%2;
            }
        }
    }
    
    //输出
    class Output implements Runnable{
        Resource resource;
    
        public Output(Resource resource) {
            this.resource = resource;
        }
    
        @Override
        public void run() {
            while(true) {
                // 输出也需要加同步
                synchronized (resource) {
                    if(!resource.flag) {
                        //如果flag为false说明要取值但是没有线程就需要等待
                        try {
                            resource.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 取完值,flag=true,notify唤醒线程
                    System.out.println(resource.name + "...." + resource.sex);
                    resource.flag = false;
                    resource.notify();
                }
            }
        }
    }
    class ResourceDemo{
        public static void main(String[] args) {
            // 创建资源传入Input和Output确保两个类操作的是同一个资源
            Resource resource = new Resource();
    
            //对资源进行输入和输入的两个类
            Input in = new Input(resource);
            Output out = new Output(resource);
    
            //开启线程任务
            Thread t1 = new Thread(in);
            Thread t2 = new Thread(out);
            t1.start();
            t2.start();
        }
    }
    
    

    代码优化就是将资源中的属性设置为私有private提供get、set方法提供访问,如此才安全。

    多生产者多消费者问题

    class Resource {
        private int count = 1;
        private String name;
        private boolean flag = false;
    
        public synchronized void set(String name) {
            if(flag) {
                try {
                    // 如果烤鸭没被消费,等待
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name + count;
            count++;
            System.out.println(Thread.currentThread().getName()+"生产了一只烤鸭:"+this.name);
    
            flag = true;
            this.notify();
        }
    
        public synchronized void out() {
            if(!flag) {
                try {
                    // 如果烤鸭没被消费,等待
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"..消费了..."+this.name);
    
            flag = false;
            this.notify();
        }
    }
    
    class Producer implements Runnable {
        private Resource resource;
    
        public Producer(Resource resource) {
            this.resource = resource;
        }
    
        @Override
        public void run() {
            while (true) {
                resource.set("烤鸭");
            }
        }
    }
    
    class Consumer implements Runnable {
        private Resource resource;
    
        public Consumer(Resource resource) {
            this.resource = resource;
        }
    
        @Override
        public void run() {
            while (true) {
                resource.out();
            }
        }
    }
    
    
    class ProducerConsumer {
        public static void main(String[] args) {
            Resource resource = new Resource();
    
            Producer producer = new Producer(resource);
            Consumer consumer = new Consumer(resource);
    
            Thread t0 = new Thread(producer);
            Thread t1 = new Thread(producer);
    
            Thread t2 = new Thread(consumer);
            Thread t3 = new Thread(consumer);
    
            t0.start();
            t1.start();
            t2.start();
            t3.start();
    
            /**
             * 多生产者多消费者问题:
             * Thread-1生产了一只烤鸭:烤鸭13807
             * Thread-3..消费了...烤鸭13807
             * Thread-2..消费了...烤鸭13807
             * Thread-3..消费了...烤鸭13807
             * Thread-2..消费了...烤鸭13807
             * Thread-3..消费了...烤鸭13807
             * Thread-2..消费了...烤鸭13807
             * 经过分析是因为if标记判断时线程等待再次被唤醒后不在判断标记直接执行导致的
             */
        }
    }
    
    

    修改判断为while:

    while(!flag) {
        try {
            // 如果烤鸭没被消费,等待
            this.wait();//线程再此等待被唤醒后会回到while判断
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    

    但是这么已修改运行立马发现线程死锁发生了。while标记判断导致运行过程中所有线程都被wait,没有线程能继续执行,所以死锁了,理想的情况是唤醒生产者线程后下次应该唤醒消费者线程,但是线程唤醒是随机的,所以解决办法可以是使用notifyAll(),把所有线程唤醒

    class Resource {
        private int count = 1;
        private String name;
        private boolean flag = false;
    
        public synchronized void set(String name) {
            while(flag) {
                try {
                    // 如果烤鸭没被消费,等待
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name + count;
            count++;
            System.out.println(Thread.currentThread().getName()+"生产了一只烤鸭:"+this.name);
    
            flag = true;
            this.notifyAll();
        }
    
        public synchronized void out() {
            while(!flag) {
                try {
                    // 如果烤鸭没被消费,等待
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"..消费了..."+this.name);
    
            flag = false;
            this.notifyAll();
        }
    }
    
    class Producer implements Runnable {
        private Resource resource;
    
        public Producer(Resource resource) {
            this.resource = resource;
        }
    
        @Override
        public void run() {
            while (true) {
                resource.set("烤鸭");
            }
        }
    }
    
    class Consumer implements Runnable {
        private Resource resource;
    
        public Consumer(Resource resource) {
            this.resource = resource;
        }
    
        @Override
        public void run() {
            while (true) {
                resource.out();
            }
        }
    }
    
    
    class ProducerConsumer {
        public static void main(String[] args) {
            Resource resource = new Resource();
    
            Producer producer = new Producer(resource);
            Consumer consumer = new Consumer(resource);
    
            Thread t0 = new Thread(producer);
            Thread t1 = new Thread(producer);
    
            Thread t2 = new Thread(consumer);
            Thread t3 = new Thread(consumer);
    
            t0.start();
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    

    使用Lock解决多生产者多消费者问题

    Lock实现提供了比使用synchronized方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition

    锁用于通过多个线程控制对共享资源的访问的工具,通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读锁。

    使用synchronized方法或语句提供对与每个对象相关联的隐式监视器锁的访问,但是强制所有锁获取和释放以块结构的方式发生:当获取多个锁时,他们必须以相反的顺序被释放,并且所有的锁都必须被释放在与他们相同的词汇范围内。

    虽然synchronized方法和语句的范围机制是的使用监视器锁更容易编程,并且有助于避免设计锁的许多常见变成错误,但是有时你需要以更灵活的方式处理锁。例如,用于遍历并发访问的数据结构的一些算法需要使用手动或者链锁定:你获取节点A的锁定,然后获取节点B,然后释放A并获取C,然后释放B并获得D等。所属的实施方式中lock接口通过允许获得并在不同范围释放的锁,并允许获得并以任何顺序释放多个锁是的能够使用这样的技术。

    随着这种增加的灵活性,额外的责任,没有块结构化锁会删除使用synchronized方法和语句发生的锁自动释放,在大多数情况下应该使用Lock

    使用方式:

    Lock lock = new ReentrantLock();
     try {
         lock.lock(); // 获取锁
         // code......
     } finally {//确保异常时释放锁
         lock.unlock(); //释放锁
     }
    
    

    ReentrantLock是接口Lock的实现,是一个可重入互斥Lock具有与使用synchronized方法和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能。

    jdk1.5以后将同步锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,隐式动作变成了显式动作。

    Condition

    public interface Condition
    
    

    Condition因素出Object监视器方法( waitnotifynotifyAll )成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock实现。Lock替换synchronized方法和语句的使用, Condition取代了对象监视器方法的使用。

    条件(也称为条件队列条件变量 )为一个线程暂停执行(“等待”)提供了一种方法,直到另一个线程通知某些状态现在可能为真。 因为访问此共享状态信息发生在不同的线程中,所以它必须被保护,因此某种形式的锁与该条件相关联。 等待条件的关键属性是它原子地释放相关的锁并挂起当前线程,就像Object.wait

    一个Condition实例本质上绑定到一个锁。 要获得特定Condition实例的Condition实例,请使用其newCondition()方法。

    使用Condition后替代方法

    class Resource {
        private int count = 1;
        private String name;
        private boolean flag = false;
    
        // 使用lock锁
        Lock lock = new ReentrantLock();
    
        // 通过已有的锁获取该锁上的监视器对象
        Condition condition = lock.newCondition();
        public void set(String name) {
            lock.lock();
            try {
                while (flag) {
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                this.name = name + count;
                count++;
                System.out.println(Thread.currentThread().getName() + "生产了一只烤鸭:" + this.name);
    
                flag = true;
                condition.signalAll();
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
    
        public void out() {
            lock.lock();
            try {
                while (!flag) {
                    try {
                        // 如果烤鸭没被消费,等待
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "..消费了..." + this.name);
    
                flag = false;
                condition.signalAll();
            }finally {
                lock.unlock();
            }
        }
    }
    
    

    这是基本用法和之前synchronized没什么区别,但是Lock一个锁可以有多组Condition以后,就不用在全唤醒了。

    其他代码全不变,只需要更改Resource

    class Resource {
        private int count = 1;
        private String name;
        private boolean flag = false;
    
        // 使用lock锁
        Lock lock = new ReentrantLock();
    
        // 通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者
        Condition producerCondition = lock.newCondition();
        Condition consumerCondition = lock.newCondition();
    
        public void set(String name) { // 生产者
            lock.lock();
            try {
                while (flag) {
                    try {
                        // 已经有烤鸭了,生产者等待
                        producerCondition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                this.name = name + count;
                count++;
                System.out.println(Thread.currentThread().getName() + "生产了一只烤鸭:" + this.name);
    
                flag = true;
    
                // 生产完唤醒消费者
                consumerCondition.signal();
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
    
        public void out() { // 消费者
            lock.lock();
            try {
                while (!flag) {
                    try {
                        // 消费者等待
                        consumerCondition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "..消费了..." + this.name);
    
                flag = false;
                // 消费完毕,唤醒生产者生产
                producerCondition.signal();
            }finally {
                lock.unlock();
            }
        }
    }
    
    

    总结:

    Lock接口:它的出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成显式锁操作。同时更为灵活,可以一个锁上加上多组监视器。

    lock():获取锁的方法

    unlock():释放锁的方法,通常需要定义在finally代码块中,以确保即使发生异常也能释放锁

    Condition接口: 出现代替了Object中的waitnotify,notifyAll方法。这些监视器方法单独进行了封装,变成Condition监视器对象。可以与任意锁进行组合,其中:

    • await():相当于synchronized中的wait()
    • signal(): 相当于synchronized中的notify()
    • signalAll(): 相当于synchronized中的notifyAll()

    Wati和Sleep的区别

    1. wait可以指定时间也可以不指定。sleep必须指定时间
    2. 在同步中时,对于cpu的执行权和锁的处理不同,wait释放执行权且释放锁,sleep释放cput执行权,不释放锁。

    停止线程

    1. stop方法(已过时)
    2. run方法结束,线程任务结束就会停止,

    如何控制线程任务结束?

    任务中都会有循环结构,只要控制住循环就可以结束任务,控制循环通常就用自定义标记来完成

    但是如果线程处于了冻结状态,无法读取标记该如何结束?

    可以使用public void interrupt()方法将线程从冻结状态强制恢复到运行状态,让线程具备cpu的执行资格。

    但是强制动作会发生InterruptException,需要处理

    public void interrupt()
    中断这个线程。
    
    

    除非当前线程中断自身,这是始终允许的,所以调用此线程的checkAccess方法,这可能会导致抛出SecurityException

    如果该线程阻塞的调用wait()wait(long),或wait(long, int)的方法Object类,或者在join()join(long)join(long, int)sleep(long) ,或sleep(long, int),这个类的方法,那么它的中断状态将被清除,并且将收到一个InterruptedException

    如果该线程在可阻止在I / O操作InterruptibleChannel则信道将被关闭,该线程的中断状态将被设置,并且螺纹将收到一个ClosedByInterruptException

    如果该线程在Selector中被阻塞,则线程的中断状态将被设置,并且它将从选择操作立即返回,可能具有非零值,就像调用了选择器的wakeup方法一样。

    如果以前的条件都不成立,则该线程的中断状态将被设置。

    中断不存在的线程不需要任何效果。

    线程类的其他方法

    setPriority(int num)

    public final int getPriority()
    
    

    返回此线程的优先级

    setDaemon(boolean b)

    public final void setDaemon(boolean on)
    
    

    将此线程标记为daemon线程或用户线程。当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。

    线程启动前必须调用此方法。

    • 参数

    on - 如果 true ,将此线程标记为守护线程

    join()

    public final void join()
                 throws InterruptedException
    
    

    等待这个线程死亡。

    调用此方法的行为方式与调用完全相同

    join (0)

    toString()

    public String toString()
    
    

    返回此线程的字符串表示,包括线程的名称,优先级和线程组。

    • 重写:

    toStringObject

    • 结果

    这个线程的字符串表示形式。

    线程优先级常量:

    static int MAX_PRIORITY

    线程可以拥有的最大优先级。

    static int MIN_PRIORITY

    线程可以拥有的最小优先级。

    static int NORM_PRIORITY

    分配给线程的默认优先级。

    相关文章

      网友评论

          本文标题:多线程基础篇

          本文链接:https://www.haomeiwen.com/subject/pxebrctx.html