多线程

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();
    }
}

 

posted @ 2020-08-26 21:50  xlwu丶lz  阅读(170)  评论(0编辑  收藏  举报