Java SE 线程

一、继承Thread类:线程类  --->  线程对象

1、线程对象就要开始争抢cpu资源了;

2、当前线程类必须继承Thread类并且重写run方法;

3、具体实现的代码要写在run方法里面;

4、想要执行线程对象里面的run方法,就需要先创建这个类的对象并且调用start方法;

设置和读取线程的名字

1、thread.setName(), this.getName()

2、获取当前线程:

Thread.currentThread()

3、通过构造器设置名字

public Thread01(String name) {
    super(name); //调用父类的有参构造器
}

4、买火车票实例

public class BuyTicketThread extends Thread{
    private static int ticketNum = 10;
    public BuyTicketThread(String name) {
        super(name);
    }

    @Override    
    public void run() {
        //每个窗口有100个人抢票        
        //每个线程抢票100次        
        for (int i = 0; i < 100; i++) {
            if (ticketNum > 0)
                System.out.println(this.getName() + "买到车票============" + ticketNum--);
        }
    }
}
public class BuyTicketTest {
    public static void main(String[] args) {
        BuyTicketThread thread1 = new BuyTicketThread("线程1");
        thread1.start();
        BuyTicketThread thread2 = new BuyTicketThread("线程2");
        thread2.start();
        BuyTicketThread thread3 = new BuyTicketThread("线程3");
        thread3.start();
    }
}

二、实现Runnable接口

1、创建线程类     注意线程类要实现Runnable接口,并且实现run方法

public class Thread02 implements Runnable{
    @Override    
    public void run() {
        //输出1-10        
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "============" + i);
        }
    }
}

2、创建子线程对象    注意这时还不能直接用

Thread02 thread02 = new Thread02();

3、构建子线程    将子线程对象传入Thread类的构造器中,这时的对象才是真正的子线程对象

Thread thread = new Thread(thread02);

4、运行子线程的run方法

thread.start();
// 注意:调用start方法,实际就是调用Thread构造器入参对象的run方法 --> 见3;

5、买火车票实例

public class BuyTicketThread implements Runnable{
    private int ticketNum = 10;

    @Override    public void run() {
        //每个窗口有100个人抢票        
        //每个线程抢票100次        
        for (int i = 0; i < 100; i++) {
            if (ticketNum > 0)
                System.out.println(Thread.currentThread().getName() + "买到车票============" + ticketNum--);
        }
    }
}
public class BuyTicketTest {
    public static void main(String[] args) {
        BuyTicketThread thread = new BuyTicketThread();

        Thread t1 = new Thread(thread, "线程1");
        t1.start();

        Thread t2 = new Thread(thread, "线程2");
        t2.start();

        Thread t3 = new Thread(thread, "线程3");
        t3.start();
    }
}

小结:

1、使用实现Runable接口的方式创建线程好于继承Thread类,java单继承的特点;

2、Thread类和Runnable接口具有以下关系

3、实现的run方法具有以下不足:

-- 不能有返回值

-- 无法抛出异常

三、实现Callable接口

1、好处:有返回值,可以抛出异常

2、缺点:线程的创建比较麻烦

3、实现Callable接口可以指定泛型,不指定泛型默认返回值类型为Object,否则为指定的类

4、创建子线程类,实现Callable接口,实现call方法

public class TestRandom implements Callable<Integer> {
    @Override    
    public Integer call() throws Exception {
        return new Random().nextInt(10);
    }
}

5、创建子线程对象,但是这时这个对象还不能直接使用

TestRandom testRandom = new TestRandom();

6、创建一个中间对象,传入上一步的子线程对象,这一步是为了兼容Thread类的构造器的参数

utureTask futureTask = new FutureTask(testRandom);

7、将上一步的中间对象传入Thread类的构造器,的带真正的子线程对象

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

8、获取子线程功能的返回值,需要借助第6步的中间对象的get()来获取到返回值

Object object = futureTask.get();

四、线程的生命周期

1、开始  --->  消亡

2、线程经历哪些阶段:

五、线程常见方法

1、start():

        -- 作用:启动当前线程;

        -- 用法:thread.start();线程对象直接调用;

        -- 底层逻辑:表面上调用start方法,实际上调用线程里面的run方法;

2、run():

        -- 作用:run方法里面写的是具体的业务逻辑;

        -- 用法:子线程类 继承Thread类或者实现Runnable接口的时候,重写的这个run方法;

        -- 底层逻辑:调用start方法后,cpu给线程分配资源后运行的就是这个run方法;

