关于线程的一些汇总

目录

创建线程的三种方式

通过类继承Thread类

同过类实现Runnable接口

通过类实现Callable接口

解决并发问题():

认识synchronized ()---同步与同步监视器

通过同步代码块

通过同步方法

通过Lock锁

小结

线程通信(wait和notify)

原理解释:

注意:

通过wait 方法进入等待的线程被唤起后进入锁池状态的原因:

通过代码理解

其余零散知识点:

程序,进程,线程

并发与并行

线程常见方法

程序优先级:

join方法

sleep

setDaemon

stop

线程的生命周期


创建线程的三种方式

通过类继承Thread类

根据要求抽象出一个需要被多线程操作的类,让该类继承Thread类,并重写run方法。在测试类的主方法中正常创建该类对象,通过对象名.start()调用。在创建线程时,可以向内传入一个字符串类型参数,用于设定,该线程的名字,之后通过Thread.currentThread().getName()方法可进行调用,其中Thread.currentThread()是获取当前线程对象。

此方法弊端较为明显,每实现一个线程,都要创建一个该类对象,但是这几个对象的资源并不共享,若希望共享,需要将资源设为静态

下面以多个窗口售票的例子进行理解

售票窗口类:

public class ExtendSell extends Thread{
    private int ticket=10;
    public ExtendSell(String name) {
        super(name);
    }

    @Override
    public void run() {
            while (ticket >= 1) {
                System.out.println(Thread.currentThread().getName() + "卖出倒数第" + (ticket--) + "张");
            }
        }
}

测试类:

public class Test {
    public static void main(String[] args) {
        ExtendSell s1=new ExtendSell("窗口1");
        ExtendSell s2=new ExtendSell("窗口2");
        ExtendSell s3=new ExtendSell("窗口3");
        s1.start();
        s2.start();
        s3.start();
}

运行截图:


同过类实现Runnable接口

同样根据要求抽象出一个需要被多线程操作的类,此方法是让该类实现接口Runnable,在调用多线程时,与方法一有所区别,需要先创建该类对象,然后Thread 创建线程名=new Thread(刚才创建的对象名);可以通过同一个对象名多次创建线程,这样就会有多个线程,且这几个线程的资源(属性共享),无需像方法1一样,将属性设置为静态

售卖窗口类:

public class ImplementsSell implements Runnable {
    int i = 100;
    @Override
    public void run() {
        for (;i>=0;){
            System.out.println(Thread.currentThread()+"sell第"+(i--)+"张票");
        }
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        ImplementsSell sell=new ImplementsSell();
        Thread thread1=new Thread(sell);
        Thread thread2=new Thread(sell);
        Thread thread3=new Thread(sell);
        thread3.start();
        thread2.start();
        thread1.start();
}

运行截图:

可以看出,此方法还有并发问题。出现原因是当前代码块还未执行完,但系统所分配的CPU使用时间已经结束,需要调度给其他进程,直到再次轮到此进程,才会继续进行刚才未完成的代码块。

通过类实现Callable接口

此种方法解决了方法一二的两个问题,没有返回值  和  不能抛出异常,但在调用的时候会多一个步骤 

同时实现的也不再是run方法,而是call方法,虽然名字不同,但作用相同。
同样是需要根据要求抽象出一个需要被多线程操作的类,此方法是让该类实现接口Callable<>,<>中要写的是run方法的返回值类型,不写默认为Object类。
主方法中调用时,需要先创建该类对象,然后根据该对象,创建FutureTask对象,再根据FutureTask对象创建线程。

售卖窗口类:

public class CallableSell implements Callable<String> {
    int countnum;
    int ticket=100;
    @Override
    public String call() throws Exception {
        for (int i=0;i<1000;i++){
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "售出" + (++countnum) + "张票,还剩" + (--ticket) + "张票");
                }
        }
        return "线程" + Thread.currentThread().getName() + "执行结束";
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        CallableSell csell=new CallableSell();

        FutureTask task1=new FutureTask<>(csell);
        FutureTask task2=new FutureTask<>(csell);
        FutureTask task3=new FutureTask<>(csell);

        Thread th1=new Thread(task1,"窗口1:");
        Thread th2=new Thread(task2,"窗口2:");
        Thread th3=new Thread(task3,"窗口3:");
        th1.start();
        th2.start();
        th3.start();
    }
}

运行结果:

同样,有并发问题

========================分割==================================


解决并发问题():

