java多线程

JAVA多线程

一、并发和并行

​ 并发:指两个或多个事件在同一个时段内发生。一个cpu交替执行

​ 并行:指两个或多个事件在同一时刻发生(同时发生)。多个cpu同时执行

二、线程与进程

​ 进程:指一个内存中运行的应用程序。应用程序通常是存储在硬盘中的,当点击运行程序时,会将程序放入内存执行,进入到内存的一个程序就叫一个进程,一个程序运行后至少有一个进程

​ 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行。一个进程中至少有一个线程,也可以有多个线程。如果一个进程有多个线程也可称为多线程程序。

在这里插入图片描述

线程调度

1、分时调度:所有线程轮流使用cpu,平均分配每个线程占用CPU的时间

2、抢占式调度:让优先级高的线程使用CPU,如果优先级相同则随机执行 java是抢占式调度

主线程

​ 主线程指main()方法执行的线程。如果我们没有创建其他的线程那么主线程就是一个单线程的,程序发生错误后,后面的代码将不会被执行

三、创建线程:

创建线程有两种方式:继承Thread类和实现Runable接口

1、通过继承Thred类的实现方法:

​ 1、创建一个Thread类的子类,即创建一个类继承Thread类

​ 2、在Thread类的子类中重Thread类中的run方法,设置线程任务

​ 3、创建Thread的子类对象

​ 4、调用Thread类中的方法start()方法,开启线程。start方法会自己执行我们重写的run方法。创建的新线程就和会main线程并发的执行。

多线程的内存图
在这里插入图片描述

Thread类常用的方法;(都是静态方法可以直接使用 Thread.方法 调用

1、获取当前线程及名称

Thread.currentThread().getName(); currentThrad()是Thread提供的静态方法,返回当前线程

2、sleep(long millies):使当前正在执行的线程暂停指定毫秒数

2、通过实现Runnable的方式创建线程:

步骤:

​ 1、创建一个Runnable接口的实现类

​ 2、在实现类中重写Runnable接口的run方法,设置线程任务

​ 3、在使用时,创建一个Runnable接口的实现类对象

​ 4、创建Thread对象,构造方法中传递Runnable接口的实现类对象

​ 5、调用Thread实现类的start()方法

3、继承Thread和实现Runnable接口创建线程的两种方式的区别:

使用Runnable接口创建线程的好处:

​ 1、避免了单继承的局限性:一个类继承了Thread类就不能继承其他的类,而实现了Runnable接口,还可以继承其他的类,实现其他的接口。

​ 2、增强了程序的扩展性,降低了程序的耦合。Runnable把创建线程和开启线程进行了分离(解耦)我们需要实现不同的线程,只需要在开启线程时传入不同的线程实现。

4、使用匿名内部类的方式创建线程
//使用匿名内部类的方式,且采用继承Thread类的方式创建线程
        new Thread(){
            @Override
            public void run() {
                for(int i= 0;i<=200;++i){
                    System.out.println(Thread.currentThread().getName()+": "+i);
                }
            }
        }.start();

 //使用匿名内部类的方式,并且采用实现runnable接口的方式创建线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i= 0;i<=200;++i){
                    System.out.println(Thread.currentThread().getName()+": "+i);
                }
            }
        }).start();
四、线程安全

当多个线程同时对一个共享数据进行访问时,就会出现线程安全的问题,比如电影院多个窗口都在卖票且同时都在卖同一张票,那么就会出现卖出去同一张票的问题。

1、线程安全问题产生原因

​ 比如当一个线程执行买票时顾客已经付了钱,锁定了这种票,但是售票员还未来得及通知电影院这种票已经卖出去了,就被其他线程给抢占cpu进行执行了,而恰巧这个线程也在卖这种票,但是它不知道这张票已经卖出去了,因此就卖出去多张一样的票出现了线程安全问题。

2、线程同步

​ 当多个线程访问同一资源的时候,且多个线程中队资源有写的操作时,就容易出现线程安全问题。解决这种多线程并发访问同一个资源的安全问题的的方法:java中提供了同步机制(synchronized)来解决

​ 案例分析:当一个线程在访问共享数据时,无论是否失去了cpu的执行权,其他的线程只能等待,等待当前线程卖完当前票之后,其他线程才能抢占执行。

三种方法完成同步操作:

1、同步代码块

2、同步方法

3、锁机制

1、同步代码块:

格式:

​ //创建一个锁对象 这个锁对象可以直接使用this

​ Object obj = new Object();

