龙空技术网

干货内容-线程的介绍和应用

学日语的咩咩 114

前言:

现时兄弟们对“java中线程的作用”大约比较着重,看官们都想要剖析一些“java中线程的作用”的相关文章。那么小编在网摘上搜集了一些关于“java中线程的作用””的相关知识,希望各位老铁们能喜欢,同学们一起来学习一下吧!

1.程序 进程 线程程序(program):是为完成的特定任务,用某种语言编写的一组指令的集合。简单来说,就是我们写的代码。进程:进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间。进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程。线程:线程是由进程创建的,是进程的一个实体一个进程可以有多个线程,比如:用迅雷同时下载多个文件其他相关概念:单线程:同一时刻,只允许执行一个线程多线程:同一时刻,可以执行多个线程,比如:一个qq进程,可以同时打开多个聊天窗口;一个迅雷进程,可以同时下载多个文件。并发:同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单地说,单核cpu实现的多任务就是并发并行:同一时刻,多个任务同时执行。多核cpu可以实现并行。在电脑中也可能同时出现并发和并行的状态。

例子:

package li.thread; public class CpuNum {    public static void main(String[] args) {         Runtime runtime = Runtime.getRuntime();        //获取当前的电脑的cpu数量        int cpuNums = runtime.availableProcessors();        System.out.println("当前的CPU数量="+cpuNums);//当前的CPU数量=8     }}
2.线程的基本使用创建线程的两种方式

在java中线程来使用有两种方法:

继承Thread类,重写run方法实现Runnable接口,重写run方法2.1继承Thread创建线程

线程应用案例1-继承Thread类:

1)请编写程序,开启一个线程,该线程每隔一秒,在控制台输出 “喵喵,我是小猫咪”

2)对上题改进:当输出80次“喵喵,我是小猫咪”时,结束该线程

3)使用JConsole监控线程执行情况,并画出程序示意图

package li.thread; //演示通过继承Thread类创建线程public class Thread01 {    public static void main(String[] args) throws InterruptedException {                //创建一个Cat对象,可以当做线程来使用        Cat cat = new Cat();                cat.start();//启动线程                //当main线程启动一个子线程 Thread-0后,主线程不会阻塞,会继续执行        //这时 主线程和子线程是交替执行        System.out.println("主线程继续执行="+Thread.currentThread().getName());//主线程继续执行=main        for (int i = 0; i < 60; i++) {            System.out.println("主线程 i="+i);            //让主线程休眠            Thread.sleep(1000);        }    }} //1.当一个类继承了Thread类,该类就可以当做一个线程使用//2.我们会重写run方法,写上自己的业务代码//3.run Thread类实现了Runnable接口的run方法/*      @Override      public void run() {          if (target != null) {              target.run();          }      } */class Cat extends Thread {    @Override    public void run() {//重写run方法,写上自己的业务逻辑        int times = 0;        while (true) {            //该线程每隔1秒,在控制台输出 “喵喵,我是小猫咪”            System.out.println("喵喵,我是小猫咪" + (++times)+" 线程名称="+Thread.currentThread().getName());            //让该线程休眠一秒            try {                Thread.sleep(1000);//单位为毫秒 try-catch快捷键:Ctrl+Alt+T            } catch (InterruptedException e) {                e.printStackTrace();            }            if (times == 80) {                break;//当times到80,退出while,这时线程也就退出了            }        }    }}

3)使用JConsole监控线程执行情况,并画出程序示意图:

如下,在控制台点击run,运行程序,在程序运行时,点击Termial

在控制台输入JConsole,回车。

点击本地进程,点击Thread01,点击下方连接按钮:

在弹出窗口中点击不安全的连接按钮:

在窗口中点击“线程”:

可以在左下角的线程小窗口中看到main线程和Thread-0线程在同时进行

等待一段时间,可以看到当run窗口的主线程 i = 60之后,main线程结束

结束前:

结束后:

当线程名称=Thread-0输出到80次时,虽然可以Thread-0还在左下角,但是实际上Thread-0线程已经结束了,整个进程随之结束。

程序示意图:

注意:在多线程编程里面,并不一定说主线程结束了,整个进行就结束了,等所有线程都结束了,进程才会结束。

2.2为什么是start?

