多线程学习(一)

线程创建

三种创建线程的方式

继承Thread


.start() 方法启动线程执行的是 run() 里的线程体!

测试一:

// 创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
//总结:注意,线程开启不一定立即执行,由cpu调度执行
public class TestThread1 extends Thread{

    @Override
    public void run(){
        //run方法线程体
        for (int i = 0;i<20;i++){
            System.out.println("我在看代码-----"+i);
        }
    }

    public static void main(String[] args){
        //main线程,主线程

        //创建一个线程对象
        TestThread1 testThread1 = new TestThread1();
        //调用start()方法开启线程
        testThread1.start();

        for(int i= 0 ;i<200;i++){
            System.out.println("我在学习多线程----"+i);
       }
    }
}

输出结果:

可以发现 start() 开启的线程和主线程是交替执行的!!!

而直接调用 run() 方法,会发现先执行run()方法,再执行主线程:

public static void main(String[] args){
        //main线程,主线程

        //创建一个线程对象
        TestThread1 testThread1 = new TestThread1();

        //调用run()方法开启线程
        testThread1.run();

        for(int i= 0 ;i<200;i++){
            System.out.println("我在学习多线程----"+i);
        }
    }

输出结果

以上对比直接调用 run() 和调用 start()可以得出以下结论:

为了更好地验证上述结论,我们进行测试二:

测试二:

//练习Thread,实现多线程同步下载图片
public class TestThread2 extends Thread {

    private String url;  //网络图片地址
    private String name;  //保存的文件名

    public TestThread2(String url,String name){
        this.url=url;
        this.name=name;
    }

    //下载图片的线程执行体
    @Override
    public void run(){
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载的文件名为:"+name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://pics7.baidu.com/feed/a50f4bfbfbedab64cddfc490f31fc9c578311ee4.jpeg?token=d6cc78140999ca5390cd8002a754d9a4","1.jpg");
        TestThread2 t2 = new TestThread2("https://images2015.cnblogs.com/blog/1168144/201706/1168144-20170628161248586-632368241.png","2.jpg");
        TestThread2 t3 = new TestThread2("https://images2015.cnblogs.com/blog/1168144/201706/1168144-20170628162624243-370438535.png","3.jpg");

        t1.start();
        t2.start();
        t3.start();

    }

}

//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        try{
            FileUtils.copyURLToFile(new URL(url),new File(name));
        }catch (IOException e){
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }

    }

下载结果为:(发现并不是按1,2,3的顺序下载)

从而更好的说明,start() 方法开启的子线程和主线程是并行交替执行的!

实现Runnable

测试一:

//创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类Thread中,调用start()。
public class TestThread3 implements Runnable{

    @Override
    public void run(){
        //run方法线程体
        for (int i = 0;i<20;i++){
            System.out.println("我在看代码-----"+i);
        }
    }

    public static void main(String[] args){
        //创建runnable接口的实现类对象
        TestThread3 testThread3 = new TestThread3();

        //创建线程对象,通过线程对象来开启我们的线程,代理
        Thread thread = new Thread(testThread3);
        thread.start();

        for(int i= 0 ;i<200;i++){
            System.out.println("我在学习多线程----"+i);
        }
    }

}

输出结果:

测试二:让TestThread2实现Runnable接口下载图片,而非继承Thread

//实现Runnable接口下载图片
public class TestThread2 implements Runnable {

    private String url;  //网络图片地址
    private String name;  //保存的文件名

    public TestThread2(String url,String name){
        this.url=url;
        this.name=name;
    }

    //下载图片的线程执行体
    @Override
    public void run(){
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载的文件名为:"+name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://pics7.baidu.com/feed/a50f4bfbfbedab64cddfc490f31fc9c578311ee4.jpeg?token=d6cc78140999ca5390cd8002a754d9a4","1.jpg");
        TestThread2 t2 = new TestThread2("https://images2015.cnblogs.com/blog/1168144/201706/1168144-20170628161248586-632368241.png","2.jpg");
        TestThread2 t3 = new TestThread2("https://images2015.cnblogs.com/blog/1168144/201706/1168144-20170628162624243-370438535.png","3.jpg");

        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();

    }

}

