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