• Java第三阶段学习(七、线程池、多线程)


    一、线程池

    1.概念:

    线程池,其实就是一个容纳多个线程的容器,其中的线程可以重复使用,省去了频繁创建线程对象的过程,无需反复创建线程而消耗过多资源,是JDK1.5以后出现的。

     2.使用线程池的方式----Runnable接口

    线程池是由线程池工厂创建的,再调用线程池中的方法调用线程再通过线程去执行任务方法

    构造代码:2.1 Executors:线程池创建工厂类

         2.2 ExecutorService:线程池类      Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行

           2.3 Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

    创建线程池的步骤:

    1.创建线程池对象

    2.创建Runnable接口子类对象

    3.提交Runnable接口子类对象

    4.关闭线程池

    Runnable接口实现类:

    package com.oracle.Demo01;
    
    public class MyRunnable implements Runnable{
    //Runnable接口实现类,并且有线程任务
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
            
        }
    
    }

    线程池代码:

    package com.oracle.Demo01;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    //Executor创建线程池方法
    public class Demo01 {
    
        public static void main(String[] args) {
            // 1.通过线程池工厂  获得线程池对象
            ExecutorService es=Executors.newFixedThreadPool(5);
            //2.获得线程对象并提交
            es.submit(new MyRunnable());
            es.submit(new MyRunnable());
            //销毁线程池:执行完毕后,销毁线程池,线程也就进入死亡状态了
            //若不销毁,线程运行完毕又回到线程池,回到新建状态,没有进入死亡状态
            es.shutdown();
        }
    
    }

    3.使用线程池方法-----Callable接口

    构造代码:3.1 Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。

         3.2  ExecutorService:线程池类

              <T> Future<T> submit(Callable<T> task):获取线程池中的某一个线程对象,并执行线程中的call()方法

         3.3  Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

    使用线程池中线程的步骤:

    1.创建线程池对象

    2.创建Callable接口子类对象

    3.提交Callable接口子类对象

    4.摧毁线程池

    Callable接口实现类代码:

    package com.oracle.Demo01;
    
    import java.util.concurrent.Callable;
    //创建Callable接口的子类,并且有线程任务
    public class MyCcallable implements Callable<String>{
    
        @Override
        public String call() throws Exception {
            System.out.println("Call方法");
            return "abc";
        }
    
    }

    线程池运行代码:

    package com.oracle.Demo01;
    
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    //使用Callable方法使用线程池的线程
    public class Demo02 {
    
        public static void main(String[] args) throws InterruptedException, ExecutionException {
            // TODO Auto-generated method stub
            ExecutorService es=Executors.newFixedThreadPool(3);
            Future<String> f=es.submit(new MyCcallable());
            String s=f.get();
            System.out.println(s);
        }
    
    }

    二、多线程

    1.线程安全:

    如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的就是线程安全的。

    线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全

    比如电脑端,手机端,跟售票窗口同时卖一张票,因为是多线程所以是同时进行的,当一个线程买到票后,可能另外两个线程也进行到一半了,但是因为已经没有票了,所以就会出现异常错误,比如重复的票号或异常的票号,这就是出现了线程安全问题。

    2.线程同步:

    Java中提供了线程同步机制,来解决线程安全的问题。

    线程同步的方式有两种:

    1.同步代码块

    2.同步方法

    2.1同步代码块:

    在代码块声明上,加上synchronized

    synchronized (锁对象) {
        可能会产生线程安全问题的代码
    }

    同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

    多线程代码:

    package com.oracle.Demo02;
    
    public class Demo {
    
        public static void main(String[] args) {
            Tickets t=new Tickets();
            Thread t0=new Thread(t);
            Thread t1=new Thread(t);
            Thread t2=new Thread(t);
            t0.start();
            t1.start();
            t2.start();
    
        }
    
    }

    使用同步代码块:

    package com.oracle.Demo02;
    //卖票任务
    //synchronized(任意对象){
    //    线程要操作的共享数据
    //}
    //同步代码块解决线程不安全的原理:
    //    线程遇到同步代码块时,线程会先判断你的同步锁有没有,如果有,就获取同步锁,进入同步去执行代码,执行完毕后,
            //再把锁还回去。
    //    在同步中,线程进行了休眠,此时另一个线程会执行,遇到同步代码块的时候,会判断同步锁有没有,如果没有,
            //没有获取到同步锁的线程,不能进入同步去执行,被阻挡在同步代码块外面
    public class Tickets implements Runnable{
        private int ticket=100;
        //private Object obj=new Object();
        public void run() {
            while(true){
                synchronized (this) {   //这里的this  可以为任意对象,替换成上面的obj也可以,为自身对象this也可以
                if(ticket>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"出售第"+(ticket--)+"张票");
                    }
                }
            }
            
        }
        
    } 

    2.2 同步方法

    在方法声明上,加上synchronized

    public synchronized void method(){
           可能会产生线程安全问题的代码
    }

    同步方法中的锁对象是 this

    多线程代码:

    package com.oracle.Demo03;
    
    public class Demo {
    
        public static void main(String[] args) {
            Tickets t=new Tickets();
            Thread t0=new Thread(t);
            Thread t1=new Thread(t);
            Thread t2=new Thread(t);
            t0.start();
            t1.start();
            t2.start();
    
        }
    
    }

    使用同步代码块:

    package com.oracle.Demo03;
    
    public class Tickets implements Runnable{
        private int ticket=100;
        public void run() {
            while(true){
                    method();
                }
            }
        //同步方法:解决线程不安全问题
        //问题1:同步方法有锁吗?有,锁是本类引用,this关键字
        //问题2:如果你的同步方法是静态的,还有同步锁吗?是this吗?
        //有,不是this,是本类自己,Tickets.class
        
        public synchronized void method(){
            if(ticket>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"出售第"+(ticket--)+"张票");
                }else{
                    return;
                }
        }
        
    }

     静态同步方法: 在方法声明上加上static synchronized

    public static synchronized void method(){
    可能会产生线程安全问题的代码
    }

     使用同步方法:

    package com.oracle.Demo03;
    
    public class Tickets implements Runnable{
        private int ticket=100;
        public void run() {
            while(true){
                    method();
                }
            }
        //同步方法:解决线程不安全问题
        //问题1:同步方法有锁吗?有,锁是本类引用,this关键字
        //问题2:如果你的同步方法是静态的,还有同步锁吗?是this吗?
        //有,不是this,是本类自己,Tickets.class
        
        public synchronized void method(){
            if(ticket>0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"出售第"+(ticket--)+"张票");
                }else{
                    return;
                }
        }
        
    }

     

    2.3死锁

    同步锁使用的弊端:当线程任务中出现了多个同步锁(多个锁)时如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁

    synchronzied(A锁){
        synchronized(B锁){
             
    }
    }

    所以,不能在一个同步中再嵌套另一个同步

    2.4  Lock接口

    Lock 实现类提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作

    常用方法:

    Lock提供了一个更加面对对象的锁,在该锁中提供了更多的操作锁的功能

    package com.oracle.Demo04;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    //Lock接口
    public class Tickets implements Runnable{
        private int ticket=100;
        private Lock lock=new ReentrantLock();   //创建一个锁的对象
        public void run() {
            while(true){
                lock.lock();   //提供锁
                if(ticket>0){
                    try {
                        Thread.sleep(10);
                        System.out.println(Thread.currentThread().getName()+"出售第"+(ticket--)+"张票");
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }finally{
                        lock.unlock();   //执行完毕释放锁
                    }
                }
            }
            
        }
        
    }

     2.5等待唤醒机制

    线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制

    等待唤醒机制的方法

      wait():等待。将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。

      notify():唤醒。唤醒线程池中被wait()的线程,一次只能唤醒一个,而且是随机的任意线程。

      notifyAll():唤醒全部。可以将线程池中所有被wait()的线程唤醒。

    所谓唤醒的意思就是让线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,

    这样才可以明确出这些方法操作的到底是哪个锁上的线程

    等待唤醒示例图:

    自定义实体类:

    package com.oracle.Demo06;
    //等待唤醒机制 自定义实体类
    public class Resource {
        public String name;
        public String sex;
        public boolean flag=false;
    }

     线程类:

    package com.oracle.Demo06;
    
    public class Input implements Runnable{
        private Resource r;
        public Input(Resource r){
            this.r=r;
        }
        public void run() {
            int i=0;
            while(true){
                synchronized(r){
                if(r.flag){
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    if(i%2==0){
                        r.name="张三";
                        r.sex="男";
                    }else{
                        r.name="lisi";
                        r.sex="nv";
                    }
                    i++;
                 }
                    r.flag=true;
                    r.notify();
                }
            }
            
        }
    
    }
    package com.oracle.Demo06;
    
    public class Output implements Runnable{
        private Resource r;
        public Output(Resource r){
            this.r=r;
        }
        //flag:当flag为true的时候,代表赋值完成
                //为false,获取值完成
        public void run() {
            while(true){
                synchronized (r) {
                    if(!r.flag){
                        try {
                            r.wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        
                    }
                    System.out.println(r.name+"..."+r.sex);
                    r.flag=false;
                    r.notify();
                }
            }
        }
    
    }

    测试类:

    package com.oracle.Demo06;
    
    public class Output implements Runnable{
        private Resource r;
        public Output(Resource r){
            this.r=r;
        }
        //flag:当flag为true的时候,代表赋值完成
                //为false,获取值完成
        public void run() {
            while(true){
                synchronized (r) {
                    if(!r.flag){
                        try {
                            r.wait();
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        
                    }
                    System.out.println(r.name+"..."+r.sex);
                    r.flag=false;
                    r.notify();
                }
            }
        }
    
    }
  • 相关阅读:
    环境部署:Linux下查看JDK安装路径
    环境部署(一):Linux下安装JDK
    linux常见命令
    设计用例
    测试用例的优先级
    快速幂和快速乘
    docker-compose安装
    jmeter通过命令生成自动测试报告
    jmeter环境变量配置
    java将毫秒转化为当前时间
  • 原文地址:https://www.cnblogs.com/0328dongbin/p/9272744.html
Copyright © 2020-2023  润新知