首页 > 编程语言 >java 实训第15天 Java 多线程

java 实训第15天 Java 多线程

时间:2024-11-14 10:50:07浏览次数:3  
标签:Java Thread void class 线程 println 15 多线程 public

Java 多线程
一、定义
    java程序本身就是多线程的。
    
二、相关概念
    1、操作系统
    内核实现其他资源的管理和调度。
    2、进程
    一个进程就是操作系统中运行的一个应用程序,每个进程都有进程ID标志唯一性
    3、线程
    线程是计算机进行调度的最小资源,线程运行一般也包含在进程中。    
    
三、java中的线程

    在java中Thread就表示线程   
    注意:
          1. 启动线程不能直接调用run方法,而是调用start启动。
          2. 虽然在Thread中提供了stop终止线程,但是可能会出现不可控问题,不推荐使用;
             应该通过程序逻辑控制线程的终止条件。  
          3. 如果线程运行过程中出现异常也会导致线程异常终止
          
    1、java中创建线程的方法
        1)通过一个类继承Thread类。调用创建对象的start方法启动线程。
        案例代码:
        public class ThreadDemo01 {
            public static void main(String[] args) {
                MyThread myThread = new MyThread();
                //此处不应使用run方法运行线程,应该使用start启动线程
                myThread.start();
            }
        }
        class MyThread extends Thread{
            @Override
            public void run() {
                //当调用start方法时,启动线程运行的逻辑代码
                System.out.println(1);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(2);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(3);

            }
        }
        
        注意:这种创建线程的方法不推荐使用,因为java支持的是单继承,这里会导致
        类的扩展性降低。
    
        2)使用类实现接口Runnable,并实现run方法。将类创建的对象传递给Thread构造。
        案例:
        public class ThreadDemo02 {
            public static void main(String[] args) {
                MyThead1 myThead1 = new MyThead1();
                Thread thread = new Thread(myThead1);

                thread.start();
            }
        }
        class MyThead1 implements Runnable{
            @Override
            public void run() {
                System.out.println(1);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(2);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(3);
            }
        }
        
        3)创建一个类实现JUC(Java并发工具包)下的Callable<V>接口,泛型V表示
        线程运行结束后返回的结果类型。
    
        案例代码:
            public class ThreadDemo04 {
                public static void main(String[] args) throws Exception {
                    int question = 50;

                    AStudent aStudent = new AStudent(question);
                    FutureTask<String> stringFutureTask = new FutureTask<>(aStudent);
                    Thread thread = new Thread(stringFutureTask);
                    thread.start();

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //通过FutureTask获取执行的结果
                    String result = stringFutureTask.get();
                    System.out.println(result);

                }
            }

            class AStudent implements Callable<String> {
                private int question;
                public AStudent(int question){
                    this.question = question;
                }

                @Override
                public String call() throws Exception {
                    while (this.question>0){
                        System.out.println("A同学当前回答的问题是:"+this.question--);
                    }
                    return "A同学回答完毕!";
                }
            }
    
    
    2、线程类Thread相关方法
    构造方法:
        Thread()
        Thread(Runnable target)
    
    常用方法:
        Thread.currentThread()//获取当前线程
        String getName() //获取线程的名字
        void setName(String name) //设置线程的名称
        Long getId()  //获取线程ID
        final void setDaemon(boolean on)  //是否设置为守护线程
        Thread.activeCount()  //当前活动的线程数
        Thread.sleep(tm)  //设置睡眠时间
        
    3、Object类中关于线程状态相关方法
        final native void notify()  //通知
        final native void notifyAll();  //通知所有
        final native void wait(long timeout)  //等待,timeout表示等待超时时间
        final void wait() //等待
        final void stop() //终止线程(不推荐使用)
        
四、线程运行状态
    1、创建 NEW  
        实例化线程对象时(初始)
    2、就绪、运行 RUNNING  
        就绪: 实现化线程对象并调用了start方法(等待分配cpu运行资源)
        运行:线程分配到了运行资源,只有就绪状态的线程才可以进入运行状态
    3、线程终止 TERMINATION
        stop(),线程正常执行结束,运行时出现异常导致线程终止。
    4、阻塞 BLOCKING
        当线程遇到synchronized并且没有抢占到锁资源时
    5、限时等待 WAITTING TIMEOUT
        线程调用了类似于wait(timeout),join()线程加入时会进入限时等待。当等待超时后线程
        会重新进入到就绪状态。
    6、等待 WAITTING
        调用了wait()进入等待,在等待期间会释放锁资源并且不抢占cpu资源。只有被其他线程
        通知notify()时才会被唤醒。
        
    
    wait()线程等待,sleep()睡眠:
        被设置为wait()的线程,不会争抢CPU资源,而且还会释放锁  
        sleep()睡眠,不会争抢CPU,而且不会释放锁