下载结果:(交替下载,而非顺序下载)

小结

初识并发问题

//多个线程同时操作同一个对象
//买车票的例子

//发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
public class TestThread4 implements Runnable {

    //票数
    private int ticketNums = 10;

    @Override
    public void run(){
        while(true){
            if (ticketNums<=0){
                break;
            }
            //模拟延时
            try{
                Thread.sleep(200);
            }catch (InterruptedException e){
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
        }
    }

    public static void main(String[] args) {
        TestThread4 ticket = new TestThread4();
        new Thread(ticket,"小明").start();
        new Thread(ticket,"老师").start();
        new Thread(ticket,"黄牛").start();
    }
}

输出结果:(发现线程不安全)

案例:龟兔赛跑—Race

//模拟龟兔赛跑
public class Race implements Runnable {

    //胜利者
    private static String winner;

    @Override
    public void run(){
        for (int i = 1; i<=100 ; i++){

            //模拟兔子休息
            if(Thread.currentThread().getName().equals("兔子") && i%10==0){
                try{
                    Thread.sleep(10);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }

            //判断比赛是否结束
            boolean flag = gameOver(i);
            //如果比赛结束了,就停止程序
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"---->跑了"+i+"步");
        }
    }

    //判断是否完成比赛
    private boolean gameOver(int steps){
        //判断是否有胜利者
        if (winner!=null){ //已经有胜利者
            return true;
        }else{
            if (steps>=100){
                winner = Thread.currentThread().getName();
                System.out.println("winner is "+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {

        Race race = new Race();

        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

实现Callable接口(了解即可)

//线程创建方式三:实现callable接口
/*
* callable的好处
* 1.可以定义返回值
* 2.可以抛出异常
* */
public class TestCallable implements Callable<Boolean> {


    private String url;  //网络图片地址
    private String name;  //保存的文件名

    public TestCallable(String url,String name){
        this.url=url;
        this.name=name;
    }

    //下载图片的线程执行体
    @Override
    public Boolean call(){
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载的文件名为:"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://pics7.baidu.com/feed/a50f4bfbfbedab64cddfc490f31fc9c578311ee4.jpeg?token=d6cc78140999ca5390cd8002a754d9a4","1.jpg");
        TestCallable t2 = new TestCallable("https://images2015.cnblogs.com/blog/1168144/201706/1168144-20170628161248586-632368241.png","2.jpg");
        TestCallable t3 = new TestCallable("https://images2015.cnblogs.com/blog/1168144/201706/1168144-20170628162624243-370438535.png","3.jpg");

        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);
        //获取结果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();

        System.out.println(rs1);
        System.out.println(rs2);
        System.out.println(rs3);
        //关闭服务
        ser.shutdownNow();

    }

}

//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        try{
            FileUtils.copyURLToFile(new URL(url),new File(name));
        }catch (IOException e){
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }

    }
}

下载结果:

静态代理

//静态代理模式总结:
    //真实对象和代理对象都要实现同一个接口
    //代理对象要代理真实角色

//好处:
    //代理对象可以做很多真实对象做不了的事情
    //真实对象专注做自己的事情

public class StaticProxy {

    public static void main(String[] args) {

        You you = new You();//你要结婚
        WeddingCompany weddingCompany = new WeddingCompany(you);
        weddingCompany.HappyMarry();
    }
}

interface Marry{

    void HappyMarry();
}

//真实角色,你去结婚
class You implements Marry{
    @Override
    public void HappyMarry(){
        System.out.println("秦老师要结婚了,超开心");
    }
}

//代理角色,帮助你结婚
class WeddingCompany implements Marry{

    //代理谁-> 真实目标角色
    private Marry target;

    public WeddingCompany(Marry target){
        this.target = target;
    }

    @Override
    public void HappyMarry(){
        before();
        this.target.HappyMarry(); //这就是真实对象
        after();
    }

    private void after(){
        System.out.println("结婚后,收尾款");
    }

