• 多线程


    1 多线程
        1.1 什么是进程?什么是线程?
            进程是一个应用程序。(一个进程是一个软件)
            线程是一个进程中的执行场景/执行单元。
            一个进程可以启动多个线程。
            
        1.2 对于java程序来说,当在DOS命令窗口中输入:
            java HelloWorld 回车之后。
            回先启动JVM,而JVM就是一个进程。
            JVM在启动一个主线程调用main方法。
            同时在启动一个垃圾回收线程负责看护,回收垃圾。
            最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。
            
        1.3 进程和线程是什么关系?举个例子
            阿里巴巴:进程
                马云:阿里巴巴的一个线程。
                童文红:阿里巴巴的一个线程。
                
            京东:进程
                强东:京东的一个线程。
                妹妹:京东的一个线程。
                
            进程可以看做是现实生当中的公司。
            线程可以看做是公司当中的某个员工。
            
            注意:
                进程A和进程B的内存独立不共享。(阿里巴巴和京东资源不会共享的)
                    魔兽游戏是一个进程
                    酷狗音乐是一个进程
                    这两个进程是独立的,不共享资源。
                    
                线程A和线程B呢?
                    在java语言中:线程A和线程B,堆内存和方法区内存共享。
                    但是栈内存独立,一个线程一个栈。
                    
                假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
                
            火车站,可以看做是一个进程。
            火车站中的每一个售票窗口可以看做是一个线程。
            我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。
            所以多线程并发可以提高效率。
            
            java中之所以有多线程机制,目的就是为了提高程序的处理效率。
            
        1.4 思考一个问题:
            使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。
            main方法结束之时主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。
            
        1.5 分析一个问题:对于单核的CPU来说,真的可以做到真正的多线程并发吗?
            对于多核的CPU电脑来说,真正的多线程并发是没问题的。
                4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
            
            什么是真正的多线程并发?
                t1线程执行t1的。
                t2线程执行t2的。
                t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。
                
            单核的CPU表示只有一个大脑:
                不能够做到真正多线程并发,但是可以做到给人一种“多线程”并发的感觉。
                对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,
                给人的感觉是:多个事情同时在做。
                    线程A:播放音乐。
                    线程B:运行魔兽游戏。
                    线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,给我们的感觉是同时并发的。
                
            电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,人类的眼睛产生了错觉,感觉是动画的。
            这说明人类的反应速度很慢,就像一根钢针扎到手上,到最终感觉到疼,这个过程是需要“很长的”时间的,在这个期间计算机可以进行亿万次的循环。
            所以计算机的执行速度很快。
            
        1.6 java语言中,实现线程有两种方式,哪两种方式呢?
            第一种方式:编写一个类,直接机场java.lang.Thread,重写run方法。
                // 定义线程类
                public class MyThread exetends Thread{
                    public void run(){
                    
                    }
                }
                
                // 创建线程对象
                MyThread t = new MyThread();
                // 启动线程
                t.start();
                
            第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。
                // 定义一个可运行的类
                public class MyRunnable implements Runnable{
                    public void run(){
                        
                    }
                }
                
                // 创建一个线程对象
                Thread t = new Thread(new MyRunnable());
                // 启动线程
                t.start();
                
            注意:第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其他的类,更灵活。
            
        1.7 关于线程对象的生命周期
            新建状态
            就绪状态
            运行状态
            阻塞状态
            死亡状态
            
        1.8 (这部分内容属于了解)关于线程的调度
            1.1 常见的线程调度模型有哪些?
                抢占式调度模型:
                    哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
                    java采用的就是抢占式调度模型。
                    
                均分是调度模型:        
                    平均分配CPU时间片,每个线程占有的CPU时间片长度一样,平均分配,一切平等。
                    有一些编程语言,线程调度模型才用的是这种方式。
                    
            1.2 java中提供了哪些方法是和线程调度有关系的呢?
                实例方法
                void setPriority(int newPriority) 设置线程的优先级
                int getPriority() 获取线程的优先级
                最低优先级1
                默认优先级是5
                最高优先级是10
                优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
                
                静态方法
                    static void yield() 让位方法。
                    暂停当前正在执行的线程对象,并执行其他线程。
                    yield()方法不是祖册方法。让当前线程让位,让给其它线程使用。
                    yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
                
                实例方法:
                    void join()
                    合并线程
                    class MyThread1 extends Thread{
                        public void doSome(){
                            MyThread2 t = new MyThread2();
                            t.join();// 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。
                        }
                    }
                    
                    calss MyThread2 extends Thread{
                    
                    }
                    
    2 关于多线程并发环境下,数据的安全问题。
        
        2.1 为什么这个是重点?
            以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。
            这些代码我们都不需要编写。
            最重要的事:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点:*****)
            
        2.2 什么时候数据在多线程并发的环境下会存在安全问题呢?
            三个条件:
                条件1:多线程并发。
                条件2:有共享数据。
                条件3:共享数据有修改的行为。
                
            满足以上3个条件之后,就会存在线程安全问题。
            
        2.3 怎么解决线程安全问题呢?
            当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
                线程排队执行。(不能并发)
                用排队执行解决线程安全问题。
                这种机制被称为:线程同步机制。
                
                专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
                
            怎么解决线程安全问题呀?
                使用“线程同步机制”。
                
            线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,
            只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事。
            
        2.4 说到线程同步这块,涉及到这两个专业术语:
            异步编程模型:
                线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型(效率较高)
                
                异步就是并发。
                
            同步编程模型:
                线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,
                这就是同步编程模型。效率较低,线程排队执行。
                
                同步就是排队。
                
    3 Java中有三大变量:[重要的内容]
        实例变量: 在堆中
        
        静态变量:在方法区
        
        局部变量:在栈中
        
        以上三大变量中:
            局部变量永远都不会存在线程安全问题。
            因为局部变量不共享。(一个线程一个栈)
            局部变量在栈中,所以局部变量永远都不会共享。
        
        实例变量在堆中,堆只有一个。
        静态变量在方法区中,方法区只有一个。
        堆和方法区都是多线程共享的,所以可能存在线程安全问题。
        
        局部变量 + 常量:不会有线程安全问题。
        成员变量:可能会有线程安全问题。
        
    4 如果使用局部变量的话:
        建议使用:StringBuilder
        因为局部变量不存在线程安全问题。选择StringBuilder。
        StringBuffer效率比较低。
        
        ArrayList是非线程安全的。
        Vector是线程安全的。
        HashMap HashSet是非线程安全的。
        HashTable是线程安全的。
        
    5 总结
        synchronized有两种写法:
        
            第一种:同步代码块
                灵活
                synchronized(线程共享对象){
                    同步代码块;
                }    
                
            第二种:在实例方法上使用synchronized
                表示共享对象一定是this
                并且同步代码块是整个方法体。
                
            第三种:在静态方法上使用synchronized
                表示找类锁。
                类锁永远只有1把。
                就算创建了100个对象,那类锁也只有一把。
                
        对象锁:1个对象1把锁,100个对象100把锁。
        类锁:100个对象,也可能只是一把锁。            
     
    6 聊一聊,我们以后开发中应该子呢么解决线程安全问题?
        是一上来就选择线程同步吗?synchronized
            不是,synchronized会让程序的执行效率降低,用户体验不好。
            系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。
            
        第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
        
        第二种方案:如果必须是实例变量,那么可以考虑常见多个对象,这样实例变量内存就不共享了。(一个线程对应一个对象,100个线程对应100个对象。
            对象不共享,就没有数据安全问题了。)
            
        第三种:如果不能使用局部变量,对象也不能创建多个,这个时候只能选择synchronized了。线程同步机制。
        
    7 线程这块还有哪些内容呢?列举一下:
     
        7.1 守护线程
        
            java语言中线程分为两大类:
                一类是:用户线程。
                一类是:守护线程(后台线程)
                其中具有代表行的就是:垃圾回收线程(守护线程)。
                守护线程的特点:
                    一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
                    
                注意:主线程main方法是一个用户线程。
                
                守护线程用在什么地方呢?
                    每天00:00点的时候系统数据自动备份。
                    这个需要使用到定时器,并且我们可以将定时器设置为守护线程。
                    一直在那里看着,每到00:00的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。
                
        7.2 定时器
            定时器的作用:
                间隔特定的时间,执行特定的程序。
                
                每周要进行银行账户的总账操作。
                
                每天要进行数据的备份操作。
                
                在实际的开发汇总,没隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种睡眠方式实现:
                    可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间醒来,执行任务,这种方式是最原始的定时器。(比较low)
                    
                    在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。
                    不过,这种方式在目前的开发中很少用,因为现在有很多高的框架都是支持定时任务的。
                    
                    在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单地配置,
                    就可以完成定时器的任务。
            
        7.3 实现线程的第三种方式:FutureTask方式,实现Callable接口。(JDK8新特性。)
            这种方式实现的线程可以获取线程的返回值。
            之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。
            
            思考:
                系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么拿到这个执行结果呢?
                    使用第三种方式:实现Callable接口方式。
            
        7.4 关于Object类中的wait和notify方法。(生产者和消费者模式。)
        
            第一:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。
                wait方法和notify方法不是通过线程对象调用的,
                不是这样的:t.wait(),也不是这样的:t.notify .. 不对。
                
            第二:wait()方法作用?
                Object o = new Object();
                o.wait();
                表示:让正在o对象上活的线程进入等待状态,无期限等待,直到被唤醒为止。
                o.wait();方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态。
            
            第三:notify()方法作用?
                Object o = new Object();
                o.nodify();
                
                表示:
                    唤醒正在o对象上等待的线程。
                    
                还有一个nodifyAll()方法:
                    这个方法是唤醒o对象上处于等待的所有线程。


    一个线程一个栈:

    案例1的内存图:

    案例2:线程的run内存图

    案例2:线程的start内存图:

    案例1 猜猜一下代码中有几个线程???:

    package com.javaSe.Thread;
    /*
    大家分析以下程序,有几个线程?除了垃圾回收线程之外。有几个线程?
        1个线程(因为程序只有一个栈)
        
        main begin
        m1 begin
        m2 begin
        m3 execute
        m2 end
        m1 end
        main end
        一个栈中,自上而下的顺序依次逐行执行。
    */
    public class ThreadTest01 {
        public static void main(String[] args) {
            System.out.println("main begin");
            m1();
            System.out.println("main end");
        }
        
        public static void m1(){
            System.out.println("m1 begin");
            m2();
            System.out.println("m1 end");
        }
        
        public static void m2(){
            System.out.println("m2 begin");
            m3();
            System.out.println("m2 end");
        }
        
        public static void m3(){
            System.out.println("m3 execute");
        }
    }

    案例2 Thread线程:

    package com.javaSe.Thread;
    /*
    实现线程的第一种方式:
        编写一个类,直接继承java.lang.Thread,重写run方法。
        
        怎么创建线程对象?new就行了
        怎么启动线程呢?调用线程对象的start()方法
        
    注意:
        亘古不变的道理:
            方法体当中的代码永远都是自上而下的顺序依次逐行执行的。
            
    以下程序的输出结果有这样的特点:
        有先有后。
        有多有少。
        这是怎么回事呢????
    */
    public class ThreadTest02 {
        public static void main(String[] args) {
            // 这里是main方法,这里的代码属于主线程,在主栈中运行。
            // 新建一个分支线程对象。
            MyThread t = new MyThread();
            
            // 启动线程
            // t.run();// 这样子做的话不会启动线程,不会分配新的分支栈。不能并发,也就是说他还是一个单线程,run方法中的程序走完了,main方法的程序才会继续执行。
            
            // 启动线程
            // start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
            // 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了。
            // 启动成功的线程会自动调用run()方法,并且run方法在分支栈的站底部(压栈)。
            // run方法在分支栈的站底部,main方法在主栈的栈底部。run和main是平级的。
            t.start();
            
            // 这里的代码还是运行在主线程中
            for (int i = 0; i < 100; i++) {
                System.out.println("主线程--->" + i);
            }
        }
    }
    
    
    class MyThread extends Thread{
        @Override
        public void run() {
            // 编写程序,这段程序运行在分支线程中(分支栈)。
            for(int i = 0; i < 1000; i++){
                System.out.println("分支线程--->" + i);
            }
        }
    }

    案例3 实现Runnable线程接口:

    package com.javaSe.Thread;
    /*
    实现线程的第二种方式:编写一个类实现java.lang.Runnable接口。
    */
    public class ThreadTest03 {
        public static void main(String[] args) {
           /* // 创建一个可运行对象
            MyRunnable r = new MyRunnable();
            // 将可运行的对象封装成一个线程对象
            Thread t = new Thread(r);*/
           
           // 将上面两行代码合并成一行
            Thread t = new Thread(new MyRunnable());
           
            // 启动线程
            t.start();
        
            // 这里的代码还是运行在主线程中
            for (int i = 0; i < 100; i++) {
                System.out.println("主线程--->" + i);
            }
        }
    }
    
    
    // 这并不是一个线程类,是一个可运行的类,他还不是一个线程。
    class MyRunnable implements Runnable{
        
        @Override
        public void run() {
            // 编写程序,这段程序运行在分支线程中(分支栈)。
            for(int i = 0; i < 1000; i++){
                System.out.println("分支线程--->" + i);
            }
        }
    }

    案例4 利用匿名内部类实现线程:

    package com.javaSe.Thread;
    /*
    采用匿名内部类可以吗?
    */
    public class ThreadTest04 {
        public static void main(String[] args) {
            // 创建线程对象,采用匿名内部类方式。
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        System.out.println("分支线程---> " + i);
                    }
                }
            });
            t.start();
        
            for (int i = 0; i < 100; i++) {
                System.out.println("主线程---> " + i);
            }
        }
    }

    案例5获取线程的名字:

    package com.javaSe.Thread;
    /*
    1 怎么获取当前线程对象
    2 获取线程对象的名字
        String name = 线程对象.getName();
    3 修改线程对象的名字
        线程对象.setName("tttt");
    4 当线程没有设置名字的时候,默认的名字有什么规律?(了解一下)
        Thread-0
        Thread-1
        Thread-2
        Thread-3
        ...
    */
    public class ThreadTest05 {
        public static void main(String[] args) {
            // 创建线程对象
            MyThread2 mt = new MyThread2();
            
            // 设置线程的名字
            mt.setName("t1");
            
            // 获取线程的名字
            String mtName = mt.getName(); // 如果没有设置线程的名字  默认为:Thread-0
            System.out.println(mtName);
        
            // 在新建一个线程
            MyThread2 mt2 = new MyThread2();
            mt2.setName("t2");
            String mt2Name = mt2.getName();
            System.out.println(mt2Name); // Thread-1
            
            // 启动线程。
            mt.start();
        }
    }
    
    
    class MyThread2 extends Thread {
        public void run(){
            for (int i = 0; i < 100; i++) {
                System.out.println("分支线程--->" + i);
            }
        }
    }
     
    案例6:关于线程的sleep方法
    package com.javaSe.Thread;
    /*
    关于线程的sleep方法:
        static void sleep(long millis)
        1 静态方法
        2 参数是毫秒
        3 作用:让当前线程进入休眠装填,进入阻塞状态,放弃占有的CPU时间片,让给其它线程使用。
            这行代码出现在A线程中,A线程就进行休眠。
            这行代码出现在B线程中,B线程就进行休眠。
        4 Thread.sleep()方法,可以做到这种效果:
            间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
    */
    public class ThreadTest06 {
        public static void main(String[] args) {
            /*try {
                // 让当前线程进入休眠,睡眠5秒钟。
                // 当前线程是主线程。
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        
            System.out.println("Hello World!");*/
        
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    // 睡眠1秒
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    案例7:关于sleep线程方法的面试题:

    package com.javaSe.Thread;
    /*
    关于线程的sleep方法:
        static void sleep(long millis)
        1 静态方法
        2 参数是毫秒
        3 作用:让当前线程进入休眠装填,进入阻塞状态,放弃占有的CPU时间片,让给其它线程使用。
            这行代码出现在A线程中,A线程就进行休眠。
            这行代码出现在B线程中,B线程就进行休眠。
        4 Thread.sleep()方法,可以做到这种效果:
            间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
    */
    public class ThreadTest06 {
        public static void main(String[] args) {
            /*try {
                // 让当前线程进入休眠,睡眠5秒钟。
                // 当前线程是主线程。
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        
            System.out.println("Hello World!");*/
        
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    // 睡眠1秒
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
     
    案例8 如何让正在睡眠的线程执行:
    package com.javaSe.Thread;
    /*
    sleep睡眠太久了,如果希望半道上醒来,你应该怎么办?也就是说怎么唤醒正在睡眠的线程。
        注意:这个不是终断线程的执行,是终止线程的睡眠。
    */
    public class TheadTest08 {
        public static void main(String[] args) {
            // 创建线程对象
            Thread t = new Thread(new MyRunnable2());
            // 更改线程名称
            t.setName("t");
            // 启动线程
            t.start();
            
            // 希望5秒之后,t线程醒来(5秒之后主线程手里的活干完了)
            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                // 打印异常信息
                e.printStackTrace();
            }
            
            // 终断t线程的睡眠(这段终断睡眠的方法依靠了java的异常处理机制。)
            t.interrupt();// 干扰,一盆冷水过去!
        }
    }
    
    
    class MyRunnable2 implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "---> begin");
            try {
                // 睡眠1年,1年之后才可以醒来。
                // 这里只能try/catch不能throws是因为什么呢?
                // 因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常。
                Thread.sleep(1000 * 60 * 60 * 24 * 365);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 1年之后才会执行这里。
            System.out.println(Thread.currentThread().getName() + "---> end");
            
            // 调用doOther
            /*try {
                doOther();
            } catch (Exception e) {
                e.printStackTrace();
            }*/
        }
        
        // 这个是可以的,你可以在其他方法进行throws,但是在run()方法中还是只可以进行try/catch】
        /*public void doOther() throws Exception{
        
        }*/
    }

    案例9 怎么强行终止一个线程(此方法现已不可以用,下面的案例才是最完美的):

    package com.javaSe.Thread;
    /*
    在java中怎么强行终止一个线程的执行。
        这种方式存在很大的缺点:容易丢失数据,因为这种方式是直接将线程杀死了。
        线程没有保存的数据将会丢失。不建议使用。
    */
    public class ThreadTest09 {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable3());
            t.setName("t");
            t.start();
            
            // 模拟五秒钟
            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            // 5秒之后强行终止t线程
            t.stop();// 已过时(不建议使用)
        }
    }
    
    
    class MyRunnable3 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    案例10 如何合理的终止一个线程:

    package com.javaSe.Thread;
    /*
    怎么合理的终止一个线程的执行,这种方式很常用的:
    */
    public class ThreadTest10 {
        public static void main(String[] args) {
            MyRunnable4 r = new MyRunnable4();
            Thread t = new Thread(r);
            t.setName("t");
            t.start();
            
            // 模拟5秒
            try {
                t.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 终止线程
            // 你想要什么时候终止t的执行,那么你把标记修改成false,就结束了。
            r.run = false;
        }
    }
    
    
    class MyRunnable4 implements Runnable{
        
        // 打一个布尔标记。
        boolean run = true;
        
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                if (run){
                    System.out.println(Thread.currentThread().getName() + "--->" + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    // return就结束了,你在结束之前还有什么没有保存的
                    // 在这里进行保存就可以了 save...
                    
                    // 终止当前线程
                    return;
                }
            }
        }
    }

    案例11 如何获取线程的优先级:

    package com.javaSe.Thread;
    
    
    public class ThreadTest11 {
        public static void main(String[] args) {
            
            /*System.out.println("最高优先级 = " + Thread.MAX_PRIORITY);
            System.out.println("最低优先级 = " + Thread.MIN_PRIORITY);
            System.out.println("默认优先级 = " + Thread.NORM_PRIORITY);*/
            
            // 获取当前线程对象,获取当前线程的优先级
            Thread currentThread = Thread.currentThread();
        
            // 设置主线程的优先级1
            currentThread.currentThread().setPriority(1);
            
            // main线程的默认优先级是5
            // System.out.println(currentThread.getName() + "线程的默认优先级是" + currentThread.getPriority());
            /*int priority = currentThread.getPriority();
            System.out.println("当前对象线程优先级为 = " + priority);*/
            
            Thread thread = new Thread(new MyRunnable5());
            thread.setPriority(10);
            thread.setName("run");
            thread.start();
        
            // 优先级较高的,只是抢到的CPU时间片相对多一些。
            // 大概率方向更偏向于优先级比较高的。
            for (int i = 0; i < 10000; i++) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
            
        }
    }
    
    
    class MyRunnable5 implements Runnable{
        
        @Override
        public void run() {
            
            // 获取线程优先级
            // System.out.println(Thread.currentThread().getName() + "线程的默认优先级"  + Thread.currentThread().getPriority());
            
            for (int i = 0; i < 10000; i++) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }

    案例12 让位,当前线程暂停,回到就绪状态,让给其它线程

    package com.javaSe.Thread;
    /*
    让位,当前线程暂停,回到就绪状态,让给其它线程。
    静态方法:Thread.yield();
    */
    public class ThreadTest12 {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable6());
            t.setName("t");
            t.start();
        
            for (int i = 1; i <= 10000; i++) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    
    
    class MyRunnable6 implements Runnable{
        
        @Override
        public void run() {
            for (int i = 1; i <= 10000; i++) {
                // 每100个让位1次
                if (i % 100 == 0){
                    Thread.yield(); // 当前线程暂停一下,让给主线程。
                }
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }

    案例13 线程合并:

    package com.javaSe.Thread;
    /*
    线程合并
    */
    public class ThreadTest13 {
        public static void main(String[] args) {
            System.out.println("main begin");
            
            Thread t = new Thread(new MyRunnable7());
            t.setName("t");
            t.start();
            
            // 合并线程
            try {
                t.join(); // t合并到当前线程中,当前线程受到阻塞,t线程执行直到结束。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        
            System.out.println("main end");
        }
    }
    
    
    class MyRunnable7 implements Runnable{
        
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }

    多线程并发对同一个账户进行取款

    1 错误的多线程案例 如果线程并发,不进行排队,那么当两个线程操作同一个对象,就会出现问题:
        银行账户类:
    package com.javaSe.threadsafe;
    /*
    银行账户
        使用线程同步机制,解决线程安全问题。
    */
    public class Account {
        // 账户
        private String actno;
        // 余额
        private double balance; // 实例变量
        
        // 对象
        Object obj = new Object(); // 实例变量(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的。)
        
        public Account() {
        }
        
        public Account(String actno, double balance) {
            this.actno = actno;
            this.balance = balance;
        }
        
        public String getActno() {
            return actno;
        }
        
        public void setActno(String actno) {
            this.actno = actno;
        }
        
        public double getBalance() {
            return balance;
        }
        
        public void setBalance(double balance) {
            this.balance = balance;
        }
        
        // 取款的方法
        public void withdraw(double money){
            double before = this.getBalance(); // 10000
            double after = before - money;
    
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
    
            this.setBalance(after);
        }
    }

        账户线程类:

    package com.javaSe.threadsafe;
    
    
    public class AccountThread extends Thread {
        
        // 两个线程必须共享同一个账户对象。
        private Account act;
        
        // 通过构造方法传递过来账户对象
        public AccountThread(Account act){
            this.act = act;
        }
        
        public void run(){
            // run方法的执行表示取款操作
            // 假设取款5000
            double money = 5000;
            // 取款
            act.withdraw(money);
            System.out.println(Thread.currentThread().getName() + "对" + act.getActno() + "取款"+ money +"成功,余额 = " + act.getBalance());
        }
    }

        测试类:

    package com.javaSe.threadsafe;
    
    
    public class Test {
        public static void main(String[] args) {
            // 创建账户对象(只创建一个)
            Account act = new Account("A-001",10000);
            
            // 创建两个线程对象
            Thread t1 = new AccountThread(act);
            Thread t2 = new AccountThread(act);
            
            // 设置name
            t1.setName("t1");
            t2.setName("t2");
            
            // 启动线程取款
            t1.start();
            t2.start();
        }
    }
    2 使用线程同步机制,解决线程安全问题:
        银行账户类:
    package com.javaSe.threadsafe2;
    /*
    银行账户
        使用线程同步机制,解决线程安全问题。
    */
    public class Account {
        // 账户
        private String actno;
        // 余额
        private double balance; // 实例变量
        
        // 对象
        Object obj = new Object(); // 实例变量(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的。)
        
        public Account() {
        }
        
        public Account(String actno, double balance) {
            this.actno = actno;
            this.balance = balance;
        }
        
        public String getActno() {
            return actno;
        }
        
        public void setActno(String actno) {
            this.actno = actno;
        }
        
        public double getBalance() {
            return balance;
        }
        
        public void setBalance(double balance) {
            this.balance = balance;
        }
        
        // 取款的方法
        public void withdraw(double money){
            // 以下这几行代码必须是线程排队的,不能并发。
            // 一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
            
            /*
            线程同步机制的语法是:
                synchronized (){
                    // 线程同步代码块。
                }
                synchronized后面的小括号中传的这个“数据”是相当关键的。
                这个数据必须是多线程共享的数据,才能达到多线程排队。
                
                ()中写什么?
                    那要看你想让哪些线程同步。
                    假设t1 t2 t3 t4 t5,有五个线程
                    你只希望t1 t2 t3排队,t4 t5 不需要排队怎么办?
                    你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。
                    
                这里的共享对象:账户对象
                账户对象是共享的吗,那么这里this就是账户对象吧
                不一定是this,这里只要是多线程共享的那个对象就行。
                
                在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁)
                100个对象,100把锁。1个对象1把锁
                
                以下代码的执行原理?
                    1 假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
                    2 假设t1先执行了,遇到了synchronized 这个时候会自动找“后面共享对象”的对象锁,找到之后并占有这把锁,然后执行同步代码块中的程序,
                    在程序执行过程中一直都是占有者把锁的。知道同步代码块代码结束,这把锁才会释放。
                    3 假设t1已经占有了这把锁,t2也遇到了synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块的外面等待t1的结束,
                    直到t1把同步代码块执行结束了,t1也会归还这把锁,此时t2终于等到这把锁,然后t2占有这这把锁之后,进入同步代码块中执行代码。
                    
                    这样就达到了线程排队执行。
                    这里需要注意的事:这个共享对象一定要选好了。这个共享对象一定是你需要排队执行的这些线程对象所共享的。
             */
            
            // Object obj2 = new Object();
            // Object o = null;
            // synchronized (o) { // java.lang.NullPointerException
            // synchronized (obj2) { // 这样编写就不安全了,因为obj2是一个局部变量,他不是共享对象,第一个线程对象进来的时候会new一个obj对象,第二个线程进来还是会new一个,那就是多个了
            // synchronized (obj) { // 这样也行
            // synchronized (this) { // 这种才是最好的
            // synchronized ("abc") { // "abc"在字符串常量池当中,只有一个  但是这样的话 所有的线程都需要进行等待,这样不行。
                double before = this.getBalance(); // 10000
                double after = before - money;
        
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        
                this.setBalance(after);
            // }
        }
    }

        账户线程类:

    package com.javaSe.threadsafe2;
    
    
    public class AccountThread extends Thread {
        
        // 两个线程必须共享同一个账户对象。
        private Account act;
        
        // 通过构造方法传递过来账户对象
        public AccountThread(Account act){
            this.act = act;
        }
        
        public void run(){
            // run方法的执行表示取款操作
            // 假设取款5000
            double money = 5000;
            // 取款
            // synchronized (this){ // 这样不行,因为这个是线程对象,你new了两个线程对象,他就是两个内存地址,不存在线程共享。
            synchronized (act){
                act.withdraw(money); // 这种方式也可以,只不过你扩大了同步的范围,效率更低了。
            }
            System.out.println(Thread.currentThread().getName() + "对" + act.getActno() + "取款"+ money +"成功,余额 = " + act.getBalance());
        }
    }

        测试类:

    package com.javaSe.threadsafe2;
    
    
    public class Test {
        public static void main(String[] args) {
            // 创建账户对象(只创建一个)
            Account act = new Account("A-001",10000);
            
            // 创建两个线程对象
            Thread t1 = new AccountThread(act);
            Thread t2 = new AccountThread(act);
            
            // 设置name
            t1.setName("t1");
            t2.setName("t2");
            
            // 启动线程取款
            t1.start();
            t2.start();
        }
    }
    实例方法应用锁类:
        银行账户类:
    package com.javaSe.threadsafe3;
    public class Account {
        // 账户
        private String actno;
        // 余额
        private double balance; // 实例变量
        
        // 对象
        Object obj = new Object(); // 实例变量(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的。)
        
        public Account() {
        }
        
        public Account(String actno, double balance) {
            this.actno = actno;
            this.balance = balance;
        }
        
        public String getActno() {
            return actno;
        }
        
        public void setActno(String actno) {
            this.actno = actno;
        }
        
        public double getBalance() {
            return balance;
        }
        
        public void setBalance(double balance) {
            this.balance = balance;
        }
        
        // 取款的方法
        /*
        在实例方法上可以使用synchronized吗?可以的。
            synchronized出现在实例方法上,一定锁的是this。
            没得挑。只能是this。不能是其它的对象了。
            所以这种方式不灵活哦
            
            另外还有一个缺点:synchronized 出现在实例方法上
            表示这个方法体都需要同步,可能会无故扩大同步的范围,导致程序的执行效率较低。
            所以这种方式不常用。
            
            synchronized使用在实例方法上有什么优点?
                代码写的少了。节俭了。
                
            如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。
            
         */
        public synchronized void withdraw(double money){
            double before = this.getBalance(); // 10000
            double after = before - money;
    
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            this.setBalance(after);
        }
    }
        账户线程类:
    package com.javaSe.threadsafe3;
    
    
    public class AccountThread extends Thread {
        
        // 两个线程必须共享同一个账户对象。
        private Account act;
        
        // 通过构造方法传递过来账户对象
        public AccountThread(Account act){
            this.act = act;
        }
        
        public void run(){
            // run方法的执行表示取款操作
            // 假设取款5000
            double money = 5000;
            // 取款
            act.withdraw(money); // 这种方式也可以,只不过你扩大了同步的范围,效率更低了。
            System.out.println(Thread.currentThread().getName() + "对" + act.getActno() + "取款"+ money +"成功,余额 = " + act.getBalance());
        }
    }

        测试类:

    package com.javaSe.threadsafe3;
    
    
    public class Test {
        public static void main(String[] args) {
            // 创建账户对象(只创建一个)
            Account act = new Account("A-001",10000);
            
            // 创建两个线程对象
            Thread t1 = new AccountThread(act);
            Thread t2 = new AccountThread(act);
            
            // 设置name
            t1.setName("t1");
            t2.setName("t2");
            
            // 启动线程取款
            t1.start();
            t2.start();
        }
    }
  • 相关阅读:
    创建与合并分支
    Git丢弃本地修改
    《人月神话》小记
    财商培养
    赚钱有道,增加睡后收入
    学点经济学,升级认知
    保险小白普及知识
    管理决策、资源分配的最理想状态
    AI时代做一个终身学习者
    基于需求的测试
  • 原文地址:https://www.cnblogs.com/xlwu/p/13568167.html
Copyright © 2020-2023  润新知