Java 多线程详解
一.重点
1.创建和启动线程
2.实现线程调度
3.实现线程同步
4.实现线程通信
1.为什么要学习多线程?
当多个人访问电脑上同一资源的时候,要用到多线程,让每个人感觉很多电脑同时为多个人服务。
比如:
1.1.排队叫号系统,多个人同一时间请电脑生成一张等待票据时,
如果没有多线程的话,有可能会生成同一等待号的票据。
1.2.两个乘客拿到同一张火车票或飞机票。
2.什么是线程?
先了解线程与进程的区别。
1.一个进程包含多个线程
2.cup执行的是线程,而不是进程
3.线程是共享资源的,进程是独立占用资源的。
4.线程虚拟瓜分计算机资源,而进程是真实的瓜分计算机资源。
线程的定义:线程被认为带有自己的程序代码和数据的虚拟处理机的封装。
结论:线程是强占式操作的。
3.线程的创建与启动。
定义线程有两种方式:
3.1 继承java.lang.Thread类
3.2 实现java.lang.Runnable接口 节约一个宝贵的继承机会,因为Java是单继承。
使用线程的步骤:
3.1 定义一个线程,同时指明这个线程所要执行的代码。
3.2 创建线程对象
3.3 启动线程
线程的生命周期:有四个状态:
3.1 新生状态、
3.2 可运行状态、
3.3 阻塞状态
3.4 死亡状态
解决线程的抢占执行有如下三种:
4.线程调度
线程调度的方法:
4.1 join()方法
4.2 sleep()方法
4.3 yield()方法
5.线程同步
需要注意,同步不是同时,同步其实指的排队执行,异步反而指的时同时操作
5.1 同步方法( synchronized )
5.2 同步代码块( synchronized)
public class TestAccount implements Runnable {
// 所有用TestAccount对象创建的线程共享同一个帐户对象
private Account acct = new Account();
public void run() {
for (int i = 0; i < 5; i++) {
makeWithdrawal(100);// 取款
if (acct.getBalance() < 0) {
System.out.println("账户透支了!");
}
}
}
private void makeWithdrawal(int amt) {
if (acct.getBalance() >= amt) {
System.out.println(Thread.currentThread().getName() + " 准备取款");
try {
Thread.sleep(500);// 0.5秒后实现取款
} catch (InterruptedException ex) {
}
// 如果余额足够,则取款
acct.withdraw(amt);
System.out.println(Thread.currentThread().getName() + " 完成取款");
} else {
// 余额不足给出提示
System.out.println("余额不足以支付 " + Thread.currentThread().getName()
+ " 的取款,余额为 " + acct.getBalance());
}
}
}
6.线程通信
线程通信的方法:
wait();
notify();
notifyAll();
生产者与消费者问题。
二.线程定义
1 何为线程
在一个进程程序中运行的多条分支,这些分支会被cpu轮流切换执行,这些分支我们就叫线程
2 进程vs线程
进程是一个完整的程序应用,由操作系统去创建执行,进程之间是相互独立的,各自有自己的内存空间,互相之间不能访问各自的内存。例如我们打开记事本,就开启了记事本进程,我们打开酷狗,实际就运行了一个酷狗进程,记事本和酷狗进程是不能互相访问,CPU在调度切换执行不同进程,由于速度太快,感觉像是同时在进行
线程是进程内部的多路分支,这些分支共享进程的整个内存,所以线程是可以互相访问,也经常容易造成数据冲突,跟进程一样,线程也是由CPU在高速的切换执行
3 为什么需要线程
1)多线程改善交互性
2)提高性能
cpu是非常强大,我们平常只是使用部分,为了更大程度利用cpu,我们可以开辟多个线程执行,cpu执行更多的任务
三.定义线程的2种方式
(1)继承Thread
(2)实现Runnable
为什么提供继承Thread的方式,又要提实现Runnable的方式?
为了节约一个宝贵的继承机会
Hero extends Spriter implements Runnable
Tree extends Spriter implements Runnable
四.线程运行的原理
线程的三要素:
1 执行代码
2 CPU资源
3 时间片
start()启动线程,然后start()内部就会去调用线程run()
五.线程的名字
Thread(String name)
setName(String name)
getName()
Thread.currentThread()
六.线程的优先级
1 多线程情况下,是平等的去抢夺cpu资源,换句话说,大家的优先级相同
2 我们可以设置线程的优先级
高 - 被cpu执行的概率要高(抢夺cpu的能力强)
低 - 被cpu执行的概率要低(抢夺cpu的能力弱)
3 Thread类提供三种优先级常量
public static final int MAX_PRIORITY 10
public static final int MIN_PRIORITY 1
public static final int NORM_PRIORITY 5 (默认)
4 API
setPriority(int priority)
getPriority()
5 垃圾回收器
后台的一个低优先级的线程,该线程会不定时扫描垃圾对象
七.线程状态
(1)线程状态总述
对象
创建--->消亡
线程
1 创建状态 (新生状态)
刚刚new出来,在堆内存中分配内存空间
2 就绪状态 (可运行状态)
表示线程已经做好准备,可以被cpu执行,等待cpu分配时间片执行
3 运行状态
被cpu选中执行,是一种正在执行的状态,此时会执行run()方法内部的代码
4 阻塞状态
相当于一种休眠状态,被动的放弃cpu执行的机会,比如由IO流阻塞导致,或者由另一个线程合并导致
5 等待池等待状态
也是主动放弃cpu执行的机会,让给其它线程,需要等待其它的线程通知唤醒
6 锁池等待
多个线程同时执行同一份代码,如果想完整的执行完这段代码,然后才轮到另一个线程执行
此时就可对这份代码上锁,先获得锁的线程先执行,其它线程就在锁池里等待,等待正在执行的线程释放锁
7 结束(消亡)
当线程的run()方法执行完,或者调用stop()停止时,线程就进入到消亡状态
(2)就绪状态(可运行状态)start
start()方法让一个线程做好准备,进入到就绪状态,还不会立刻执行,可运行状态
(3)运行状态
1.run
当cpu调用到该线程时,那该线程就处于运行状态,运行状态中执行run()方法里面的代码
2.yield
如果此时yield的话则会进入就绪状态
(4)阻塞
1.sleep
2.join
等待调用该方法的线程结束后再继续执行本线程
合并,如果A线程join到B线程,就会导致B线程进入阻塞状态
3.io
(5)等待
1.线程同步的数据安全问题
数据冲突发生的条件
1 多线程
2 多个线程访问操作同一个数据
2.同步锁和锁池
同步锁
相对于异步来说
需要注意,同步不是同时,同步其实指的排队执行,异步反而指的时同时操作
锁池
当多线程同时执行一段上了锁的代码时,获得锁的线程可以执行,其它线程就只能在锁池等待释放锁
一旦线程释放锁,大家又回到同一起跑线,开始竞争锁
3.Synchronized代码块
1 如果一段代码会访问操作一个共同的数据,我们将这段代码用synchronized锁住,这段代码块就称为同步代码块
synchronized (lock) {
//代码
}
2 注意
上的锁一定是同一把,如果不是同一把,多线程操作仍然可能会出问题。因为多把锁,每个线程都可能获得一把锁
lock一定是同一个对象
3 项目举例
public Ticket takeTicket(String businessCode,String ip){
synchronized (lock) {
//1 按照业务类型查得该业务类型今天的最大号
select max(no) from ticket where business_code=? and take_date between ? and ?;
//2 判断max(no)有没有达到最大号
if(max(no) <= business_limit){
//3 生成号码对象
Ticket t = new Ticket();
t.setNo(max(no)+1);
t.setBusinessCode(?);
t.setTakeIp(ip);
。。。。
//4 将新生成的号插入数据库
insert(t);
//5 返回
return t;
}
}
}
4.Synchronized方法
1 当我们锁住的代码外面没有更多的其它代码时,我们可以将synchronized放到放到方法的声明上
public synchronized void fn(){
}
2 synchronized方法锁住锁是当前对象,就是this
这里比较容易出问题,当创建多个对象时,就会有多个this,每个this都是不同的锁,达不到同步的效果了
3 synchronized代码块比synchronized方法能够控制更细的粒度
5.死锁
发生在已经获得一把,又像获得另一锁,锁嵌套的问题
synchronized(lock1){
synchronized(lock2){
}
}
死锁只能避免,不能解决
6.synchronized的优缺点
优点
保证共享数据安全
缺点
消耗更多资源,效率低
(6)消亡
调用stop
run()执行完成
进入消亡的状态的线程不能再启动了,等待垃圾回收
八.线程通信
(1)wait
对象监视器 - 实际上就是synchrozied锁住的锁对象
只有在synchrozied锁住的锁对象上才可以调用wait方法,否则会报错
调用wait()的线程会导致等待,此时会释放掉锁,让其它线程获得锁
(2)notify
(3)生产者&消费者
当生产者生产的产品需要放置在一个空间,而这个空间是资源有限的时候,就需要等待(空间满时)
当消费者消费产品时,如果没有产品可以消费,那么此时也需要等待
九.同步集合
1 同步集合也叫线程安全的集合,在多线程操作时不会发生数据冲突问题,以前我们学习的ArrayList这种集合是线程不安全的,在多线程情况下会发生问题
//private static List list = new ArrayList();//线程不安全
//private static List list = new Vector();//线程安全
//建议使用下面的方式得到一个同步集合,在效率上要比Vector高一点
private static List list = Collections.synchronizedList(new ArrayList());//线程安全
2