3、Thread.currentThread():

        -- 作用:获取当前正在运行的线程对象,Thread类中的一个静态方法;

        -- 用法:因为是静态方法,所以写法为 Thread.currentThread();

4、setName(),getName():设置、读取线程名字;

5、setPriority(Integer integer):

        -- 作用:设置优先级;

        -- 用法:thread对象.setPriority(Integer integer);

        -- 底层逻辑:

                ① 同优先级别的线程,采用先到先服务的策略,使用时间片策略;

                ② 如果优先级别高,那么被CPU调度的概率高;

                ③ 不是级别越高一定会先执行;

                ④ 级别分为 1 ~ 10分,默认为5分;

6、join():

        -- 作用:使线程被先执行,而且会将它完全执行完后,才会执行其他线程;

        -- 用法:必须先start,再join才会生效;

        -- 底层逻辑:相当于调度这个线程的时候,给他的时间是整块的,不是分片的;

        -- 测试代码

public class TestThread extends Thread{
    public TestThread(String name) {
        super(name);
    }

    @Override    
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName() + "====" + i);
        }
    }
}
class Test{
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            if (i == 6){
                //创建子线程                
                TestThread testThread = new TestThread("子线程");
                testThread.start();
                testThread.join();
            }
            System.out.println("main=====" + i);
        }

    }
}

7、Thread.sleep():

        -- 作用:使线程睡眠 N 毫秒,人为地制造阻塞;

        -- 用法:thread对象.sleep(1000);

        -- 底层逻辑:

                ①、主动让出当前CPU

                ②、在N毫秒内不参与竞争

                ③、时间过去重新参与竞争;

                ④、开始参与竞争也不会立刻被执行;

8、Thread.setDaemon():

        -- 伴随线程:皇上 -->  驾崩  --> 妃子陪葬;妃子死前垂死挣扎;

        -- 作用:设置 子线程主线程 的伴随线程;将子线程设置为主线程的 妃子;

        -- 用法:先setDaemon(true),再start();

        -- 也叫守护线程;

9、Thread.currentThread.stop():

        -- 一个过期的方法,不推荐使用;

        -- 作用:立即弄死当前线程;

        -- 用法:thread对象.stop();

六、同步代码块

1、锁 --> 加同步 --> 同步监视器

2、同步代码块1

        -- 使用 synchronized关键字给 this 上锁,将共享资源锁住;this 其实就是调用run方法的对象,此时这个对象在内存中是唯一的;

        -- synchronized关键字应该只限制有安全隐患的代码,以提高效率;

3、同步代码块2

4、引入 【锁子 --> 同步监视器】;synchronized(同步监视器){};

-- 1、要求synchronized锁住的资源在内存中是唯一的,这个同步监视器用来给线程标识目标代码块是否可以执行;

-- 2、不能是基本数据类型,只能是引用数据类型;

-- 3、不推荐使用 String和Integer包装类对象当作 锁子;

-- 4、可以创建一个唯一的无意义对象当 锁子;

-- 5、推荐使用 final 修饰 锁子;

-- 6、一般使用共享资源做 锁子;

-- 7、锁子的指向地址不能修改,锁子内部的属性不能修改;

5、 在同步代码块中线程切换过程;

-- 1、线程【1】来了,发现锁子状态是 open;

-- 2、线程【1】将锁子状态改成 close,线程【1】进入同步代码块;

-- 3、线程【2】来了,发现锁子状态是 close,那么不进入同步代码块;

-- 4、线程【1】执行完成,将锁子状态改成 open;

-- 5、线程【2】检查锁子状态后进入同步代码块;

-- 注意:锁子上锁时,会发生CPU资源切换,但是没锁的线程执行不了同步代码块;

-- 问题:同步代码块可以发生CPU资源的切换吗?

        -- 能;

        -- 但是没拿到锁的线程无法执行同步代码块;

6、多个同步代码块使用了同一个 锁子;

-- 假如 锁子 A 被上锁,那么其他所有使用 A 的同步代码块都被锁住了;

-- 假如 锁子 A 被上锁,锁子 B 没被上锁,那么使用锁子 B 的同步代码块可以被其他线程运行;

七、同步方法