在2.1的例子中,主方法中定义了cat对象,该对象调用了start方法,start方法会去启动一个线程,最终会执行Cat 类的run方法。

思考一个问题:既然最终都是要调用run方法,为什么cat对象还要通过start方法对调用run呢?为什么不直接调用?

答案: 首先通过 对象.run() 方法 可以执行方法,但是不是使用的多线程的方式,就是一个普通的方法,没有真正地启动一个线程。即这时候把run方法执行完毕,才能执行主方法剩下的语句。

如下图:将cat.start();改为cat.run();之后的运行结果:

在run方法执行完之后才执行主方法剩下的语句

那么在调用start方法时,整个过程到底是什么样子的?

点击start()方法:可以在start方法中看到一个start0()方法:

点击start0( )方法:可以看到start0是一个本地方法,由 JVM调用,底层是c/c++实现。

再看看run()方法的源码:可以看到run方法只是简单的调用了实现类的run,没有进行任何的多线程处理。

换而言之,Java中真正实现多线程的效果的是start0方法,而不是run方法

2.3实现Runnable创建线程

说明:

java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类方法来创建线程显然不可能了。java设计者们提供了另外一种方式来创建线程,就是通过实现Runnable接口来创建线程。

线程应用案例2-实现Runnable接口:

请编写程序,该程序可以每隔一秒在控制台输出“hi”,当输出10次后,自动退出。请使用实现Runnable接口的方式实现。

package li.thread; //通过实现Runnable接口的方式来开发线程public class Thread02 {    public static void main(String[] args) {        Dog dog = new Dog();        //dog.start();这里不能调用start方法         //创建Thread对象,把dog对象(实现Runnable)放入Thread对象中        Thread thread = new Thread(dog);        thread.start();     }}class Dog implements Runnable{//通过实现Runnable接口的方式开发线程    int count = 0 ;    @Override    public void run(){        while(true){            System.out.println("小狗汪汪叫..hi"+(++count)+Thread.currentThread().getName());            //休眠一秒            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            if (count == 10) {                break;            }        }    }}

因为Runnable接口只有run方法,因此不能直接通过dog对象调用start方法

思考:为什么将dog对象放入到thread对象之后,通过调用thread对象的start方法,就可以调用到dog的run方法了呢?

答案:这里的底层使用了一个设计模式[代理模式中的静态代理],下面用代码模拟实现Runnable接口 开发线程的机制

代码模拟实现Runnable接口 开发线程的机制:

package li.thread; //通过实现Runnable接口的方式来开发线程public class Thread02 {    public static void main(String[] args) {                Tiger tiger = new Tiger();                //tiger之所以可以放进threadProxy里,是因为tiger实现了Runnable接口        //ThreadProxy构造器的参数是Runnable类型,tiger赋给了target        ThreadProxy threadProxy = new ThreadProxy(tiger);         /*        start()方法调用start0()方法,start0()又会调用run方法,        run()方法返回去调用target,判断发现此时target不为空        接着就会进行动态绑定,运行类型为(Tiger),然后到Tiger类去执行run方法        */        threadProxy.start();    }} class Animal{}class Tiger extends Animal implements Runnable{     @Override    public void run() {        System.out.println("老虎嗷嗷叫...");    }} //线程代理类,模拟了一个极简的Thread类class ThreadProxy implements Runnable {//可以将ThreadProxy当做是Thread     private Runnable target = null;//属性,类型是Runnable     @Override    public void run() {        if (target != null) {            target.run();//动态绑定,运行类型(Tiger)        }     }     public ThreadProxy(Runnable target) {//接收一个实现了Runnable接口的对象,将它赋给target        this.target = target;    }     public void start(){        start0();//这个方法是最重要的,真正实现了多线程的方法(注意这里只是模拟,没有真正实现)    }     public void start0(){        run();    }}

动态绑定:动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。程序运行过程中,把函数(或过程)调用与响应调用所需要的代码相结合的过程称为动态绑定。

2.4多线程执行

请编写一个程序,创建两个线程。一个线程每隔一秒输出“hello world”,输出10次后退出,另一个线程每隔1秒输出“hi”,输出5次后退出。要求使用实现Runnable接口的方式创建线程。

线程应用案例3-多线程执行:

package li.thread; public class Thread03 {    public static void main(String[] args) {        T1 t1 = new T1();        T2 t2 = new T2();         Thread thread1 = new Thread(t1);        Thread thread2 = new Thread(t2);         thread1.start();        thread2.start();        }} class T1 implements Runnable {    int count = 0;     @Override    public void run() {        while (true) {            //每隔1秒输出“hello world”,输出10次后退出            System.out.println("hello,world "+(++count));             try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            if (count == 10) {                break;            }        }    }} class T2 implements Runnable {    int count = 0;     @Override    public void run() {        while (true) {            //每隔1秒输出“hi”,输出5次后退出            System.out.println("hi "+(++count));            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            if (count == 5) {                break;            }        }    }}
3.继承Thread和实现Runnable的区别从java的设计来看,通过继承Thread或者实现Runnable接口本身来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable接口3.1多线程售票问题

编程模拟三个售票窗口售票100张,分别使用继承Thread类和实现Runnable接口的方法,并分析有什么问题?

1.使用继承Thread的方法:

package li.thread; //使用多线程,模拟三个窗口同时售票共100张public class SellTicket {    public static void main(String[] args) {         SellTicket01 sellTicket01 = new SellTicket01();        SellTicket01 sellTicket02 = new SellTicket01();        SellTicket01 sellTicket03 = new SellTicket01();         sellTicket01.start();//启动售票线程        sellTicket02.start();//启动售票线程        sellTicket03.start();//启动售票线程    }} //1.使用继承Thread类的方式class SellTicket01 extends Thread {     //多个对象共享同一个静态成员变量(多个实例的static变量会共享同一块内存区域)    private static int ticketNum = 100;//让多个线程共享ticketNum     @Override    public void run() {        while (true) {             if (ticketNum <= 0) {                System.out.println("售票结束...");                break;            }             //休眠50毫秒,模拟            try {                Thread.sleep(50);            } catch (InterruptedException e) {                e.printStackTrace();            }             System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票 "                    + "剩余票数:" + (--ticketNum));        }    }}

一个显然的问题是,剩余票数竟然是负数!

原因是:每个线程都要进行票数判断才能进行下一步操作,假设某时刻票数还剩2张,此时线程0判断条件ticketNum <= 0不成立;于此同时,线程1线程2也同时进行了判断,三者都通过了判断,于是都认为此刻票数为2,都进行-1售票操作。于是三者结束后就会出现总票数为-1 的情况。

可以看到,造成票数超卖的主要原因是三个线程同时操作一个资源。

2.使用实现接口Runnable的方式:

package li.thread; //使用多线程,模拟三个窗口同时售票共100张public class SellTicket {    public static void main(String[] args) {         SellTicket02 sellTicket02 = new SellTicket02();        new Thread(sellTicket02).start();//第1个线程-窗口        new Thread(sellTicket02).start();//第2个线程-窗口        new Thread(sellTicket02).start();//第3个线程-窗口    }} class SellTicket02 implements Runnable {     private int ticketNum = 100;     @Override    public void run() {        while (true) {             if (ticketNum <= 0) {                System.out.println("售票结束...");                break;            }             //休眠50毫秒,模拟            try {                Thread.sleep(50);            } catch (InterruptedException e) {                e.printStackTrace();            }             System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票 "                    + "剩余票数:" + (--ticketNum));        }    } }

可以看到,实现接口Runnable的方式同样发生了票数为负数的情况,原因与上面一致,是由于多个线程同时操作一个资源而造成的。

要解决类似的问题,就要引入线程的同步和互斥的概念。该问题将在之后解决。

4.线程终止基本说明:当线程完成任务后,会自动退出还可以通过使用变量来控制run方法退出的方式来停止线程,即通知方式

例子:

启动一个线程t,要求在main线程中去停止线程t,请编程实现。

package li.thread.exit_; public class ThreadExit_ {    public static void main(String[] args) throws InterruptedException {        T t = new T();        t.start();         //如果希望main线程可以去控制 t1线程的终止,必须可以修改loop        //让 t1退出run方法,从而终止 t1线程 -->称为 通知方式         //让主线程休眠 10秒,在通知 t1线程退出        System.out.println("主线程休眠10秒...");        Thread.sleep(10*1000);         t.setLoop(false);    }} class T extends Thread {    int count = 0;     //设置一个控制变量    private boolean loop = true;     @Override    public void run() {        while (loop) {            try {                Thread.sleep(50);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("T 运行中..."+(++count));        }    }     public void setLoop(boolean loop) {        this.loop = loop;    }}

可以用于一个线程通过变量控制另一个线程终止的情况。

5.线程常用方法常用方法第一组:setName //设置线程名称,使之与参数name相同getName //返回该线程的名称start //使该线程开始执行;Java虚拟机底层调用该线程的start0()方法run //调用线程对象run方法setPriority //更改线程的优先级getPriority // 获取线程的优先级sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)interrupt //中断线程

注意事项和细节:

start方法底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程线程优先级的范围interrupt,中断线程,但并没有真正地结束线程。所以一般用于中断正在休眠的线程sleep:线程的静态方法,使当前线程休眠

例子1:

package li.thread.method; public class ThreadMethod01 {    public static void main(String[] args) throws InterruptedException {        //测试相关方法        T t = new T();        t.setName("jack");//设置线程的名称        t.setPriority(Thread.MIN_PRIORITY);        t.start();//启动子线程         //主线程打印5句hi,然后中断子线程的休眠        for (int i = 0; i < 5; i++) {            Thread.sleep(1000);            System.out.println("hi" + i);        }         System.out.println(t.getName() + "线程的优先级=" + t.getPriority());        t.interrupt();//当执行到这里的时候,就会中断 t线程的休眠    }} class T extends Thread {//自定义的线程类     @Override    public void run() {        while (true) {//每隔5秒吃100个包子,然后休眠5秒,再吃...            for (int i = 0; i < 100; i++) {                //Thread.currentThread().getName()获取当前线程的名称                System.out.println(Thread.currentThread().getName() + "吃包子~~~" + i);            }            try {                System.out.println(Thread.currentThread().getName() + "休眠中~~~");                sleep(20000);//休眠20秒            } catch (InterruptedException e) {                //当该线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码                //InterruptedException是捕获到一个中断异常                System.out.println(Thread.currentThread().getName() + "被interrupt了");            }        }    }}
常用方法第二组:yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功。join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程的所有任务

例子2:创建一个子线程,每个1秒输出hello,输出20次;主线程每隔1秒输出hi,输出20次。要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续。(join)

package li.thread.method; public class ThreadMethod02 {    public static void main(String[] args) throws InterruptedException {        T2 t2 = new T2();        t2.start();         for (int i = 0; i <=20; i++) {            Thread.sleep(1000);            System.out.println("主线程吃了 "+i+" 个包子");            if (i == 5) {                System.out.println("主线程让子线程先吃包子...");                t2.join();//相当于让 t2线程先执行完毕                //Thread.yield();//如果这里是yield,不一定会礼让成功                System.out.println("主线程接着吃包子...");            }        }    }} class T2 extends Thread {    int loop = 0;     @Override    public void run() {        for (int i = 0; i <= 20; i++) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("子线程吃了 "+i+" 个包子");        }    }}

课堂练习:主线程每隔一秒输出hi,一共十次当输出到5次时,启动一个子线程(要求实现Runnable),该子线程每隔1s输出hello,等该宣称输出10次后,退出主线程继续输出hi,直到主线程退出

package li.thread.method; public class ThreadMethod03 {    public static void main(String[] args) throws InterruptedException {         Thread thread = new Thread(new T3());        for (int i = 0; i <= 10; i++) {            Thread.sleep(1000);            System.out.println("hi" + i);            if (i == 5) {                thread.start();//启动子线程                thread.join();//将子线程先执行            }        }    }} class T3 implements Runnable {     @Override    public void run() {        for (int i = 0; i <= 10; i++) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("hello" + i);        }    }}

线程基础023.继承Thread和实现Runnable的区别从java的设计来看,通过继承Thread或者实现Runnable接口本身来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制,建议使用Runnable接口3.1多线程售票问题

编程模拟三个售票窗口售票100张,分别使用继承Thread类和实现Runnable接口的方法,并分析有什么问题?

1.使用继承Thread的方法:

package li.thread; //使用多线程,模拟三个窗口同时售票共100张public class SellTicket {    public static void main(String[] args) {         SellTicket01 sellTicket01 = new SellTicket01();        SellTicket01 sellTicket02 = new SellTicket01();        SellTicket01 sellTicket03 = new SellTicket01();         sellTicket01.start();//启动售票线程        sellTicket02.start();//启动售票线程        sellTicket03.start();//启动售票线程    }} //1.使用继承Thread类的方式class SellTicket01 extends Thread {     //多个对象共享同一个静态成员变量(多个实例的static变量会共享同一块内存区域)    private static int ticketNum = 100;//让多个线程共享ticketNum     @Override    public void run() {        while (true) {             if (ticketNum <= 0) {                System.out.println("售票结束...");                break;            }             //休眠50毫秒,模拟            try {                Thread.sleep(50);            } catch (InterruptedException e) {                e.printStackTrace();            }             System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票 "                    + "剩余票数:" + (--ticketNum));        }    }}

一个显然的问题是,剩余票数竟然是负数!

原因是:每个线程都要进行票数判断才能进行下一步操作,假设某时刻票数还剩2张,此时线程0判断条件ticketNum <= 0不成立;于此同时,线程1线程2也同时进行了判断,三者都通过了判断,于是都认为此刻票数为2,都进行-1售票操作。于是三者结束后就会出现总票数为-1 的情况。

可以看到,造成票数超卖的主要原因是三个线程同时操作一个资源。

2.使用实现接口Runnable的方式:

package li.thread; //使用多线程,模拟三个窗口同时售票共100张public class SellTicket {    public static void main(String[] args) {         SellTicket02 sellTicket02 = new SellTicket02();        new Thread(sellTicket02).start();//第1个线程-窗口        new Thread(sellTicket02).start();//第2个线程-窗口        new Thread(sellTicket02).start();//第3个线程-窗口    }} class SellTicket02 implements Runnable {     private int ticketNum = 100;     @Override    public void run() {        while (true) {             if (ticketNum <= 0) {                System.out.println("售票结束...");                break;            }             //休眠50毫秒,模拟            try {                Thread.sleep(50);            } catch (InterruptedException e) {                e.printStackTrace();            }             System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票 "                    + "剩余票数:" + (--ticketNum));        }    } }

可以看到,实现接口Runnable的方式同样发生了票数为负数的情况,原因与上面一致,是由于多个线程同时操作一个资源而造成的。

要解决类似的问题,就要引入线程的同步和互斥的概念。该问题将在之后解决。

4.线程终止基本说明:当线程完成任务后,会自动退出还可以通过使用变量来控制run方法退出的方式来停止线程,即通知方式

例子:

启动一个线程t,要求在main线程中去停止线程t,请编程实现。

package li.thread.exit_; public class ThreadExit_ {    public static void main(String[] args) throws InterruptedException {        T t = new T();        t.start();         //如果希望main线程可以去控制 t1线程的终止,必须可以修改loop        //让 t1退出run方法,从而终止 t1线程 -->称为 通知方式         //让主线程休眠 10秒,在通知 t1线程退出        System.out.println("主线程休眠10秒...");        Thread.sleep(10*1000);         t.setLoop(false);    }} class T extends Thread {    int count = 0;     //设置一个控制变量    private boolean loop = true;     @Override    public void run() {        while (loop) {            try {                Thread.sleep(50);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("T 运行中..."+(++count));        }    }     public void setLoop(boolean loop) {        this.loop = loop;    }}

可以用于一个线程通过变量控制另一个线程终止的情况。

5.线程常用方法常用方法第一组:setName //设置线程名称,使之与参数name相同getName //返回该线程的名称start //使该线程开始执行;Java虚拟机底层调用该线程的start0()方法run //调用线程对象run方法setPriority //更改线程的优先级getPriority // 获取线程的优先级sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)interrupt //中断线程

注意事项和细节:

start方法底层会创建新的线程,调用run,run就是一个简单的方法调用,不会启动新的线程线程优先级的范围interrupt,中断线程,但并没有真正地结束线程。所以一般用于中断正在休眠的线程sleep:线程的静态方法,使当前线程休眠

例子1:

package li.thread.method; public class ThreadMethod01 {    public static void main(String[] args) throws InterruptedException {        //测试相关方法        T t = new T();        t.setName("jack");//设置线程的名称        t.setPriority(Thread.MIN_PRIORITY);        t.start();//启动子线程         //主线程打印5句hi,然后中断子线程的休眠        for (int i = 0; i < 5; i++) {            Thread.sleep(1000);            System.out.println("hi" + i);        }         System.out.println(t.getName() + "线程的优先级=" + t.getPriority());        t.interrupt();//当执行到这里的时候,就会中断 t线程的休眠    }} class T extends Thread {//自定义的线程类     @Override    public void run() {        while (true) {//每隔5秒吃100个包子,然后休眠5秒,再吃...            for (int i = 0; i < 100; i++) {                //Thread.currentThread().getName()获取当前线程的名称                System.out.println(Thread.currentThread().getName() + "吃包子~~~" + i);            }            try {                System.out.println(Thread.currentThread().getName() + "休眠中~~~");                sleep(20000);//休眠20秒            } catch (InterruptedException e) {                //当该线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码                //InterruptedException是捕获到一个中断异常                System.out.println(Thread.currentThread().getName() + "被interrupt了");            }        }    }}
常用方法第二组:yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功。join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程的所有任务

例子2:创建一个子线程,每个1秒输出hello,输出20次;主线程每隔1秒输出hi,输出20次。要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续。(join)

package li.thread.method; public class ThreadMethod02 {    public static void main(String[] args) throws InterruptedException {        T2 t2 = new T2();        t2.start();         for (int i = 0; i <=20; i++) {            Thread.sleep(1000);            System.out.println("主线程吃了 "+i+" 个包子");            if (i == 5) {                System.out.println("主线程让子线程先吃包子...");                t2.join();//相当于让 t2线程先执行完毕                //Thread.yield();//如果这里是yield,不一定会礼让成功                System.out.println("主线程接着吃包子...");            }        }    }} class T2 extends Thread {    int loop = 0;     @Override    public void run() {        for (int i = 0; i <= 20; i++) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("子线程吃了 "+i+" 个包子");        }    }}

课堂练习:主线程每隔一秒输出hi,一共十次当输出到5次时,启动一个子线程(要求实现Runnable),该子线程每隔1s输出hello,等该宣称输出10次后,退出主线程继续输出hi,直到主线程退出

package li.thread.method; public class ThreadMethod03 {    public static void main(String[] args) throws InterruptedException {         Thread thread = new Thread(new T3());        for (int i = 0; i <= 10; i++) {            Thread.sleep(1000);            System.out.println("hi" + i);            if (i == 5) {                thread.start();//启动子线程                thread.join();//将子线程先执行            }        }    }} class T3 implements Runnable {     @Override    public void run() {        for (int i = 0; i <= 10; i++) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("hello" + i);        }    }}

6.用户线程和守护线程用户线程:也叫工作线程,当线程的任务执行完或者通知方法结束。平时用到的普通线程均是用户线程,当在Java程序中创建一个线程,它就被称为用户线程守护线程(Daemon):一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束常见的守护线程:垃圾回收机制

例子1:如何将一个线程设置成守护线程

package li.thread.method; public class ThreadMethodExercise {    public static void main(String[] args) throws InterruptedException {                MyDaemonThread myDaemonThread = new MyDaemonThread();                //如果我们希望当主线程结束后,子线程自动结束,只需要将子线程设置为守护线程        myDaemonThread.setDaemon(true);                myDaemonThread.start();                for (int i = 1; i <= 10; i++) {//main线程            System.out.println("悟空在前方打妖精...");            Thread.sleep(1000);        }    }} class MyDaemonThread extends Thread {    @Override    public void run() {        for (; ; ) {//无限循环            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("八戒收拾东西回高老庄...");        }    }}
7.线程的生命周期JDK中用Thread.State枚举表示了线程的几种状态:

例子

package li.thread.state; public class ThreadState_ {    public static void main(String[] args) throws InterruptedException {        T t = new T();        System.out.println(t.getName() + "状态 " + t.getState());        t.start();        while (t.getState() != Thread.State.TERMINATED) {            System.out.println(t.getName() + "状态 " + t.getState());            Thread.sleep(1000);        }         System.out.println(t.getName() + "状态 " + t.getState());     }} class T extends Thread {    @Override    public void run() {        for (int i = 0; i < 10; i++) {            System.out.println("hi" + i);            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}
8.线程同步机制线程同步机制在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。也可以理解为:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。同步具体方法--Synchronized同步代码块synchronized(对象){//得到对象的锁,才能操作同步代码 //需要被同步的代码}synchronized还可以放在方法声明中,表示整个方法为同步方法public synchronized void m(String name){ //需要被同步的代码}就好像某个小伙伴上厕所之前先把门关上(上锁),完事之后再出来(解锁),那么其他小伙伴就可以再使用厕所了

例子:使用synchronized解决3.1售票问题

package li.thread.syn; //使用多线程,模拟三个窗口同时售票共100张public class SynSellTicket {    public static void main(String[] args) {         SellTicket03 sellTicket03 = new SellTicket03();        new Thread(sellTicket03).start();//第1个线程-窗口        new Thread(sellTicket03).start();//第2个线程-窗口        new Thread(sellTicket03).start();//第3个线程-窗口    }} //实现接口方式,使用synchronized实现线程同步class SellTicket03 implements Runnable {     private int ticketNum = 100;    private boolean loop = true;//控制run方法变量     public synchronized void sell() {//同步方法,在在同一时刻,只能有一个线程来执行run方法        if (ticketNum <= 0) {            System.out.println("售票结束...");            loop = false;            return;        }        //休眠50毫秒,模拟        try {            Thread.sleep(50);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票 "                + "剩余票数:" + (--ticketNum));    }     @Override    public void run() {        while (loop) {            sell();//sell方法是一个同步方法        }    }}
8.1互斥锁基本介绍Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性每一个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能有一个线程访问同步的局限性:导致程序的执行效率降低非静态的同步方法,锁可以是this(当前对象),也可以是其他对象(要求锁的是同一个对象)同步方法(静态的)的锁为当前类本身(类.class)

synchronized实现同步的基础:Java中的每一个对象都可以作为锁。

具体表现为以下3种形式。

对于普通同步方法,锁是当前实例对象。

对于静态同步方法,锁是当前类的Class对象。

对于同步方法块,锁是Synchonized括号里配置的对象。

注意事项和细节:同步方法如果没有使用static修饰:默认锁对象为this如果方法使用static修饰,默认锁对象:当前类.class实现的落地步骤:需要先分析上锁的代码选择同步代码块或者同步方法要求多个线程的锁对象为同一个

package li.thread.syn; //使用多线程,模拟三个窗口同时售票共100张public class SynSellTicket {    public static void main(String[] args) {         SellTicket03 sellTicket03 = new SellTicket03();        new Thread(sellTicket03).start();//第1个线程-窗口        new Thread(sellTicket03).start();//第2个线程-窗口        new Thread(sellTicket03).start();//第3个线程-窗口    }} //实现接口方式,使用synchronized实现线程同步class SellTicket03 implements Runnable {     private int ticketNum = 100;    private boolean loop = true;//控制run方法变量    Object object = new Object();     //1.public synchronized static void m1(){}的锁加在SellTicket03.class    public synchronized static void m1(){}    //2.如果在静态方法中,要实现一个同步代码块则应该这样写:(原因是静态方法适合类一起加载的,静态方法不能使用this)    public static void m2(){        synchronized (SellTicket03.class){            System.out.println("m2");        }    }     // public synchronized void sell() {}就是一个同步方法。这时,锁在this对象    //也可以在代码块上写synchronized,同步代码块,互斥锁还是在this对象    public /*synchronized*/void sell() {//同步方法        synchronized (/*this*/object) {//如果是new Object就不是同一个对象            if (ticketNum <= 0) {                System.out.println("售票结束...");                loop = false;                return;            }            //休眠50毫秒,模拟            try {                Thread.sleep(50);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票 "                    + "剩余票数:" + (--ticketNum));        }    }     @Override    public void run() {        while (loop) {            sell();//sell方法是一个同步方法        }    }}
8.2线程的死锁基本介绍:

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁。

在编程中一定要避免死锁的发生。

例子:

package li.thread.syn; //模拟线程死锁public class DeadLock_ {    public static void main(String[] args) {        //模拟死锁现象        DeadLockDemo A = new DeadLockDemo(true);        DeadLockDemo B = new DeadLockDemo(false);        A.setName("A线程");        B.setName("B线程");        A.start();        B.start();    }} class DeadLockDemo extends Thread {    static Object o1 = new Object();//保证多线程,共享一个对象,这里使用static    static Object o2 = new Object();    boolean flag;     public DeadLockDemo(boolean flag) {//构造器        this.flag = flag;    }     @Override    public void run() {        //下面业务逻辑的分析        //1.如果flag为true,线程就会先得到/持有 o1对象锁,然后尝试去获取o2对象锁        //2.如果线程A得不到o2对象锁,就会Blocked        //3.如果flag为false,线程B就会先得到/持有 o2对象锁,然后尝试去获取o1对象锁        //4.如果线程B得不到o1对象锁,就会Blocked        if (flag) {            synchronized (o1) {//对象互斥锁,下面就是同步代码                System.out.println(Thread.currentThread().getName() + "进入1");                synchronized (o2) {//这里获得li对象的监视权                    System.out.println(Thread.currentThread().getName() + "进入2");                }            }        } else {            synchronized (o2) {                System.out.println(Thread.currentThread().getName() + "进入3");                synchronized (o1) {//这里获得li对象的监视权                    System.out.println(Thread.currentThread().getName() + "进入4");                }            }        }    }}

如下图:两个线程卡住了

8.3释放锁

下面操作会释放锁:

当前线程的同步方法、同步代码块执行结束当前线程在同步代码块、同步方法中遇到break、return当前线程在同步代码块、同步方法中出现了未处理的Error或者Exception,导致异常结束当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁

下面的操作不会释放锁:

线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。

提示:应尽量避免使用suspend()和resume()来控制线程,这两个方法不再推荐使用

9.本章作业9.1线程HomeWork01

(1)在main方法中启动两个线程

(2)第一个线程循环随机打印100以内的整数

(3)直到第二个线程从键盘读取了“Q”命令

练习:

package li.thread.syn.homework; import java.util.Scanner; //(1)在main方法中启动两个线程public class ThreadHomeWork01 {     public static void main(String[] args) {         A a = new A();        B b = new B(a);//注意把a对象传入b构造器中         a.start();        b.start();    }} //创建A线程类class A extends Thread {    private boolean loop = true;     @Override    public void run() {        while (loop) {            System.out.println((int) (Math.random() * 100 + 1));            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println("a线程退出...");    }     public void setLoop(boolean loop) {        this.loop = loop;    }} //创建B线程类class B extends Thread {    private A a;    Scanner scanner = new Scanner(System.in);     public B(A a) {        this.a = a;    }     @Override    public void run() {        while (true) {            //接到用户输入            System.out.println("请输入你的指令(Q)表示退出");            char key = scanner.next().toUpperCase().charAt(0);            if (key == 'Q') {                //以通知的方式结束a线程                a.setLoop(false);                System.out.println("b线程退出...");                break;            }        }    }}
9.2线程线程HomeWork02

(1)有两个用户分别从同一张卡上取钱(总额10000)

(2)每次都取1000,当余额不足时,就不能取款了

(3)不能出现超取现象==>线程同步问题

易错点:关于互斥锁的理解

对于普通同步方法,锁是当前实例对象

对于静态同步方法,锁是当前类的Class对象

对于同步方法块,锁是Synchonized括号里配置的对象

package li.thread.syn.homework; public class ThreadHomeWork02 {    public static void main(String[] args) {        T t = new T();        Thread thread1 = new Thread(t);        Thread thread2 = new Thread(t);        thread1.setName("t1");        thread2.setName("t2");        thread1.start();        thread2.start();    }} class T implements Runnable {    private int money = 10000;     @Override    public void run() {        while (true) {//while不要放到同步代码块里面            //1.使用了synchronized实现线程同步            //2.当多个线程执行到这里的时候就会去争夺 this对象锁            //3.哪个线程争夺到(获取)this对象锁,就执行synchronized代码块            //4.争夺不到this对象锁,就Blocked,准备继续争夺            //5.this对象锁是非公平锁            synchronized (this) {                if (money <= 0) {                    System.out.println("余额不足...");                    break;                }                money -= 1000;                System.out.println(Thread.currentThread().getName() + "取出了1000元" + " 当前余额为:" + money);            }            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

标签: #java中线程的作用