通过上面的例子我们能过够看到,输出语句是乱序的,出现原因是当前代码块还未执行完,但系统所分配的CPU使用时间已经结束,需要调度给其他进程,直到再次轮到此进程,才会继续进行刚才未完成的代码块。若是判断条件没有加好,甚至可能会出现负数,而为了解决这个问题,就引出了线程安全问题。

认识synchronized ()---同步与同步监视器

synchronized关键字的中文代称是“同步”,括号内的中文代称是“同步监视器”。

当某个线程进入了一个被synchronized修饰的方法或者代码块时,它会尝试获取同步监视器的锁。如果同步监视器的锁已经被其他线程获取了,那么这个线程就会被阻塞,直到获取到锁为止。

通过使用同步监视器,我们可以确保在同一时间只有一个线程可以进入被synchronized修饰的方法或者代码块,从而保证了线程安全性。

当我们使用synchronized关键字来修饰一个方法或者代码块时,需要指定一个同步监视器(也称为锁对象)。这个同步监视器可以是任意对象,但通常情况下会选择当前类的实例对象即(this)或者静态对象作为同步监视器。

具体来说:

1.对于实例方法,同步监视器是调用该方法的实例对象。

public synchronized void doSomething() {
    // 这里的同步监视器是调用doSomething方法的实例对象
}

2.对于静态方法,同步监视器是当前类的Class对象。 

public static synchronized void doSomethingStatic() {
    // 这里的同步监视器是当前类的Class对象
}

3.对于代码块,可以使用任意对象作为同步监视器。

但一般为this和当前类名.class。区别在于如果是多个线程共用一个该类对象(如通过实现Runnable接口来实现多线程),用this;

如果是多个线程使用多个该类对象(如通过继承Thread类实现多线程),就需要用类名.class获取其字节码信息对象。这两种都是为了确保多个线程使用的是同一把锁。

Object lock = new Object();
synchronized(lock) {
    // 这里的同步监视器是lock对象
}

需要说明的是

多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,其他线程无法访问其中的任何一个代码块。 但是没有锁住使用其他同步监视器的代码块,其他线程有机会访问其他同步监视器的代码块

同步监视器的具体运行流程为:

1)第一个线程来到同步代码块,发现同步监视器open状态,需要close,然后执行其中的代码

2)第一个线程执行过程中,发生了线程切换(阻塞 就绪),第一个线程失去了cpu,但是没有开锁open

3)第二个线程获取了cpu,来到了同步代码块,发现同步监视器close状态,无法执行其中的代码,第二个线程也进入阻塞状态

4)第一个线程再次获取CPU,接着执行后续的代码;同步代码块执行完毕,释放锁open

5)第二个线程也再次获取cpu,来到了同步代码块,发现同步监视器open状态,拿到锁并且上锁,由阻塞状态进入就绪状态,再进入运行状态,重复第一个线程的处理过程(加锁)

通过同步代码块

通过方法synchronized ()实现,具体实现方法为,将需要被完整执行的代码块用{}包裹起来,然后再左大括号之前加上该关键字,有点类似while循环的格式,()中填入的是就是锁(同步监视器),关于这个锁有以下几点要求

1)必须是引用数据类型,不能是基本数据类型

2)也可以创建一个专门的同步监视器,没有任何业务含义 

3)一般使用共享资源做同步监视器即可   

4)在同步代码块中不能改变同步监视器对象的引用 ,即不能重新赋值,所以一般建议,这个锁用final来修饰

5)尽量不要String和包装类Integer做同步监视器 

以多个窗口售票(实现Callable接口)为例,来理解

public class CallableSell implements Callable<String> {
    int countnum;
    int ticket=100;
    @Override
    public String call() throws Exception {
        for (int i=0;i<1000;i++){
            synchronized (this) {//将判断与输出锁到一起
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "售出" + (++countnum) + "张票,还剩" + (--ticket) + "张票");
                }
            }
        }
        return null;
    }
}

测试方法与创建线程中的  通过类实现Callable接口  实现多线程一致

输出结果:

可以看出,已经能够按序输出


通过同步方法

通过同步方法实现与通过同步代码块类,只不过一个是把锁放在了方法外,一个是放在代码块外,本质一样。

而且

对于普通方法:public synchronized void buyTicket(){}--------锁(同步监视器)是:this

对于静态方法:public static synchronized void buyTicket(){}----锁(同步监视器)是: 当前类.class

还是以多个窗口售票(实现Callable接口)为例,来理解