​ synchronized(锁对象){

​ 可能会出现线程安全问题的代码(访问了共享的数据)

​ }

注意:

​ 1、通过代码块中的锁对象(也叫同步锁、对象锁、对象监视器),可以使用任意的对象

​ 2、但是必须保证多个线程使用的锁对象是同一个

​ 3、锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行

同步原理:

​ 当t1线程首先抢到cpu执行权,运行方法时,遇到synchronized代码块,发现锁对象还在,就会获得锁对象,然后t0就进入到同步中执行。

​ 这时候t0又抢到了cpu执行权,遇到synchronized代码块,t0这时否发现锁对象不在了,就会进入到阻塞状态,一直等待t1归还锁对象。

​ 而当t1执行完synchronized代码块中的任务时,就会归还锁对象给代码块,这时t0就获得锁对象,进入到同步中执行。

总结:同步中的线程,没有执行完毕代码块中的任务时不会释放锁,同步外中的线程没有锁不能进入同步中执行任务。
2、同步方法解决

步骤:

​ 1、把访问了共享数据的代码块抽取出来,放到一个方法中

​ 2、在方法上添加synchronized修饰符

定义方法格式:修饰符 synchronized 返回值 方法名(){ }

同步方法也会把方法内部的代码锁住,而锁对象就是当前这个实现类,也就是this对象

3、Lock锁

​ Lock锁比synchronized更加灵活,有更广泛的操作。synchronized中的锁会在任务执行完后自动释放,而lock锁则需要用户手动去释放

Lock接口中的两个方法:

​ void lock(); //获取锁

​ void unlock(); //释放锁

使用步骤:

​ 1、在自定义线程类中的成员位置创建一个ReentrantLock对象,这个对象实现了lock接口

​ 2、在可能出现安全问题的代码前调用 lock()方法获取锁

​ 3、在可能出现安全问题的代码后调用 unlock()释放锁 这个可以放在finally中,无论程序是否出现异常,都会释放锁

五、线程状态:

在这里插入图片描述

Waiting(无限等待)状态:一个线程调用wait()进入无限等待状态,需要等待另一个线程执行唤醒动作(通过notify或notifyAll方法唤醒),waiting状态是不能自动唤醒的。

Timed_Waitng(计时等待、休眠状态):这一状态将休眠指定的休眠时间或者在休眠时被其他线程唤醒。唤醒之后如果抢占到线程就会继续执行wait()后的代码,否则就进入等待状态。但是一定要注意这时候即使唤醒后的代码任在synchroized代码块中,也会被其他线程抢占执行**

notifyAll()是指唤醒当前对象监视器上的所有线程。而不是指进程中所有线程。

注意:

1、wait()与notify()必须由同一个锁对象调用。因为这样才能使用锁对象的notify唤醒使用同一个锁对象调用的wait方法后的线程

2、wait和notify必须在同步代码块或是同步函数中使用。因为必须通过锁对象调用这两个方法

六、线程池

底层就是集合 ,集合类型是Thread,当程序第一次启动的时候,创建多个线程,保存到一个集合中,当我们要使用线程的时候,就可以从集合中取出线程来使用,用完之后再把线程放回去。

在JDK1.5之后,JDK内置了线程池,我们可以直接使用。

java.util.concurrent.Executors:线程池的工厂类,用来生成线程池。线程池开启后会一直存在,使用完了线程后,会自动把线程归还给线程池

Executors类中的静态方法:

​ ExecutorService newFixedThreadPool(int nThreads);创建一个可重用固定线程数的线程池,返回值是ExecutorService接口的实现类对象。我们可以使用ExecutorService接口接收(面向接口编程),并调用其submit(Runnable task)方法执行线程任务。

​ void shutdown();关闭/销毁线程池的方法

线程池使用步骤:

​ 1、使用线程池的工厂类Executors的newFixedThreadPool的静态方法生产一个指定数量的线程池

​ 2、创建一个类,实现Runnable接口,重写run方法,设置线程任务

​ 3、调用ExecutorService中的方法submit,传递线程任务,开启线程,执行run方法

​ 4、调用ExecutorService的shutdown销毁, 但是一般不用。因为线程要重用

//自定义线程类
public class MyThread implements Runnable{
    @Override
    public void run() {
        System.out.println("线程"+Thread.currentThread().getName()+"调用");
    }
}

//测试线程池
public class TestPool {
    public static void main(String[] args) {
        ExecutorService executors =Executors.newFixedThreadPool(2);
        executors.submit(new MyThread());
        executors.submit(new MyThread());
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值