概念
代码:即文本文件,是静态的。
程序:代码在内存中运行,是动态的。
进程与线程:
- 一个程序代表一个进程,一台主机有多个进程,一个进程有多个线程
- 进程是系统进行资源分配的基本单位,线程是进行运算调度的最小单位。
- 进程是线程的容器,线程是进程中的实际运作单位
线程状态
创建状态:线程对象一旦创建就进入到新生状态
就绪状态:调用start方法后,线程立即进入就绪状态,但不意味着立即调度执行
运行状态:由cpu分配到资源,才进入运行状态,真正执行线程的代码块
阻塞状态:当调用sleep、wait或同步锁定时,线程进入阻塞状态。此时,代码不往下执行,阻塞事件解除后,重新进入就绪状态,等待cpu调度执行
死亡状态:线程中断或结束,一旦进入死亡状态,就不能再次启动
创建线程的方法
1)继承Thread类
- 自定义线程类继承Thread类
- 重写run方法,编写线程执行体
- 创建线程对象,调用start方法启动线程
2)实现Runnable接口
- 自定义线程类实现Runnable接口
- 重写run方法,编写线程执行体
- 创建线程对象,调用start方法启动线程
3)通过Callable和Future创建
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务
- 提交执行
- 获取结果
- 关闭服务
常用API
线程api
//更改线程的优先级
setPriority(int new Priority)
//在指定的毫秒数内让当前正在执行的线程休眠
static void sleep(long millis)
//等待该线程终止
void join()
//暂停当前正在执行的线程对象,并执行其它线程
static void yield()
//中断线程,别用该方式
void interrupt()
//测试线程是否处于活动状态
boolean isAlive()
线程停止
不推荐使用JDK提供的stop()、destroy()方法。已废弃。
推荐线程自己停止下来——>利用次数,不建议死循环,即使死循环也应该添加延时。
建议使用一个标志位进行终止变量。当flag=false时,终止线程运行。
private boolean flag=true;
public void(){
while(flag){}
}
public void stop(){
this.flag=false;
}
线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
//每秒输出系统时间
public class SleepDemo {
public static void main(String[] args) {
Date date = new Date(System.currentTimeMillis());
while(true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
date = new Date(System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功!看cpu心情
public class YieldDemo {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}
线程强制执行
- Join合并线程,待此线程执行完成后,再执行其它线程,其它线程阻塞
public class JoinDemo implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println("线程vip来了"+i);
}
}
public static void main(String[] args) throws InterruptedException {
JoinDemo joinDemo = new JoinDemo();
Thread thread = new Thread(joinDemo);
thread.start();
for(int i=0;i<1000;i++){
if(i==200){
thread.join();
}
System.out.println("main--"+i);
}
}
}
线程状态观测
NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
public class StateDemo{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);//NEW
//观察启动后
thread.start();
state = thread.getState();
System.out.println(state);
while(state!=Thread.State.TERMINATED){
Thread.sleep(100);
state = thread.getState();
System.out.println(state);//输出状态
}
}
}
线程优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
- 线程的优先级用数字表示,范围从1-10
- Thread.MIN_PRIORITY=1
- Thread.MAX_PRIORITY=10
- Thread.NORM_PRIORITY=5
- 使用以下方式改变或获取优先级
- getPriority(),setPriority(int x)
先设置优先级,再启动
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看cpu的调度
守护线程
- 线程分为用户线程和守护daemon线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕,如后台记录操作日志、监控内存,垃圾回收等
//守护线程
public class DaemonDemo {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true);//默认是false,表示是用户线程
thread.start();//守护线程启动
new Thread(you).start();
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
while(true){
System.out.println("上帝保护着你");
}
}
}
//个人
class You implements Runnable{
@Override
public void run() {
for(int i=0;i<36500;i++){
System.out.println("你,开心的活着"+i);
}
System.out.println("good bye!world");
}
}
线程同步机制
并发:同一个对象被多个线程同时操作
抢票事件&取钱事件&厕所
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。线程同步其实就是等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
队列和锁
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其它线程必须等待。
使用后释放锁即可,存在以下问题:
- 一个线程持有锁会导致其它所有需要此锁的线程挂起
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题
同步方法
由于我们可以通过private关键字来保证数据对象只能被方法访问,所以只需 要对方法提出一套机制,这套机制就是synchronized关键字,它包括两种方法:synchronized方法和synchronized块
public synchronized void method(){}
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获取调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法声明为synchronized将会影响效率
同步块
synchronized(Ojb){}
Obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
锁的是需要变化的量
死锁
多个线程各自占有一些共享资源,并且互相等待其它线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
某一个同步块同时拥有两个以上对象的锁时,就可能会发生死锁的问题
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个线程使用
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
只要想办法破坏其中的任意一个或多个条件,就可以避免死锁发生
Lock锁
- 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