    private void before(){
        System.out.println("结婚前,布置现场");
    }

和上文实现Runnable接口创建线程作对比:

​ Marry接口——>Runnable接口

​ 实现Marry接口的You类——>实现Runnable接口的TestThread3类

​ 实现Marry接口的WeddingCompany类——>实现Runnable接口的Thread类

Lambda表达式



/**
 * 推导lambda表达式: 
 *      1.创建外部类——>2.创建静态内部类——>3.创建局部内部类——>4.创建匿名内部类——>5.使用lambda表达式
 */
public class TestLambda {

    //3.静态内部类
    static class Like2 implements ILike{
        @Override
        public void lambda(){
            System.out.println("I like lambda2");
        }
    }

    public static void main(String[] args) {
		
        //调用外部类创建对象
        ILike like1 = new Like();
        like1.lambda();
		
        //调用静态内部类创建对象
        ILike like2 = new Like2();
        like2.lambda();


        //4.局部内部类
        class Like3 implements ILike{
            @Override
            public void lambda(){
                System.out.println("I like lambda3");
            }
        }
        ILike like3 = new Like3();
        like3.lambda();

        //5.匿名内部类,没有类名字,必须借助接口或者父类
        ILike like4 = new ILike() {
            @Override
            public void lambda() {
                System.out.println("I like lambda4");
            }
        };
        like4.lambda();

        //6.用lambda简化
        ILike like5 = ()->{
            System.out.println("I like lambda5");
        };
        like5.lambda();
    }
}

//1.定义一个函数接口
interface ILike{
    void lambda();
}

//2.实现外部类
class Like implements ILike{
    @Override
    public void lambda(){
        System.out.println("I like lambda");
    }
}

在函数式接口的抽象函数有参数的情况下,使用lambda表达式:

public class TestLambda2 {

    public static void main(String[] args) {
        
		//使用lambda表达式,不做任何化简
        ILove love1 = (int a)->{
                System.out.println("I love you-->"+a);
            };
        love1.love(1);

        //1.简化参数类型
        ILove love2 = (a)->{
            System.out.println("I love you-->"+a);
        };
        love2.love(2);

        //2.简化括号和参数类型
        ILove love3 = a->{
            System.out.println("I love you-->"+a);
        };
        love2.love(3);

        //3.简化括号和参数类型和花括号
        ILove love4 = a->System.out.println("I love you-->"+a);
        love4.love(4);

        //总结:
            //lambda表达式只能在方法体有一行代码的情况下才能简化成为一行(即去掉花括号),如果有多行,那么就用代码块包裹(即使用花括号)
            //lambda的使用前提是接口为函数式接口(即接口里只有一个函数方法)
            //多个参数也可以去掉参数类型(参数类型不同也可以),要去掉就都去掉,但必须加括号
            //当使用lambda有返回值时,方法体即使只有一行代码,也要用花括号包裹!
    }
}

//函数式接口
interface ILove{
    void love(int a);
}

线程状态

线程的五大状态

停止线程

使用我们自定义的stop方法,通过改变标志位让线程停止:

//测试stop
//1.建议线程正常停止————>利用次数,不建议死循环
//2.建议使用标志位————>设置一个标志位
//3.不要使用stop或者destroy等过时或者JDK不建议使用的方法
public class TestStop implements Runnable{

    //1.设置一个标识位
    private boolean flag = true;

    @Override
    public void run(){
        int i = 0;
        while(flag){
            System.out.println("run ..... Thread"+i++);
        }
    }

    //2.设置一个公开的方法停止线程,转换标志位
    public void stop(){
        this.flag = false;
    }

    public static void main(String[] args) {

        TestStop testStop = new TestStop();
        new Thread(testStop).start();

        for (int i=0;i<1000;i++){
            System.out.println("main"+i);
            if (i==900){
                //调用 stop方法切换标志位,让线程停止
                testStop.stop();
                System.out.println("线程该停止了!!!");
            }
        }
    }
}

线程在执行第1060次时停止:

线程休眠

import com.ztx.dem01.TestThread4;

//模拟网络延时:放大问题的发生行
public class TestSleep implements Runnable{

    //票数
    private int ticketNums = 10;