五、多线程并发安全问题
1、定义
    如果多个线程在对同一个资源进行修改操作时,就可能会产生并发安全问题。

2、如何处理并发安全问题
    1-使用同步代码块
        synchronized(对象-用来承载锁的对象){
          //将需要同步的代码包裹起来
        }
        
    注意:    
        用作承载锁的对象必须是所有的线程都能看到的同一个对象
        同步代码块一次只有一个线程在其中执行,会降低效率,所以在设
        计同步代码块的时候,应该在能包裹住会造成线程并发安全问题代码
        的前提下尽量少的包裹其他代码,减少在同步中执行的时间,可以整体的提升效率
        
    同步方法:
        方法中所有代码都需要上锁,可以将当前方法设置为同步方法;在方法前加上synchronized。  
        如果是同步的非静态方法,锁对象选择的就是this。  
        同步的静态方法,锁对象是当前类的class字节码。

    
六、死锁
    锁之间互相等待无法进行下去的状态就称为产生了死锁。  
    如何解决死锁?  

    * 其中锁占用一方强制退出
    * 等待超时机制

    
    案例:打印机和扫描仪相互抢占导致死锁
    public class Thread08 {
        public static void main(String[] args) {
            Thread p1 = new Thread(new Person1());
            Thread p2 = new Thread(new Person2());
            p1.start();
            p2.start();
        }
    }
    //打印机
    class Printers {

    }

    //扫描仪
    class Scanners {

    }

    class Person1 implements Runnable{
        public void run() {
            synchronized (Printers.class) {
                System.out.println("Person1 正在使用打印机...");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (Scanners.class) {
                    System.out.println("Person1 正在使用扫描仪...");
                }
            }
        }
    }

    class Person2 implements Runnable{
        public void run() {
            synchronized (Scanners.class) {
                System.out.println("Person2 正在使用扫描仪...");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for(int i=0; i<10; i++) {
                    if(i==6) {
                        throw new RuntimeException("no");
                    }
                }
                synchronized (Printers.class) {
                    System.out.println("Person2 正在使用打印机...");
                }
            }
        }
    }
    
七、关于线程的其他细节

1、守护线程
    线程可以分为前台线程和后台线程。  
    如果一个线程被设置为守护线程后,当前台没有线程时,哪怕没有执行完也会自动退出。  
    设置守护线程的方式: 在运行线程前调用setDaemon方法

    方法:
        void setDaemon(boolean on)    //默认为false,true表示设置为守护(后台)线程
        
    注意:设置守护线程的方法,必须要在调用start方法之前执行。
    
    案例代码:
        public class ThreadDemo07 {
            public static void main(String[] args) {
                Person person = new Person();
                Thread thread = new Thread(person);
                thread.setDaemon(true);
                thread.start();

                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"线程已经执行结束");
            }
        }

        class Person implements Runnable{
            @Override
            public void run() {
                for (int i=1; i<=10; i++){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("当前执行的编号为:"+i);
                }
            }
        }
        
2、线程加入
    当正在执行的线程执行到了join(),就会将执行权让给调用此方法的线程,自己转入冻结状态,
    直到调用此方法的线程结束,当前线程才会从冻结状态醒来,接着执行。  
    
    方法:
         final void join()
    
    
    案例代码:炒菜
        public class ThreadDemo08 {
            public static void main(String[] args) throws Exception {

                System.out.println("准备炒菜~~");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("菜没了~~");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                Buy buy = new Buy();
                Thread thread = new Thread(buy);
                thread.start();
                thread.join();

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("继续炒菜~");
            }
        }

        class Buy implements Runnable{

            @Override
            public void run() {
                System.out.println("准备出门~~~");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("买菜~~~");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("回家~~~");
            }
        }

3、线程退让:
    让当前线程暂停执行,可以执行其他线程  
    Thread.yield();
    