public class CallableSell implements Callable<String> {
    int countnum;
    int ticket=100;
    @Override
    public String call() throws Exception {
        for (int i=0;i<1000;i++){
            buyticket();
        }
        return null;
    }
    public synchronized void buyticket(){
        if (countnum <1000) {
            System.out.println(Thread.currentThread().getName() + "售出第" + (++countnum) + "张票");
        }
    }

}

输出结果

关于同步方法要注意:

1)不要将run()定义为同步方法

2)非静态同步方法的同步监视器是this

  静态同步方法的同步监视器是 类名.class 字节码信息对象

3)同步代码块的效率要高于同步方法

  原因:同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了 代码块的外部,但是却是方法的内部

4)同步方法的锁是this,一旦锁住一个方法,就锁住了所有的同步方法(因为都是默认当前类,即都是this);同步代码块只是锁住使用该同步监视器的代码块,而没有锁住使用其他监视器的代码块 


通过Lock锁

关于LOCK锁,是JDK1.5后新增新一代的线程同步方式;与采用synchronized相比,lock可提供多种锁方案,更灵活。

而且synchronized是Java中的关键字,这个关键字的识别是靠JVM来识别完成。是虚拟机级别的。 但是Lock锁是API级别的(即写成了类),提供了相应的接口和对应的实现类,这个方式更灵活,表现出来的性能优于之前的方式。

用法为先创建一个锁对象,Lock lock = new ReentrantLock();

然后再要锁的代码块开始的位置加上lock.lock();再结束位置加上lock.unlock();

public class CallableSell implements Callable<String> {
    int countnum;
    Lock lock = new ReentrantLock();
    @Override
    public String call() throws Exception {
        for (int i=0;i<1000;i++){
            lock.lock();
            //这里可以加上try-catch,然后将unlock放在finally中
            if (countnum <1000) {
                System.out.println(Thread.currentThread().getName() + "售出第" + (++countnum) + "张票");
            }
            lock.unlock();
        }
        return null;
    }

}

一般会对锁的位置加上try-catch-finally,用于即便发生异常,也能让锁解开,而不是一直被锁着。

运行结果:

小结

Lock和synchronized的区别

      1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁

      2.Lock只有代码块锁,synchronized有代码块锁和方法锁

      3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

建议优先使用顺序:

    Lock---->同步代码块(已经进入了方法体,分配了相应资源)---->同步方法(在方法体之外)

====================================分割=================================


线程通信(wait和notify)

原理解释:

在Java对象中,有两种池

琐池-------------synchronized

等待池---------------------wait(),notify(),notifyAll()

如果一个线程调用了某个对象的wait方法,那么该线程进入到该对象的等待池中(并且已经将锁释放),如果未来的某一时刻,另外一个线程调用了相同对象的notify方法或者notifyAll方法,那么该等待池中的线程就会被唤起,然后进入到对象的锁池裏面去获得该对象的锁,如果获得锁成功后,那么该线程就会沿着wait方法之后的路径继续执行。

注意:

1.wait方法和notify方法  是必须放在同步方法或者同步代码块中才生效的 (因为在同步的基础上进行线程的通信才是有效的)

2.sleep和wait的区别:sleep进入阻塞状态没有释放锁,wait进入阻塞状态但是同时释放了锁


通过wait 方法进入等待的线程被唤起后进入锁池状态的原因:

当一个线程调用wait方法时,它会释放对象的锁,这意味着其他线程可以获得该对象的锁并执行同步方法或同步块。当另一个线程调用notify或notifyAll方法时,等待中的线程会被唤醒,但在进入就绪状态之前,它需要重新获得对象的锁。
这里所说的锁指的是对象级别的锁,也称为监视器锁或内置锁。在Java中,每个对象都有一个关联的监视器锁,可以通过synchronized关键字来获取对象的锁。当一个线程进入synchronized方法或块时,它会尝试获取对象的监视器锁。在调用wait方法后,线程会释放这个锁,允许其他线程进入同步方法或块。当线程被唤醒后,它需要重新获取对象的监视器锁,这样才能继续执行同步方法或块。
因此,在被唤醒后,线程需要重新获取对象的监视器锁,这个锁就是指对象级别的锁,也称为内置锁。获取了锁之后,线程才能进入就绪状态,等待CPU调度执行。

通过代码理解

如何让两个线程按一定的顺序执行,比如我有两个线程,一个生产者,一个消费者,生产者每生产一个产品,就需要消费者取走一个,然后生产者才能再次生产,如何能让他们实现这种类似有交流的交替效果呢。那便是通过条件判断让线程等待,直到被唤醒。

首先需要抽象出来一个商品类,再该类中实现生产和买两个方法:

生产方法
public synchronized void setProdect(String name,String type){
    if (aBoolean){

    //---aBoolen用来判断
    //如果是flase--消费者等待,直到生产者生产完,改变参数后,被生产者唤醒
    //如果是ture--生产者等待,直到消费者购买完,改变该参数后,被消费者进行唤醒

        try {
            wait();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    this.type=type;
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    this.name=name;
    System.out.println("我是生产者,生产了"+this.getType()+this.getName());

    aBoolean=true;
    //生产者买完后改变参数

    notify();//唤醒
}
购买方法
public synchronized void getProdect(){
    if (!aBoolean){
        try {
            wait();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    System.out.println("我是消费者,我买走了"+this.getType()+this.getName());
    aBoolean=false;
    notify();
}

消费者类调用(商品类是其一个属性,且与生产者共用这一个对象)

    public void run() {
        for (int i = 0; i < 10; i++) {
            product.getProdect();
        }
    }

生产者类调用:

    public void run() {
        for (int i = 0; i < 10; i++) {
            if(i%2==0){
                product.setProdect("巧克力","德芙");

            }else {
                product.setProdect("啤酒","雪花");

        }
        //这里的if判断是为了能生产两类商品,使通信效果更明显
    }

运行结果:

完整代码(分为四个类,商品,生产者,消费者,和测试类)(已删除部分没有用到的get,set,以及构造方法):

/**
 * 商品类
 */
public class Product {
    private String type;
    private String name;
    boolean aBoolean=false;
    /**
     * flase--消费者等待,生产者生产
     * ture--生产等待,消费者购买
     */
    public Product() {
    }
    public String getType() {
        return type;
    }
    public String getName() {
        return name;
    }
    public synchronized void setProdect(String name,String type){
        if (aBoolean){
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        this.type=type;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        this.name=name;
        System.out.println("我是生产者,生产了"+this.getType()+this.getName());
        aBoolean=true;
        notify();
    }
    public synchronized void getProdect(){
        if (!aBoolean){
            try {
                wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("我是消费者,我买走了"+this.getType()+this.getName());
        aBoolean=false;
        notify();
    }
}



/**
 * 消费者线程
 */
public class Consumer extends Thread{
    private Product product;

    public Consumer(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            product.getProdect();
        }
    }
}


/**
 * 生产者线程
 */
public class Maker extends Thread {
    private Product product;

    public Maker(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0) {
                product.setProdect("巧克力", "德芙");
            } else {
                product.setProdect("啤酒", "雪花");
            }
        }
    }
}


/**
*测试类
*/
public class Test {
    public static void main(String[] args) {
        Product product=new Product();
        Maker maker=new Maker(product);
        Consumer consumer=new Consumer(product);
        maker.start();
        consumer.start();
    }
}


其余零散知识点:

程序,进程,线程

程序:静态
进程:动态
线程:同时执行

程序(program):是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。 (程序是静态的) 
进程(process):是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。 (进程是动态的)是一个动的过程 ,进程的生命周期  :  有它自身的产生、存在和消亡的过程  
线程(thread),进程可进一步细化为线程, 是一个程序内部的一条执行路径。 
  若一个进程同一时间并行执行多个线程,就是支持多线程的。 

并发与并行

并行:多个CPU同时执行多个任务 
并发:一个CPU“同时”执行多个任务(采用时间片切换)

线程常见方法

start() :  启动当前线程,表面上调用start方法,实际在调用线程里   面的run方法

run() : 线程类 继承 Thread类 或者 实现Runnable接口的时候,都要   重新实现这个run方法,run方法里面是线程要执行的内容

currentThread :Thread类中一个静态方法:获取当前正在执行的线程

setName 设置线程名字

getName 读取线程名字

程序优先级:

线程的优先级通过setPriority()方法设置,默认为5,最高为10。优先级高的,被调度的可能性会大些。但依旧有很大的随机性。

join方法

直接通过线程对象调用,优先执行调用该方法的线程,抢占cpu资源,其他线程会被阻塞
需要线程先被启动,即先start


sleep

谁调用谁休眠,以毫秒为单位1秒=1000毫秒
需要异常处理try-catch
只能通过Thread打点调用


setDaemon

伴随线程:主线程死亡,伴随线程也要死亡
线程对象名打点调用,谁调用谁是伴随线程,再那个线程里调用谁就是主线程


stop

线程死亡(直接终止线程)---不推荐使用

线程的生命周期

 线程通过wait()进入等待队列被唤醒后进入锁池状态的原因见线程通信部分

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值