java中的线程

157 阅读18分钟

java中的线程

1.线程入门简介:

 线程相关概念:
1. 程序:实现功能的代码(静态)
2.
(1)..进程:是指正在运行中的程序!(动态),比如:我们使用的qq,打开qq后就相当于启动了一个进程,
这时操作系统就会为该进程分配空间。
eg:当我们启动迅雷,又启动了一个程序,该操作系统将为迅雷分配新的内存空间;
(2).进程是程序的(一次)执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生,存在,消亡
(当我们打开qq界面:进程产生,关闭qq:进程消亡,释放空间);
只要进程产生:就会吃掉一定的内存,cpu;
进程关闭(消亡):就会释放
3.线程:
(1.).线程是由进程创建的,是进程的一个实体;
(2.)一个进程可以有多个线程:比如在迅雷同时下载多个文件:迅雷:进程;几个下载任务:就是几个线程

线程其它概念:
4.线程:
(1.)单线程:同一个时刻,只允许执行一个线程;
(2.)多线程:同一个时刻,可以执行多个线程;
eg:一个qq进程,可以同时打开多个聊天窗口,一个迅雷,可以同时下载多个文件

5.并发:同一时刻,多个任务!交替!执行,造成一种“貌似同时”的错觉,简单的说,单核CPU实现的多个任务就是并发;
但其实:在具体的时刻只会执行一个,反复切换
CPU--->qq
       迅雷

6.并行:同一时刻,多个任务同时执行。多核CPU可以实现;
CPU--->qq
CPU--->迅雷
eg:qq和迅雷同时执行

并行也有可能出现并发!:比如两个CPU使用完的情况下,又来一个任务,则其中一个CPU要在两个任务间反复切换;

2.线程的基本使用

public class Thread01 {
    /*
    !!!!!!!!!!!!!!!!!
    线程的基本使用:!!!!!!!!!!!!!!!!!!!!!!!!!!!Q
    1.线程使用的两种方式
    (1.)a:继承线程Thread类,b:重写run方法
    (2.)实现Runnable接口,重写run方法

    Runnable接口
       |
       |
     Thread

  2.(1.)
  多线称机制说明(!!!)
  *1:当点击run运行这个程序时:相当于开启了一个进程!
     */

    //下面我们演示1.(1.)a:继承Thread,b:重写run方法!!!!!!!!!!!!!


    public static void main(String[] args) throws InterruptedException {//*2:进入main方法:相当于开启main线程


        //创建对象::可以当作线程使用
        Cat cat=new Cat();
        cat.start();//启动线程-->最终会执行cat的run方法:相当于创建启动一个线程!!!
        //真正实现线程的方法:start0();
        // (如:在迅雷开启一个下载任务!!)

        //cat.run();注意:这里run()方法就是普通的方法,如果调用它,他会先把run方法执行完毕后才会执行下面的代码
        //所以它没有实现启动多线程(同一时刻可以执行多个线程)--->即会发生阻塞

        //*3.:执行到start()方法:main线程就会开启一个子线程:Thread01,
        //且子线程并不会阻塞main线程,main线程会继续执行;!!!!!!!!!!
        //即main线程和子线程:Thread0会交替执行!!!!!!!
        //eg:下面的for演示:
        System.out.println("主线程会继续执行,不会阻塞,主线程名字:"+Thread.currentThread().getName());
        for (int i=0;i<10;i++){
            System.out.println("主线程i="+i);

            Thread.sleep(1000);//我们也可以让主线程休眠1s,有异常:可以catch或throws;
        }

        //*4.在多线程中并不一定主方法结束,进程就结束了,如果还有子线程,还会运行
        //只有所有的线程都结束了,进程才会结束

    }
}
class Cat extends Thread {////演示:(1.)a:继承线程类Thread
    //当一个类继承了Thread类,该类就可以当作线程使用--->:Cat就可以当作线程使用了

    public void run() {//b:重写run方法:写上自己的业务逻辑(run方法)
        int times=0;
        while (true) {

            //该线程每隔1s。在控制台输出:”喵喵,我是kk“,
            System.out.println("喵喵,我是kk"+(++times)+"线程的名字"+ java.lang.Thread.currentThread().getName());
            //休眠1s:使用sleep();方法:它是ms单位:Thread.sleep(1000);
            try {
                java.lang.Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            if (times==8){//当times=8时就退出while循环--->也就退出了线程;!!!
                break;
            }
        }
    }

}

3.多线程使用案例:

线程使用案例3.:多个子线程

    多线程的执行:原来两个案例我们都启动了1个子线程,现在我们启动2个子线程!

