韩顺平Java自学笔记 线程

发布于:2023-01-23 ⋅ 阅读:(330) ⋅ 点赞:(0)

目录

一。并发和并行

二。有关于线程的基本原理

1.第一种创建线程的方式

2.多线程机制

3.第二种创建线程的方式

四。创建线程后的操作

1.线程终止

2.线程的中断

3.线程插队

4.守护线程

五。线程的七大状态

六。线程的同步机制

1.使用场景和作用

九。线程锁

1.互斥锁

2.死锁

3.释放锁

十。最后例题

1.作业一

2.作业二


一。并发和并行

个人理解:多线程就是一个程序可以干很多事

单线程:同一时间,只允许执行一个线程

多线程:同一时间,可以执行多个线程  

例如:一个QQ可以进行多个聊天进程,一个迅雷下载可以进行多个下载的人物。简单来说多线程就是一个程序可以在同一时间执行多个任务。

并发与并行

并发:同一时间多个任务交替执行,造成貌似在同时处理多件事的效果。

例如:一个人既在开车又在打电话

 

例如:单核cpu处理多个进程。

并行:同一时间多个cpu处理多个进程。多核cpu实现并行。

例如:一个人打电话一个人开车

补充:可以通过任务管理器查看电脑进程,可以通过 任务管理器——性能——打开资源监控器——cpu    可以查看自己电脑cpu的状态

通过下列代码可以查看自己电脑cpu为几核

public class CpuNum {
    public static void main(String[] args) {
        Runtime runtime = Runtime.getRuntime();
        int i = runtime.availableProcessors();
        System.out.println(i);
    }
}

二。有关于线程的基本原理

1.第一种创建线程的方式

一共有两种创建方式,实现接口的更具有广泛性(更好使)

继承Thread类,然后重写run()方法

public class Thread1 {
    public static void main(String[] args) {
        Mao mao = new Mao();
        mao.start();
        System.out.println("线程的名字是"+Thread.currentThread().getName());
    }
}

class Mao extends Thread{
    @Override
    public void run() {
        System.out.println("猫猫在喵喵叫。。。");
        System.out.println("线程的名字是"+Thread.currentThread().getName());
    }
}

问题:为什么调用使用的是start方法,而不是run,原因如下图,真正创建线程的是start方法当中的start0方法,而不是run方法。

运行结果:

线程的名字是main
猫猫在喵喵叫。。。
线程的名字是Thread-1

补充:我们通过下面两行代码获取了线程的名字,以证明确实是创建了线程的.其中main线程是主方法自带的线程。Thread-1是我们通过继承Thread创建的

System.out.println("线程的名字是"+Thread.currentThread().getName());

例题:

 

注意:JConsole我跳过了,没有学。sorry。 

休眠方法:参数为毫秒数1000ms=1s  也就是间隔时间1执行一次

Thread.sleep(1000);

注意:该方法会报 InterruptedException异常所以要进行cry/catch或者异常抛出(方法写在run()方法中)

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

答案:

public class Thread1 {
    public static void main(String[] args) {
        Mao mao = new Mao();
        mao.start();
    }
}

