一多线程介绍
现在的操作系统基本都是多用户,多任务的操作系统。每个任务就是一个进程。而在这个进程中就会有线程。
真正可以完成程序运行和功能的实现靠的是进程中的线程,线程有自己的栈(stack)、寄存器(Register)、本地存储(ThreadLocal)。
多线程:在一个进程中,我们同时开启多个线程,让多个线程同时去完成某些任务(功能)。
多线程的目的:充分利用cpu资源,提高程序的运行效率。
二多线程运行原理
CPU负责程序的运行,而CPU在运行程序的过程中某个时刻点上,它其实只能运行一个程序。而不是多个程序。而CPU它可以在多个程序之间进行高速的切换。而切换频率和速度太快,导致人的肉眼看不到。
三创建线程的3种方式
方式一:继承Thead类
注意:启动线程只能用start,不能直接调用run方法
方式二:实现Runnable接口
方式三:带返回值线程
这种方式可以获取线程的返回值,并且抛出异常
备注:future.get()的作用是获取线程执行完毕后返回的结果,该方法是一个阻塞方法
四线程状态的生命周期
注意:除了以上方法,还有以下多个线程自身方法
1:join方法:等待线程结束,线程进入阻塞状态
2:yield方法:告诉调度器,主动让出CPU,让别的线程先执行,该方法执行后线程进入就绪状态
3:stop方法:终止线程,新版本的JDK会直接移除
4:suspend方法:挂起,resume唤醒(由于容易导致死锁,这两个方法已经废弃)
5:sleep方法:sleep执行后线程进入阻塞状态(不释放锁)
suspend不会释放锁,不推荐使用,如果resume发生在suspend之前会导致死锁
五多线程同步之synchronized
理解线程同步
通过同步(Synchronize音译:同步的意思)对临界区的访问可以避免这种线程干扰。某些动作操作对象之前,必须先获得这个对象的锁。获取待操作对象上的锁可以阻止其他对象获取这个锁,直至这个锁的持有者释放它为止。这样,多线程就不会同时执行那些会互相干扰的动作。
锁加在对象上 :给实例对象加锁
synchronized语法
1:如果不加锁会导致出现用户名是jack密码是456的情况发生
2:先抢到锁的先执行,另外的线程等待,保证每次只有一个线程在修改student对象。
3:synchronized执行完会自动释放锁,无需程序员手动释放
4:如果线程执行出现异常,jvm会让线程自动释放锁
5:保证多个线程使用的是同一把锁,否则就没法达到同步效果
*锁加在实例方法上:相当于对当前实例加锁
说明:
1:synchronized如果加载非静态方法上,就等于加在实例对象上
2:要实现线程同步,必须保证使用同一把锁
(四)锁加在静态方法上:相当于对当前类加锁
死锁产生场景
1:线程1要执行必须获取lockA和lockB
2:线程2要执行必须获取lockB和lockA
3:这种情况会导致线程1和线程2都没法执行,即死锁
尽量避免嵌套synchronized或者lock
六多线程同步之lock
众所周知synchronized必须等当前线程执行完后才会自动释放锁。如果获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,这样太影响程序执行效率。因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断)。
通过Lock就可以办到
Lock简介
1:Lock是一个接口在java.util.concurrent并发包下
2:lock()、lockInterruptibly()、tryLock()、tryLock(long time, TimeUnit unit)、是用来获取锁的。
3:unLock()方法是用来释放锁的。
4:方法详解
***lock()***方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
***tryLock()***马上返回,拿到lock就返回true,不然返回false。 比较潇洒的做法。
***tryLock(long time, TimeUnit unit)***方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
***lockInterruptibly()***:ReentrantLock.lockInterruptibly允许某线程在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待
5:ReentrantLock(重入锁、再入锁)
例子1:lock()的正确使用方法
代码解释:两个线程操作同一个arraylist,线程1先抢到锁,当满足某个条件如上(i等于3)线程1释放锁,但是此时线程1并没有结束,线程2获取锁执行。
结论:lock比synchronized代码书写上更加灵活,控制更细节,synchronized无法做到细节的控制
例子2:tryLock()的使用方法
代码解释:两个线程操作同一个arraylist,上面代码也可以做到一次只有一个线程操作arraylist。但是trylock和lock区别在于,即使线程1没有释放锁,线程2不会等待,线程2可以在拿不到锁的情况下去完成其他事情
结论:trylock有返回值获取到锁返回true,否则false,有了这个方法能让并发编程更加灵活
例子3:lockInterruptibly()使用方法
代码解释:t1通过lock.lockInterruptibly()获取锁,如果长时间获取不到锁,其他线程(比如主线程可以去中断),中断后,等待的线程就会进入catch分支,catch分支执行完线程结束。同时其他线程也可以通过t1.isInterrupted()判断等待线程是否中断
小结:lock.lockInterruptibly()和isInterrupted()配合使用,a线程如果长期等待,b线程可以将其中断。
Lock和synchronized的区别?
1 Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2 synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3 lock比synchronized代码书写上更加灵活,控制更细节,synchronized无法做到细节的控制
七多线程之线程池
线程池的好处在于,避免频繁创建线程和销毁线程带来的内存的开销和时间的浪费。同时线程池可以通过设置线程数量防止过度创建线程
newFixedThreadPool(个数)
拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待。如果任务比线程池的线程数多,不会创建新线程。
小结:
a、10个任务并发执行并不是10个线程处理,而是3个线程轮流处理
b、定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。
c、线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
八多线程通讯
线程通信的目标是使线程间能够互相发送信号。例如,线程B可以等待线程A的一个信号,这个信号会通知线程B数据已经准备好了,线程间的通讯可以使用wait和notify实现
package com.sync;
import java.util.ArrayList;
import java.util.List;
public class ListAdd1 {
//list集合
public volatile List list = new ArrayList<>();
//添加元素的方法
public void add(){
list.add(“wendao”);
}
//获取集合大小的方法
public int size(){
return list.size();
}
public static void main(String[] args) throws Exception {
final ListAdd1 add1 = new ListAdd1();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (add1) {
for(int i = 0; i<10;i++){
add1.add();
System.out.println(Thread.currentThread().getName()+"添加了第"+i+"元素");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(add1.size()==5){
try {
//先通知再等待,
add1.notify();
add1.wait();
//先等待在通知是不行的因为wait是阻塞方法
//add1.wait();
//add1.notify();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
},"t1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
while(true){
synchronized (add1) {
try {
//等待线程1的信号
add1.wait();
System.out.println("终止当前线程"+Thread.currentThread().getName());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//给线程1发送信号
add1.notify();
break;
}
}
}
},"t2");
thread2.start();
Thread.sleep(10);//睡眠的目的保证线程2先抢到锁,先执行,线程2一旦wait会立马释放锁,线程1抢到锁后执行
thread1.start();
}
}
小结:
1:wait和notify必须是同一个对象锁,如果不是的话,没法实现线程的通讯
2:wait会释放锁,notify不会释放锁
3:wait是阻塞方法,notify不是