1、多个线程对象调用,同步方法用static修饰;相当于锁上了 BuyTicketThread.class;

2、单个线程对象调用;相当于锁上了 this;

-- 锁必须是共享锁,且锁在内存中是唯一的;

-- 不建议将 run() 方法定义为同步方法;

-- 同步代码块效率高于同步方法;

-- 同步方法锁的是 this,所以一旦锁住了一个方法,那么其他被synchronized修饰的方法也被锁住了;

八、Lock 锁

-- 使用多态创建锁对象,灵活;

-- Lock 与 synchronized 区别

        -- Lock 是显式锁,需要手动开/关锁;synchronized 是隐式锁;

        -- Lock 只有代码块锁;synchronized 有代码块锁和方法锁;

        -- Lock锁效率高于 synchronized;

九、使用 同步/锁 特点

优点

缺点

数据可以保持同步;

效率低;

可能造成死锁;

    -- 死锁:你需要我的,我需要你的;一直僵持;

解决死锁办法:

    -- 减少同步资源的定义;

    -- 避免嵌套 同步/锁子;

十、线程通信

1、使用同步代码块实现资源同步;

2、VO 类使用同步方法实现资源同步;

3、线程通信主要代码

public class Product {
    private String brand;
    private String name;

    //引入一个灯 true 红色 false - 绿色   
    //默认没有商品 让生产者先生产
    boolean flag = false;
    
    //生产商品
    public synchronized void setProduct(String brand,String name){
        //如果是红色,那么不生产,就等待
        if (flag){             
            try {
               wait();
            } catch (InterruptedException e) {
               throw new RuntimeException(e);
            }
        }
        //生产        
        this.setBrand(brand);
        try {
           Thread.sleep(100);
        }catch (Exception e){
           e.printStackTrace();
        }
        this.setName(name);
        System.out.println("生产者生产了" + this.getBrand() + "-----" + this.getName());
        //生产完成后,灯变成红色        
        flag = true;
        //通知消费者
        notify();     
    }

    //消费商品
    public synchronized void getProduct(){
        //没有商品就等待        
        if (!flag){
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        //有商品就消费        
        System.out.println("消费者消费了=====" + this.getBrand() + this.getName());
        //灯变色 变成绿色        
        flag = false;
        //通知生产者
        notify();    
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

4、在 java 对象中,有两种池,一种是锁池,一种是等待池;

锁池

synchronized

等待池

wait(),notify(),notifyAll()

        -- 如果一个线程调用了某个对象的wait()方法,那么该线程进入到这个对象的等待池中,并且放弃锁;

        -- 如果未来的另一个线程调用了相同对象的notify()、notifyAll()方法,等待池中的线程就会被唤醒,可以重新进入锁池争抢该对象的锁;

        -- 如果被唤醒的线程抢到锁了,那么它会沿着之前调用wait()方法之后的代码继续执行;注意是沿着wait方法之后;

        --注意:

                ①、wait()和notify()方法,必须放在同步代码块/同步方法中才能生效(因为在同步的基础上进行线程的通信才是有效的)

                ②、sleep()和wait()方法的区别是:sleep()不会放弃对锁的占有权,wait()会放弃对锁的占有权;

-- 5、使用Lock类来增强线程通信

-- Condition 类来增强等待池,可以将原先的一个等待池拆分为多个等待池;

-- Condition condition = lock.newCondition();

-- condition.await() == wait();

-- condition.signal() == notify();

-- condition.signalAll() == notifyAll();

    Lock lock = new ReentrantLock();

     //定义 等待池
    Condition produceCondition = lock.newCondition();
    Condition consumerCondition = lock.newCondition();


    public void setProduct(String brand,String name){
        lock.lock();
        try {
            if (flag){ //如果是红色,那么不生产,就等待
                try {
//                    wait();
                    // 生产者阻塞
                    // 生产者进入等待队列
                    produceCondition.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            //生产
            this.setBrand(brand);
            try {
                Thread.sleep(100);
            }catch (Exception e){
                e.printStackTrace();
            }
            this.setName(name);
            System.out.println("生产者生产了" + this.getBrand() + "-----" + this.getName());
            //生产完成后,灯变成红色
            flag = true;
//            notify(); //通知消费者
            // 唤醒消费者
            // 消费者等待池
            consumerCondition.signal();
        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }

  • 30
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值