    @Override
    public void run(){
        while(true){
            if (ticketNums<=0){
                break;
            }
            //模拟延时
            try{
                Thread.sleep(200);
            }catch (InterruptedException e){
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
        }
    }

    public static void main(String[] args) {
        TestThread4 ticket = new TestThread4();
        new Thread(ticket,"小明").start();
        new Thread(ticket,"老师").start();
        new Thread(ticket,"黄牛").start();
    }
}

//模拟倒计时
public class TestSleep2 {

    public static void main(String[] args) {

        try {
            tenDown();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    //模拟倒计时
    public static void tenDown() throws InterruptedException{
        int num = 10;
        while(true){
            Thread.sleep(1000);
            System.out.println(num--);
            if (num<=0){
                break;
            }
        }
    }
}

//模拟倒计时
public class TestSleep2 {

    public static void main(String[] args) {
        //打印当前系统时间
        Date startTime = new Date(System.currentTimeMillis());//获取当前系统时间

        while(true){
            try{
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:MM:SS").format(startTime));
                startTime = new Date(System.currentTimeMillis());//更新当前系统时间
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

线程礼让

//测试礼让线程
//礼让不一定成功,看cpu心情
public class TestYield {

    public static void main(String[] args) {
        MyYield myYield = new MyYield();

        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
    }

}

class MyYield implements Runnable{

    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();//礼让
        System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
}

理想情况下,当把 Thread.yield(); 注释掉,输出情况应为:(实际上,不可能这么理想!)

加上礼让代码后,输出情况可能就不会很规整:(让执行的线程暂停,但不阻塞)

join

//测试join方法
//想象为插队
public class TestJoin implements Runnable{

    @Override
    public void run(){
        for (int i=0;i<1000;i++){
            System.out.println("线程vip来了"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        //启动我们的线程
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();

        //主线程
        for(int i=0;i<500;i++){
            if (i==200){
                thread.join();//插队
            }
            System.out.println("main"+i);
        }
    }
}

输出结果:(在“main199”之前,线程是交替执行的,main199之后,线程VIP开始插队直到执行结束,main在开始继续执行!!!)

线程状态观测

//观测线程的状态
public class TestState {

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(()->{
            for (int i=0;i<10;i++){
                try{
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            System.out.println("///////");
        });

        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state);  //状态为:new,即新生的

        //观察启动后的状态
        thread.start();//启动线程
        state = thread.getState();
        System.out.println(state);//状态应为:run

        while(state!=Thread.State.TERMINATED){//只要线程不终止,就一直输出状态
            Thread.sleep(1000);
            state = thread.getState(); //更新线程状态
            System.out.println(state);
        }

        thread.start();
    }
}

线程优先级

测试线程优先级:

//测试线程的优先级
public class TestPriority {

    public static void main(String[] args) {
        //主线程默认优先级
        System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);

        //先设置优先级,再启动
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();


        t5.setPriority(8);
        t5.start();

        t6.setPriority(7);
        t6.start();
    }
}

class MyPriority implements Runnable{
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
    }
}

运行结果:

守护(daemon)线程

测试守护线程:

//测试守护线程
//上帝守护你
public class TestDaemon {

    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true); //默认为false表示用户线程,正常的线程都是用户线程

        thread.start();//上帝守护线程开启

        new Thread(you).start();//you 用户线程开启
    }
}

//上帝
class God implements Runnable{
    @Override
    public void run(){
        //这里设置为一直循环,但虚拟机不会等到该线程执行结束(该线程也不会自动结束),就会将其停止,它只要确保用户线程执行完毕即可
        while(true){    
            System.out.println("s上帝保佑着你!");
        }
    }
}

//你
class You implements Runnable{
    @Override
    public void run(){
        for (int i=0;i<36500;i++){
            System.out.println("你一生都开心的活着");
        }
        System.out.println("======goodbye! world!=======");
    }
}

输出结果:

这里的用户线程执行完毕后,守护线程依旧执行是因为虚拟机完毕需要时间,在这期间守护线程还可以一直运行!

posted @ 2020-06-24 10:06  大盘鸡嘹咋咧  阅读(129)  评论(0编辑  收藏  举报