【Java学习】线程

一.创建线程的两种方式

1.继承Thread类,重写run方法

  1. 直接代码实操
public class Thread_ {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.start();
        System.out.println("主线程继续执行"+Thread.currentThread().getName());//获取当前线程的名字
        //当main线程启动一个子线程的时候 主线程不会阻塞,会继续执行
        //这时主线程和子线程是交替进行
        int i=0;
        while(i==30){
            System.out.println("主线程 "+i++);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//当一个类继承了Thread类,该类就可以当作线程使用
//我们会重写run方法,写上自己的业务代码
class Cat extends Thread {
    int time;
    @Override
    public void run() {
        while (true) {
 			System.out.println("今天我们学线程" + time+++Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (time == 30)
                break;
        }
    }
}
===========================================================================================
  • 当我们运行程序的后我们大开终端 输入jconsole打开jconsole
    在这里插入图片描述
  • 找到我们刚刚运行的程序 进行连接
    -在这里插入图片描述
  • 随后在线程中我们可以清楚的看到 main线程和Thread-0线程
  • 当main线程中的相应代码运行结束后 main线程也会 消失
    在这里插入图片描述
  • 而当Thread-0线程运行结束后并不会消失 这是因为当它运行结束后整个进程就结束死亡了
    在这里插入图片描述

由上可得:在线程中主线程结束,如果还有相应的子线程正在进行,整个进程并不会结束

  1. 为什么我们不直接调用run方法而是使用不知道哪来的start方法呢?
    • 我们把start方法注释掉,直接调用run方法进行测试,我们可以看到并没有创建新的线程 而是直接在main方法中运行的
      在这里插入图片描述
    • 而且会等run方法全部执行完成后再往下进行
      在这里插入图片描述

    得出结论

    1. run方法就是一个普通的方法,并不会真正启动一个线程
    2. 而start方法会新建一个线程来运行run方法
      在这里插入图片描述

2.实现Runnable接口,重写run方法

  • 因为java是单继承的,在某些情况下一个类可能已经继承了某个父类,这个时候就无法再使用继承Thread类的方法来创建线程了
  • 此时 我们便可以通过实现Runnable接口来创建线程
  • 代码实操
package com.java.xiancheng;

public class Thread_Runnable_ {
    public static void main(String[] args) {
        Hpj hpj = new Hpj();//3.创建对象->4
       // hpj.start(); 这里不能直接调用start方法 我们通过看源码可以得知 Runnable接口中只有一个抽象方法run
       //此时我们就可以使用设计模式中的 静态代理模式 创建一个Thread 对象 把hpj对象放入Thread中
        Thread thread = new Thread(hpj);//4.将创建的对象方法 Thread中
        thread.start();

    }
}

class Hpj implements Runnable {//1.先实现接口->2
    int counter = 0;

    @Override
    public void run() {//2.重写run方法->3
        while (true) {
            System.out.println("小侯今天学线程" + counter++ + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3.通过编写模拟售票代码来发现问题

  • 代码如下:
package com.java.day6;

public class MaiPiao {
        public static void main(String args[]) {
            Ticket ticket = new Ticket();
            Thread t1 = new Thread(ticket,"一号窗口");
            Thread t2 = new Thread(ticket,"二号窗口");
            Thread t3 = new Thread(ticket,"三号窗口");
            Thread t4 = new Thread(ticket,"四号窗口");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
        static class Ticket implements Runnable {
            private static int tickets = 50;
            public Ticket() {
                super();
            }
            @Override
            public void run() {
                          // TODO Auto-generated method stub
                while (true) {
                        if (tickets <= 0)
                            break;
                           //tickets--;
                        System.out.println(Thread.currentThread().getName()+"...这是第" + tickets-- + "号票");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
  • 经过几次测试 我们会发现买票的过程中会发现一票多卖的情况出现,而且票的顺序也是打乱的
  • 当我们把循环结束的条件改为如果票数等于0就退出循环时,甚至出现了卖出负票的情况
    在这里插入图片描述
  • 我们该如何解决这个问题呢?我们在随后的学习中便能解决这个问题,请慢慢往下看

二.线程中常用的方法

1.yield和join

  • yield:线程礼让,让出cpu,让其他线程执行,但是礼让的时间不确定,由cpu决定所以不一定能礼让成功
  • join:线程的插队,插队的线程一旦插队成功则肯定先执行完插入的线程的所有的任务
  • 代码实操:
 package com.java.xiancheng;

public class Join_yield {
    public static void main(String[] args) {
        Hpj1 hpj = new Hpj1();
        Thread h = new Thread(hpj);
        h.start();
        for (int i = 1; i <= 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("小侯2学线程的第" + i + "分钟");
            if (i == 5) {
                try {
                    System.out.println("小侯2学了五分钟了,先让小侯学");
                    h.join();//这里相当于让h 线程先执行完再回来执行剩下的 一定会成功
                    System.out.println("小侯学完了我再学");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }


    }
}

class Hpj1 implements Runnable {
    int num = 1;

    @Override
    public void run() {
        while (num <= 10) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("小侯学线程的第" + num++ + "分钟");
        }
    }
}
  • 通过运行结果可以看出 join成功的让main线程再达到条件后先让 h线程线运行完再回来运行
    在这里插入图片描述
  • 而我们把join换成yield后,让了一会儿后cpu觉得我能处理过来,边又恢复了原状
    在这里插入图片描述

三.线程的状态

线程状态名称描述
new新建线程刚被创建,还没调用start方法,或者刚刚调用了start方法,调用 start方法不一定"立即"改变线程状态,中间可能需要一些步骤才完成 一个线程的启动。
RUNNABLE可运行start方法调用结束,线程由NEW变成RUNNABLE,线程存活着,并尝试 抢占CPU资源,或者已经抢占到CPU资源正在运行,这俩种情况的状 态都显示为RUNNABLE
BLOCKED锁阻塞线程A和线程B都要执行方法test,而且方法test被加了锁,线程A先拿 到了锁去执行test方法,线程B这时候需要等待线程A把锁释放。这时 候线程B就是处理BLOCKED
WAITING无限期等待一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入 Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线 程调用notify或者notifyAll方法才能够唤醒。
TIMED_WAITING有限期等待和WAITING状态类似,但是有一个时间期限,时间到了,自己也会主 动醒来
TERMINATED终止(死亡)run方法执行结束的线程处于这种状态。

四.线程同步机制

1. 线程同步机制理解

  • 在多线程编程中,一些敏感的数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性
  • 也可以理解为:当有一个线程在对内存进行操作的时候,其它线程都不可以对这个内存地址进行操作,直到该线程完成操作,其它线程才能对该内存地址进行操作

2.那么怎么实现线程同步呢

  1. 同步代码块
    	synchronized(对象){//得到对象的锁才能同步代码
    					//需要被同步的代码
    	}
    	```
    
  2. synchronized也可以放在方法的声明当中,表示整个方法 为同步方法
    	public synchronized void hpj(String name){
    				//需要被同步的代码
    	}
    	```
    
  3. 现在我们便可以解决上面售票代码出现的错误
    1. 直接把锁加在run方法上
      public synchronized void run() {
      //此处省略
      }
      
      • 程序运行后我们发现只有一个线程在买票,这是因为当第一个线程抢到锁后 会把run方法中的代码全部执行完毕才会释放锁来让其它线程去运行, 当其它线程抢到锁以后run方法结束条件已成立,最后程序就结束了
      • 所以这个方法不可行
    2. 加在while循环中 可行
  4. synchronized关键字修饰非静态方法,默认使用 this 当做锁对象,并且不能自己另外指定
  5. synchronized 关键字修饰静态方法,默认使用 当前类的Class对象 当做锁对象,并且不能自己另外指 定

3.线程的死锁

  • 举例代码:
public class ThreadDeadLock extends Thread{
    private Object obj1;
    private Object obj2;
    public ThreadDeadLock(Object obj1,Object obj2) {
        this.obj1 = obj1;
        this.obj2 = obj2;
}
    public void run() {
        String name = Thread.currentThread().getName();
        if("Thread-0".equals(name)){
            while(true){
                synchronized (obj1) {
synchronized (obj2) { System.out.println(name+" 运行了..");
} }
} }
        else{
            while(true){
                synchronized (obj2) {
                    synchronized (obj1) {
System.out.println(name+" 运行了.."); }
} }
} }
    public static void main(String[] args) {
        Object obj1 = new Object();
        Object obj2 = new Object();
        Thread t1 = new ThreadDeadLock(obj1,obj2);
        Thread t2 = new ThreadDeadLock(obj1,obj2);
        t1.start();
        t2.start();
    }
}
  • 死锁也就是互相拿着对方需要的锁不放
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来半杯冰可乐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值