4、线程执行的优先级
    修改线程的优先级 1 - 10,越大优先级越高,但是这仅仅是理论上的优先级,真正执行时,
    谁执行谁不执行,仍然是概率的,只是概率高低有所不同。通常 1、5、10表现的效果比较明显。
    线程默认优先级都是5。

    方法:
        void setPriority(int newPriority) //优先级:1~10
        final int getPriority() //获取优先级

扩展:java的内存模型-》多线程(并发编程)


八、volatile关键字
1、作用
    关键字volatile的主要作用是使变量在多个线程间可见。
2、特点
    保证可见性:
        也就是说某个线程修改了共享变量,线程在修改变量时不会把值缓存在寄存器或者其他地方,而
    是会把值刷新回主内存。当其它线程读取该共享变量时,会从主内存重新获取变量最新值,而不是
    从当前线程的工作内存中获取。
    
    保证有序性(禁止指令重排序):
        所谓指令重排序:为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序。

3、volatile与synchronized的区别
    -关键字volatile是线程同步的轻量级实现,所以volatile的性能略胜于synchronized,
        并且volatile只能修饰变量,而synchronized可以修饰方法、代码块等;  
    -多线程访问volatile不会发生阻塞,而synchronized会出现阻塞;  
    -volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,
        也可以间接保证可见性,因为它会将私有内存和共有内存中的数据做同步;  
    -关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的
        是多个线程之间访问资源的同步性。
        
九、ThreadPoolExecutor
1、构造方法
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler);

参数介绍:  
int corePoolSize:核心线程大小,线程池最小的线程数量。  
int maximumPoolSize:最大线程大小,线程池的最大的线程数量  
long keepAliveTime:超过corePoolSize的线程在不活动的状态下存活的最大时间。  
TimeUnit unit:keepAliveTime的时间单位。  
BlockingQueue<Runnable> workQueue:任务队列。  
ThreadFactory threadFactory:线程池工厂,用来创建线程。  
RejectedExecutionHandler handler:拒绝策略。

2、执行流程
(1)当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。  
(2)当线程池超过corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行。  
(3)当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务。  
(4)当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理。  
(5)当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,释放空闲线程。  
(6)当设置allowCoreThreadTimeOut(true)时,该参数默认false,线程池中corePoolSize线程空闲时间达到keepAliveTime也将被关闭。

3、拒绝策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,这是默认的一种方式。  
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。  

 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务  
ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。

4、线程池设置
    **cpu密集型**  
        cpu密集的意思是该任务需要大量的运算,而没有阻塞,cpu一直全速运行。
        cpu密集型任务配置尽可能少的线程数量:以保证每个cpu高效的运行一个线程。  
        通常设置方式:    
        与cpu核心数相同的线程数或者加1  
    **io密集型**  
        io密集型,即该任务需要大量的io,即大量的阻塞。
        在单线程上运行io密集型的任务会导致浪费大量的cpu运算能力浪费在等待。所以在io密集型任务中使用多线程可以大大的加速程序运行,这种加速主要就是利用了被浪费掉的阻塞时间。  
        通常设置方式:
        cpu核数 * 2个线程的线程池。 

**获取核心数**
    Runtime.getRuntime().availableProcessors();
    
5、使用Executors创建线程池:
    1. Executors.newCachedThreadPool  
       是一个根据需要创建新线程的线程池,当一个任务提交时,corePoolSize为0不创建核心线程,SynchronousQueue是一个不存储元素的队列,可以理解为队里永远是满的,因此最终会创建非核心线程来执行任务。  
    2. Executors.newSingleThreadExecutor  
       单线程线程池,只有一个核心线程,用唯一的一个共用线程执行任务,保证所有任务按指定顺序执行  
    3. Executors.newFixedThreadPool  
       定长线程池,核心线程数和最大线程数由用户传入,可以设置线程的最大并发数,超出在队列等待  
    4. Executors.newScheduledThreadPool  
       定长线程池,核心线程数由用户传入,支持定时和周期任务执行  
   
总结:
FixedThreadPool和SingleThreadExecutor 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积
大量的请求,从而引起OOM异常

CachedThreadPool 和newScheduledThreadPool允许创建的线程数为Integer.MAX_VALUE,可能会创
建大量的线程,从而引起OOM异常

标签:Java,Thread,void,class,线程,println,15,多线程,public
From: https://blog.csdn.net/binishuaio/article/details/143702574

相关文章