JavaSE 线程

一、Java 中的线程

二、Java 中开启子线程的方式

1、继承Thread类

将线程类转换为线程对象

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

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

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

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

买火车票案例

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();
    }
}

2、实现Runnable接口

创建线程类实现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);
        }
    }
}
// 创建子线程对象    注意这时还不能直接用
Thread02 thread02 = new Thread02();

// 构建子线程    将子线程对象传入Thread类的构造器中,这时的对象才是真正的子线程对象
Thread thread = new Thread(thread02);

// 运行子线程的run方法
thread.start();

// 调用start方法,实际就是调用Thread构造器入参对象的run方法。

买火车票实例

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方法具有以下不足:

-- 不能有返回值。

-- 无法抛出异常。

3、实现Callable接口

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

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

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

// 创建子线程类,实现Callable接口,实现call方法
public class TestRandom implements Callable<Integer> {
    @Override    
    public Integer call() throws Exception {
        return new Random().nextInt(10);
    }
}

// 创建子线程对象,但是这时这个对象还不能直接使用
TestRandom testRandom = new TestRandom();

// 创建一个中间对象,传入上一步的子线程对象,这一步是为了兼容Thread类的构造器的参数
utureTask futureTask = new FutureTask(testRandom);

// 将上一步的中间对象传入Thread类的构造器,的带真正的子线程对象
Thread thread = new Thread(futureTask);
thread.start();

// 获取子线程功能的返回值,需要借助第6步的中间对象的get()来获取到返回值
Object object = futureTask.get();

三、线程的生命周期

从开始到消亡,线程经历哪些阶段:

四、线程常见方法

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);

-- 底层逻辑:

1、主动让出当前CPU

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

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

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

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资源的切换吗?  -- 能;

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

多个同步代码块使用了同一个 锁子(同步监视器):

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

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

六、同步方法

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

单个线程对象调用;相当于锁上了 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();
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值