    案例:编写一个程序,创建两个线程,一个线程每隔1s输出:"hello,world";,输出10次退出
    ,另一个每隔1s输出“hi”,输出5次退出(使用方式2:实现接口)

   分析:1.创建两个类分别实现R接口!!
        2.在main线程启动两个子线程!!!
     */
    public static void main(String[] args) {//1.mian线程

        //最后启动两个子线程
        T1 t1=new T1();//创建实现R接口类:T1的对象:
        Thread thread=new Thread(t1);//代理实现start方法,因为T1没有
        thread.start();//Thread-0线程;

        T2 t2=new T2();//创建实现R接口类:T2的对象:
        Thread thread1=new Thread(t2);//代理实现start方法,因为T1没有
        thread1.start();//Thread-1线程

        //综上:上面的两个子线程是交替进行的---->即多线程!!!!
    }
}
class T1 implements Runnable{//类1:实现R接口-->:启动线程
    int count=0;

    //2.子线程1:每隔1s输出:"hello,world";,输出10次退出
    public void run(){
        while(true){
            System.out.println("hello,word"+(++count)+"它的线程是:"+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);//休眠1s
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if(count==10){
                break;
            }
        }
    }
}
class T2 implements Runnable{//类2:实现R接口
    //3.子线程2:每隔1s输出:"hi";,输出5次退出
    int num=0;
    public void run(){
        while(true){
            System.out.println("hi"+(++num)+"它的线程是:"+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);//休眠1s
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if(num==5){
                break;
            }
        }
    }
}

4.练习

(1)售票系统初级方法:

多线程练习:模拟售票系统
    eg:使用多线程模拟三个窗口!同时!售票100张;
    分别使用两种方式实现


     */
    public static void main(String[] args) {
        //因为模拟三个窗口:所以创建三个对象
        SellTickets sellTickets=new SellTickets();
        SellTickets sellTickets1=new SellTickets();
        SellTickets sellTickets2=new SellTickets();

        sellTickets.start();//启动main子线程1
        sellTickets1.start();//启动main子线程2
        sellTickets2.start();//启动main子线程3
        //三个子线程同时执行----->:即多线程!!!!!!!!!!!!
        /*
        窗口Thread-2售出1张票还剩99张票
        窗口Thread-0售出1张票还剩99张票  //三个子线程同时执行----->:即多线程!!!!!!!!!!!!
        窗口Thread-1售出1张票还剩99张票
         */
  // 解析:因为是同时执行:当只有1/2张时三个子线程同时进入剩余的票数:因为票数<3:所以就会超卖!!!!


        //方式2:实现接口方式:也会超卖2张,和上面的原因一样
        //(当只剩下1张后,第一个线程会先卖出,然后休眠,现在本身已经没票了,
        // 但是3个线程是同时进入的(同时进入if判断的!!!),第一个线程无法阻止其它两个子线程,所以会超卖2张)

    }
}

//方式1:继承Thread
class SellTickets extends Thread{//继承Thread

    //方式2:实现R接口

//class SellTickets implements Runnable{


    private static int ticketNum=100;//使用static:让三个线程共享!!!!!!!!!!

    public void run(){//重写run方法
        while(true){
            if(ticketNum<=0){
                System.out.println("售票结束");
                break;
            }

            try {
                Thread.sleep(50);//休眠50ms
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            //如果有票:就卖
            System.out.println("窗口"+Thread.currentThread().getName()+"售出1张票"
            +"还剩"+(--ticketNum)+"张票");

            }
        }
    }

(2)改进1:

 线程终止:
    使用背景:eg上个售票系统:我们希望票售完后会自动停止,避免出现多售的情况;

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

总结:如果我们希望A线程去通知B线程退出,只需要在A线程中可以控制B线程的变量即可:可以在B中使用set方法
然后在A线程中调用方法实现
     */
    public static void main(String[] args) throws InterruptedException {
        A a=new A();//创建线程对象
        a.start();//启动线程
        //如果希望在main线程控制A线程的终止:就必须可以修改loop变量!!!!!:--->:使用set方法
        //让A线程退出run方法,从而中止A线程-->:通知方式

        System.out.println("休息10s.....");//让main线程休息10s,但不会影响下面的A线程的执行!!!
        Thread.sleep(10*1000);
        a.setLoop(false);//传入false//直接将循环条件改变
        //实现了在main线程中控制另一个线程:A!!!!!!!!!!
    }
}
class A extends Thread{//继承
    private boolean loop=true;
    int count=0;
    public void run(){
        while(loop){//放入循环中:loop=true,会一直执行
            System.out.println(++count);
            try {
                Thread.sleep(1000);//休眠1s
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
    public void setLoop(boolean loop){//!!!通过set方法在main中实现修改loop!!!
        this.loop=loop;

    }

}

(3)改进

5.守护线程:

  用户线程和守护线程:
    1.用户线程:也叫工作线程,当线程的任务执行完或以通知方式结束
    2.守护线程(DaemonThread):一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束;
    (不用使用通知才结束,由线程机制觉得,main结束后,子线程发现已经不需要守护了)
    3.常见的守护线程:垃圾回收机制;

    守护线程使用背景:比如我们有一个子线程和main线程,其中子线程是无限循环,当main线程执行完毕后,子线程并不会停止
   但是如果现在我们已经不需要子线程了--->:因此我们可以使用守护线程:来实现当main线程结束之后,子线程可以自动退出

    演示
     */
    public static void main(String[] args) throws InterruptedException {


        myDaemon m=new myDaemon();//创建子线程:m
        //!!!如果我们希望main线程结束后,子线程myDaemon(无限循环)也能自动结束;!!!
        //只需将线程设置为守护线程即可:--->:即使用m.setDaemon(true);方法!!!
        m.setDaemon(true);
        m.start();//启动线程!!!


        //注意:子线程的创建必须放到上面,否则他会先执行完main线程的for循环再创建子线程
        //且(因为这里是守护线程,所以执行完main线程中的for后就结束了,自线程相当于没有创建和执行)

        //main线程:
        for(int i=1;i<=10;i++){
            Thread.sleep(1000);//main线程也休眠1s
            System.out.println("你干嘛啊啊啊啊..."+i);
        }

    }

}
class myDaemon extends Thread{//子线程:myDaemon
    public void run() {

        for (; ; ) {
            try {
                Thread.sleep(50);//休眠1s
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("坤坤跳舞...");
        }
    }
}

6.线程同步机制

 线程同步机制:

    1.”同步“:在多线程中,一些敏感的数据不允许有多个线程同时访问,此时就使用同步访问技术,
    保证数据在任何时刻,最多只有一个线程访问,以保证数据的完整性。


   2.线程同步 解释:比如在同一时刻,当有一个线程在对内存进行操作时,其它线程都不可以对这个内存的·地址进行操作
   直到该线程完成操作,其它线程才可以进入;
    即


    3.同步具体方法:---*>:Synchronized
    (1.)同步代码块:
    synchronized(对象){//得到对象的锁,才能操作同步数据!!!!!
            //需要被同步的代码
    }

    (2.synchronized还可以放在方法声明中,表示整个方法 为 同步方法
    public synchronized void m(String name){
                //需要被同步的代码
    }


     !!!!使用背景:eg买票存在超卖,与三个窗口同时卖1张票的的问题:
     防止三个线程同时进入买票系统----->:这样卖出的票有顺序且不会重复卖与超卖
     */
    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(1000);
        MM sellTickets=new MM();
        MM sellTickets1=new MM();
        MM sellTickets2=new MM();

        sellTickets.start();//启动main子线程1
        sellTickets1.start();//启动main子线程2
        sellTickets2.start();//启动main子线程3


    }
}


//解决买票溢出问题---->:使用synchronized实现线程同步!!!!!!!!

class MM extends Thread{
    Boolean loop=true;//增加一个控制变量---->:通知子线程结束!!!!!!!!!
    //类似于:通知退出!!!!!!!
    private static int ticketNum=100;//使用static:让三个线程共享!!!!!!!!!!


    //说明:a:public synchronized void sell(){};--->就是一个同步方法(s在方法中)
    //     b:这时互斥锁在当前对象(this
    //     c:也可以在代码块中写synchronized;---->:就是一个同步代码块
    //     eg: synchronized(this){}:互斥锁还在当前对象,{}将子线程全部包括即可!!!
    public synchronized void sell(){//锁加在当前对象上!!
        // 有多个线程进来的时候,把他们全都堵在这,只有1个锁,
        // 一个线程(拿锁)执行完毕后,再让另一个线程进入

        // 使用synchronized同步方法:在同一时刻,只能有一个线程来执行sell方法!!!!
        // 给方法上锁,只有拿到这个锁才能执行sell方法!!!

        //如果没有s,就会超买!

        if(ticketNum<=0){//所以就不会三个同时进入判断!!!
            System.out.println("售票结束");

            loop=false;//loop设置为false--->:退出循环!!!!

           return ;
        }

        try {
            Thread.sleep(1000);//休眠50ms
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        //如果有票:就卖
        System.out.println("窗口"+Thread.currentThread().getName()+"售出1张票"
                +"还剩"+(--ticketNum)+"张票");

    }
    public  void run(){//!!!!


        while(loop){

            sell();//调用sell方法
        }
    }
}

7.练习

(1.)

import java.util.Scanner;

public class Thread12Exe {
    /*
    练习:
    在main方法中启动2个线程
    第一个线程循环随机打印100以内的int
    直到第二个线程从键盘上读取了”Q“命令

    """"""""""""使用守护机制练习""""""""""""""

    分析:可知:1.要实现在第二个线程中控制第一个线程--->:通知方式
    在BBB线程必须持有AAA线程的对象:以通知的方式终止BBB线程的run方法:!!!退出了run方法也就退出了线程
    ---》:通过修改变量
    2.先创建两个线程对应的类:AAA,BBB
     */
    public static void main(String[] args) {
        Scanner myScanner=new Scanner(System.in);
       AAA aaa=new AAA();
       aaa.start();//启动AAA线程;

        BBB bbb=new BBB(aaa);
        bbb.start();//启动BBB线程;
        //注意://!!!将线程AAA的对象传给线程BBB,才能够在BBB线程中控制AAA线程!!!

    }
}
class AAA extends Thread{//创建子线程1:AAA类:循环输出内容1-100;

    private boolean loop=true;
    public void run(){
        while(loop){
            try {
                Thread.sleep(1000);//休眠1s
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("随机数:"+(int )(Math.random()*100+1));//输出1-100int
        }

    }
    public void setLoop(Boolean loop){//提供set方法:让BBB线程类调用来通知AAA线程退出!!
        this.loop=loop;
    }
}

class BBB extends Thread{//子线程2:BBB类:当第二个线程从键盘上读取了”Q“命令时,
    // 通知AAA线程结束(即退出run方法)

    Scanner myScanner=new Scanner(System.in);//创建Scanner对象
    private AAA aaa;//在BBB线程必须持有AAA线程的对象:!!!

    public BBB(AAA aaa) {//构造器
        this.aaa = aaa;
    }

    public void run(){

        while(true){
            //接收到用户的输入
            while(true) {
                System.out.println("输入key,输入Q时退出:");
                char key = myScanner.next().toUpperCase().charAt(0);//输入

                if(key=='Q'){//当输入“Q”时,以通知的方式结束AAA线程!!!!!!
                    //只要能够修改AAA的loop变量即可!
                    aaa.setLoop(false);//调用set方法修改loop--->:终止AAA线程!!!!!

                    System.out.println("BBB线程也退出");
                    break;
                }
            }
        }
    }

}

(2.)

public class Thread1202Exe {
    /*
    练习2:
    有两个用户分别从一张卡上取钱(总额10000)
    每次都取出1000,当余额不足时,就不能取款了
    不能出现超取的情况

    分析:1.不能超取:--》应该使用线程同步问题!!!
    (解决敏感数据--在同一个时间只允许有一个线程去操作数据,防止超取,来保证数据的统一性)
    2.因此可以通过放一把锁来限制子线程,只有获取锁的线程才可以进入run方法(代码)去
    操作数据,其它线程都会被阻塞

    !!!总节:所有的线程来了以后,不能让他们立即进入代码执行数据,而是放一个锁先堵住他们。
    只有获取锁的线程才可以进入run方法(代码)去操作数据,其它线程都会被阻塞等待


    1.创建两个窗口:--即两个子线程分别操作数据money
    2.要操作的敏感数据:money;
     */
    public static void main(String[] args) {
        TT1 tt=new TT1();
        //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        //创建1个线程类TT1对象,然后将同一个tt对象放入两个线程中----,所以三两个线程操作的是同一个对象
        //所以就实现了,处理的是同一个空间下的数据

        Thread thread=new Thread(tt);//创建两个线程
        Thread thread1=new Thread(tt);

        thread.start();
        thread1.start();

        //注意:因为要对同一个代码:即钱操作,所以写一个类即可!!!!!!!!!!
        //创建两个线程:都对下面run代码进行操作:因为面对的是同一个10000块
    }
}
class  TT1 implements  Runnable{//子线程1:TT1类

    private  int money=10000;//money

    public  void   run(){
        while(true){


            /*
            两个线程同时start;
            1.这里s实现了线程同步。
            2.当多个线程执行到这时:就会去争夺 this对象锁(非公平锁)
            3.哪个线程争夺到了(获取)this对象锁,就执行s代码块!!!!!!(执行完后会释放this对象锁,方便给下一次争夺)
            4.如果争夺不到this对象锁的就阻塞在这里,准备下一次争夺;
            给关键数据加上锁:-->:相当于哪个对象抢到了这个锁,this就是指的哪个对象;--->:
            这样就实现了,同一时刻只允许1个线程执行money。


             *///重点:就是将要争夺的代码放上锁!!!!!!!!!!!
            synchronized (this) {
                if (money < 1000) {//先判断:如果<1000:就退出
                    System.out.println("余额不足");
                    break;
                }
                //如果余额充足,就取钱
                money -= 1000;
                System.out.println(Thread.currentThread().getName() + "取了1000¥");
                System.out.println("当前余额还剩:" + money + "¥");

            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

8.线程常用方法

(1.)方法1

    1.yield(静态方法):Thread.yield();
    线程的礼让。让出CPU,让其他线程执行,但礼让的时间不确定,所以也不一定成功。

    比如:有两个线程t1,t2,如果t1使用yield()方法,即相当于告诉编译器先执行t2线程,但礼让时间即概率不一定成功
    如果资源有限就可能不会成功,资源充足可能成功

    2.join:
    线程插队。插队的线程一旦成功,则肯定执行完插入的线程所有的任务。
    eg:在t1线程中调用了t2方法(调用对方的join方法:因为是要对方插过来)

    意味:比如现在t1和t2线程正在进行中,然后再t1线程中的某个位置调用了t2.join()方法
    则这时相当于t1主动放弃CPU,让它将t2全部执行完毕后,再回头执行t1线程,一定会成功;


    案例:在main线程中创建1个子线程,每隔1s输出hello,输出20次。主线程每隔1s,输出hi,输出20次;
    要求:两个线程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续执行
     */
    public static void main(String[] args) throws InterruptedException {
        TT tt=new TT();//创建子线程
        tt.start();//启动子线程


        //主线程
        for(int i=1;i<=20;i++){
            Thread.sleep(1000);
            System.out.println("hi"+i);
            if(i==5){//因为我要让tt子线程先执行完毕再执行main线程,!!!!!1
                System.out.println("main线程将CPU让给tt线程,,即让tt线程插队");
                tt.join();//所以就直接让tt子线程插队
            }
        }
        //因为主线程和子线程的休眠时间一样都是:1s,所以会交替执行!!!!!!!!!!!!!
    }
}
class TT extends Thread{
    public void run() {

        for (int i = 1; i <= 20; i++) {
            try {
                Thread.sleep(1000);//休眠1s
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("hello"+i);

        }
    }
}

(2.)方法2

public class Thread05Methods {
    /*
    线程常用方法:

    一:常用方法第一组:
    1.setName


   二:细节:
   1.start底层会创建新的线程。  而调用run,run就是一个简单的方法调用,不会启动新的线程
   2.线程优先级的范围:
   MAX_PRIORITY:10
   MIN....:1
   NORM....:5
   3.interrupt:中断线程(!不是终止),但并没有真正的结束线程。所以一般用于中断正在休眠的线程
     eg:t线程正在休眠,我们调用:t.interrupt();-->:可以终止休眠!,使线程继续执行
     即中断休眠:使它继续工作!
   4.sleep:使当前线程休眠

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

        T t =new T();//创建线程对象
        t.setName("kunkun");//方法1;setName:设置线程名字
        t.setPriority(Thread.MAX_PRIORITY);//设置线程优先级
        t.start();//启动线程;


        //现在要求:当main线程输出5个hi后,就中断子线程的休眠;
        //main线程:
        for (int i=0;i<5;i++){
            Thread.sleep(1000);
            System.out.println("hi..."+i);
        }
        //执行完for:打印5个hi
        t.interrupt();//中断T线程休眠,继续执行程序!!!!!!

        System.out.println(t.getName()+" 优先级"+t.getPriority());//获取线程优先级


    }
}
class T extends Thread{//继承线程--》:自定义线程类
    public void run() {

        while (true) {
            for (int i = 0; i < 100; i++) {
                //Thread.currentThread().getName():返回线程名称
                System.out.println("线程名字:" + Thread.currentThread().getName() + "吃包子" + i);
            }


            try {
                System.out.println(Thread.currentThread().getName()+"休眠中........");
                Thread.sleep(20000);//休眠20s
            } catch (InterruptedException e) {
                //当线程执行到interrupt方法时:就会catch一个异常,可以加入自己的业务逻辑
                //interruptedException e:是捕获到的一个中断异常(不是终止!!!)
                System.out.println(Thread.currentThread().getName() + "被interrupt");
            }
        }
    }

}