class Mao extends Thread{
    private int count = 0;
    private boolean bool = true;
    @Override
    public void run() {
        while (bool){
            if (count==80){
                bool =false;
                return;
            }
            System.out.println("喵喵,我是小猫咪!"+(++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

 通过while循环,通过bool的值来控制循环,当退出循环hou,run()方法执行完毕,线程关闭。(稍后会讲解使用查看线程状态的方法)

2.多线程机制

修改一行代码,查看线程名

            System.out.println("喵喵,我是小猫咪!"+(++count)+Thread.currentThread().getName());

得到:

喵喵,我是小猫咪!1线程名为Thread-0
喵喵,我是小猫咪!2线程名为Thread-0
喵喵,我是小猫咪!3线程名为Thread-0

 下图为线程之间的关系,通过start()创建的子线程

 对答案代码进行修改

public class Thread1 {
    public static void main(String[] args) throws InterruptedException {
        Mao mao = new Mao();
        mao.start();

        System.out.println("主线程执行"+Thread.currentThread().getName());

        for (int i = 0;i<10;i++){
            System.out.println("主线程 i="+i);
            Thread.sleep(1000);
        }
    }
}

class Mao extends Thread{
    private int count = 0;
    private boolean bool = true;
    @Override
    public void run() {
        while (bool){
            if (count==80){
                bool =false;
                break;
            }
            System.out.println("喵喵,我是小猫咪!"+(++count)+"线程名为"+Thread.currentThread().getName());
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 执行会发现,主线程与子线程一起执行并且主线程是否结束执行不会影响到子线程的执行。

3.第二种创建线程的方式

使用实现Runnable接口的方法来实现多线程

说明:Java是单继承,为避免已经有继承的父类,而无法使用第一种方法,所以创建了Runnable接口。

使用:

public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Thread thread = new Thread(dog);
        thread.start();
    }
}

class Dog implements Runnable{


    int count = 0;

    @Override
    public void run() {
        while (true){
            try {
                System.out.println("小狗创造。。");

                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            count++;
            if (count==100){
                break;
            }
        }

    }
}

注意:相比于第一种方法,第二种方法的使用多了如下步骤

        Thread thread = new Thread(dog);
        thread.start();

问题:为什么放入参数后,可以用thread直接调用start方法创建新的线程

使用了线程代理,原理如下:


public class Thread02 {
    public static void main(String[] args) {

        Tiger tiger = new Tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start0();
    }
}
//线程代理类
class ThreadProxy implements Runnable{

    private Runnable target = null;

    @Override
    public void run() {
        if (target!=null){
            target.run();
        }
    }

    public ThreadProxy(Runnable target) {  //runnable的实例传入
        this.target = target;
    }

    public void start(){
        start0();
    }
    public void start0(){
        run();
    }
}

class Animal{

}

class Tiger extends Animal implements Runnable{

    @Override
    public void run() {
        System.out.println("老虎在嗷嗷叫。。。");
    }
}

更为具体的可以看原视频和方法的源码 

案例

创建两个子线程,一个循环十次,一个循环五次,休眠时间都为1s

public class Thread03 {
    public static void main(String[] args) {


        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread = new Thread(t1);
        Thread thread1 = new Thread(t2);
        thread.start();
        thread1.start();
    }
}

class T1 implements Runnable{
        int count =0 ;

    @Override
    public void run() {
        while (true){
            System.out.println("hi!"+(++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){
            System.out.println("hello word!"+(++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count==5){
                break;
            }
        }

    }
}

主线程启动结束之后就结束了,但是不影响其他线程的执行

总感觉像是一家人,但是谁都不理谁,各干各的样子

 Thread与Runnable之间的区别和使用范围

图片中的代码建议使用Runnabe因为只有一个对象t3

实际案例:多线程售票窗口案例

public class SellTicket {
    public static void main(String[] args) {
        SellTicket01 sellTicket1 = new SellTicket01();
        SellTicket01 sellTicket2 = new SellTicket01();
        SellTicket01 sellTicket = new SellTicket01();

        Thread thread = new Thread(sellTicket);
        Thread thread1 = new Thread(sellTicket1);
        Thread thread2 = new Thread(sellTicket2);
        thread.start();
        thread1.start();
        thread2.start();
    }
}

class SellTicket01 implements Runnable{

    private static int  ticketNum = 100;

    @Override
    public void run() {
        while (true){
            if (ticketNum<=0){
                System.out.println("售票结束");
                break;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(" 窗口 "+Thread.currentThread().getName()+" 售出一张票 "+
                    " 剩余票数 "+(--ticketNum));
        }

    }
}

 执行结果

 窗口 Thread-0 售出一张票  剩余票数 3
 窗口 Thread-1 售出一张票  剩余票数 2
 窗口 Thread-2 售出一张票  剩余票数 1
 窗口 Thread-1 售出一张票  剩余票数 -1
 窗口 Thread-2 售出一张票  剩余票数 -2
售票结束
 窗口 Thread-0 售出一张票  剩余票数 0
售票结束
售票结束

发现问题:出现负数

原因:设现在还有一张票

仨线程几乎同时完成if判断(判断是否还有票),但是还没有进行减减操作,线程一-1,票数为0,线程二三再各自减一,票数变为-1。关键点在于,在第一个线程没有执行完毕方法的时候其他线程就进行了if判断。

四。创建线程后的操作

1.线程终止

本质是控制循环,而不是直接解决掉子线程

基本说明:

1.线程执行结束后自动终止

2.我们使用参数,来控制它终止

案例:十秒后子线程终止(使用Boolean来控制循环)

public class ThreatExit {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        Thread thread = new Thread(t);
        thread.start();

        Thread.sleep(1000*10);

        t.setBool(false);
    }
}

class T extends Thread{

    private int count = 0;

    private boolean bool = true;

    public void setBool(boolean bool) {
        this.bool = bool;
    }

    @Override
    public void run() {
        while (bool){
            System.out.println("程序运行中...  "+(++count));
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

重点:主线程在休眠的时候会停止向下执行代码

2.线程的中断

决定线程什么时候醒过来

线程的常用方法

注意事项和细节

个人的一些见解:setName方法非常的好玩,因为你可以将线程起成自己喜欢的,如:理塘小马之类的。interrupt可以中断休眠,叫醒的闹钟。线程的优先级:可以在高优先级的线程当中随意的操作低优先级的线程。

 

 案例:输出5个hi之后终止休眠(interrupt的使用

public class ThreadMethod {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setName("老韩");

        t.setPriority(Thread.MIN_PRIORITY);
        t.start();
//主线程输出5个hi我就中断休眠
        for (int i =0;i<5;i++){
            Thread.sleep(1000);
            System.out.println("hi "+i);
        }
        t.interrupt();
    }
}

class T extends Thread{
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "吃饱了。。。"+i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + " 休眠了。。。。。");
                Thread.sleep(10 * 1000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "被 interrupt了");
            }
        }
    }
}

理解:interrupt更像是使sleep出现异常,所以程序终止,结束休眠。

3.线程插队

决定那个线程先执行

第二组常用的方法

注意: 两种插队方法,一种是礼让,不一定成功的,一种是强行的,一定会插队成功。

案例:

public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        T2 t2 = new T2();
        t2.start();

        for (int i =1;i<=20;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主线程 吃了"+i+" 包子 ");
            if(i==5){
                System.out.println("主线程(小弟) 让 子线程(大哥)吃");
                t2.join();
                System.out.println("主线程吃完了,让子线程接着吃");
            }
        }
    }
}

class T2 extends Thread{
    @Override
    public void run() {
        for (int i =1;i<=20;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("子线程 吃了"+i+" 包子 ");
        }
    }
}

将join改为yield之后,发现子线程与主线程依旧是混着吃包子的。因为cpu的算力是足够的,也就是包子够吃,所以不用礼让。

补充:join是实例方法,yield是静态方法

4.守护线程

创造一个线程管理员,管理其他线程(甚至可以决定其他线程的生死)什么独裁者设定。哦,这更像是古代所说的陪葬。

理解:一个充当工具的作用,一个充当管理工具的作用。 

myDemoThread.setDaemon(true);
来设置守护线程

案例:主线程结束之后,子线程结束

public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        MyDemoThread myDemoThread = new MyDemoThread();
        myDemoThread.setDaemon(true);
        myDemoThread.start();
        for (int i =1;i<=10;i++){
            System.out.println("宝强在辛苦的工作。。。");
            Thread.sleep(1000);
        }
    }
}

class MyDemoThread extends Thread{
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("郭靖和黄蓉在一起愉快的聊天。。哈哈哈哈。。。");
        }
    }
}

 理解:直接结束start0开启的子线程,还是很有力量的方法。

五。线程的七大状态

1.看看jdk是如何说的

JDK当中有六种,但实际从运行原理来看,有七种,因为运行状态可以再细分两种

如下图(非常重要)

 

简单来说有:创造销毁和四种中间状态,等待,有具体时间的等待还有阻塞,六种状态,运行由分为准备和运行,注意各个状态之间的方法。补充一点:时间等待的方法都是有Interrupted(打断)异常的。毕竟计划是可以被打断的

案例:只要子线程不终止就一直偷看他的状态 什么偷窥狂

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 (Thread.State.TERMINATED!=t.getState()){
            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() {
        while (true){
            for (int i = 1;i<=10;i++){
                System.out.println("hi "+ i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }

    }
}

六。线程的同步机制

1.使用场景和作用

 理解:就像是和小伙伴去上厕所,先关上门(上锁),完事之后再出来(解锁),其他小伙伴才能进去。

注意:写入的对象应该是同一个,如果是不同的对象,那么将不会有一个公共的值。 

 2.同步代码块实现

synchronized(对象){ //得到对象的锁才能操作这个代码

   //需要被同步的代码

}

 案例:解决售票问题

public class SellTicket {
    public static void main(String[] args) {
        SellTicket01 sellTicket1 = new SellTicket01();

        Thread thread = new Thread(sellTicket1);
        Thread thread1 = new Thread(sellTicket1);
        Thread thread2 = new Thread(sellTicket1);
        thread.start();
        thread1.start();
        thread2.start();
    }
}

class SellTicket01 implements Runnable {

    private static int ticketNum = 50;

    private boolean bool = true;

    public synchronized void sell() {
        if (ticketNum <= 0) {
            System.out.println("售票结束");
            bool=false;
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(" 窗口 " + Thread.currentThread().getName() + " 售出一张票 " +
                " 剩余票数 " + (--ticketNum));
    }

    @Override
    public void run() {
        while (bool) {
            sell();
        }
    }
}

3。放在方法声明当中实现

public synchronized void m(String name ){

   //需要被实现的代码块

}

案例:售票问题修改

public class SellTicket {
    public static void main(String[] args) {
        SellTicket01 sellTicket1 = new SellTicket01();

        new Thread(sellTicket1).start();
         new Thread(sellTicket1).start();
         new Thread(sellTicket1).start();

    }
}

class SellTicket01 implements Runnable {

    private static int ticketNum = 50;

    private boolean bool = true;

    public void sell() {
        synchronized (this){
            if (ticketNum <= 0) {
                System.out.println("售票结束");
                bool=false;
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(" 窗口 " + Thread.currentThread().getName() + " 售出一张票 " +
                    " 剩余票数 " + (--ticketNum));
        }

    }

    @Override
    public void run() {
        while (bool) {
            sell();
        }
    }
}

补充:创建Object类成员变量,可以将this变为object 

九。线程锁

1.互斥锁

保证任何时刻都只能有一个线程操作这个对象(莫名的纯爱)一个对象一把锁,看那个线程能讨得对象的欢心得到锁了

还有公平锁和非公平锁

t1,t2,t3来抢这把线程锁,抢到的就可以执行线程,执行完毕,归还锁,然后再重新互相抢夺一次。 

锁是用来保证线程安全的,同一个公共数据,同一时间只能由一个对象操作。

基本的介绍

补充:静态方法的锁是加在class类上的(反射当中的内容)

有两种静态的写法如下: 


    public static synchronized void m1(){}
    
    public static void m2(){
        synchronized (SellTicket01.class){
            System.out.println("m2");
        }
    }

注意事项

注意:有限选择同步代码块,因为范围小,范围越小,效率越高。 

2.死锁

建议把小孩打一顿

案例:两个人受伤都拿着对方想要的东西

public class DeadLock_ {
    public static void main(String[] args) {
        DeadLockDemo deadLockDemo = new DeadLockDemo(true);
        DeadLockDemo deadLockDemo1 = new DeadLockDemo(false);
        deadLockDemo.start();
        deadLockDemo1.start();
    }
}
class DeadLockDemo extends Thread{
    static Object object = new Object();
    static Object object1 =new Object();
    boolean flag;

    public DeadLockDemo(boolean flag){
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag){
            synchronized (object){
                System.out.println(Thread.currentThread().getName()+"进入1");//拿不到第二个锁会进入阻塞状态
                synchronized (object1){
                    System.out.println(Thread.currentThread().getName()+"进入2");
                }
            }
        }else {
            synchronized (object1) {
                System.out.println(Thread.currentThread().getName() + "进入3");
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + "进入4");
                }
            }
        }
    }
}

 注意:因为Object是static类型的所以说创建的对象是类共享的。

3.释放锁

 释放锁的几种情况

不会释放锁的几种情况 

 

十。最后例题

1.作业一

思路

答案:

public class HomeWork01 {
    public static void main(String[] args) {
        A a = new A();
        B b = new B(a);
        a.start();
        b.start();
    }
}

class A extends Thread{
    private boolean bool = true;

    @Override
    public void run() {
        while (bool){
            System.out.println((int)(Math.random()*100+1));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void setBool(boolean bool) {
        this.bool = bool;
    }
}

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.setBool(false);
                System.out.println("线程退出");
                break;
            }
        }

    }
}

 提示:上面使用过这种方法来控制子线程,使用的是控制循环变量是否为true

2.作业二

思路

 自己写的

public class HomeWork02 {
    public static void main(String[] args) {
        Card1 card1 = new Card1();
        new Thread(card1).start();
        new Thread(card1).start();
    }
}

class Card1 implements Runnable {
    private static int money = 10000;

    private boolean bool = true;

    public synchronized void m1() {
        if (Card1.money <= 0) {
            bool = false;
            return;
        }
        Card1.money -= 1000;
        System.out.println(Thread.currentThread().getName() + "还剩的钱数为 " + Card1.money);
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (bool) {
            m1();
        }
    }
}

 老师写的

public class HomeWork03 {
    public static void main(String[] args) {
        T t = new T();
        Thread thread = new Thread(t);
        thread.setName("t1");
        Thread thread1 = new Thread(t);
        thread1.setName("t2");
        thread.start();
        thread1.start();
    }
}
class T implements Runnable{
    private int money=10000;
    @Override
    public void run() {
        synchronized (this){
            while (true){
                if (money<1000){
                    System.out.println("当前余额不足。。");
                    break;
                }

                money-=1000;
                System.out.println(Thread.currentThread().getName()+"取出了1000元,接下来的余额为"+money);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

使用的是非公平锁,还有公平锁,但是老师没有在专题当中讲。