线程的创建与启动Thread类有两个常用构造方法Thread()/Thread(Runnable)
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法----适用于此类有父类的情况下
多线程常用方法
Thread.currentThread()//获取当前线程的线程信息
Thread.currentThread().getName()//获取当前线程的线程名称
myThread.setName("mmmmmmmmmm");//设置此线程名称
this.getId()//获取此线程id
myThread.getName();//获取线程名称
this.getName()//获取当前线程名称,如果是用Thread(Runnable)构造方法传参创建的线程,则获取当前线程名称是传入参数的线程名称
myThread.isAlive()//获取线程活动状态
Thread.sleep(1000);//让当前线程睡眠多少毫秒,和谁调用没关系
myThread.join()//此时myThread线程开始执行,结束后当前线程才开始执行
Thread.yield()//线程让步,放弃cpu执行权,不是阻塞,是从运行到就绪状态,让其他线程去执行
myThread.setPriority();//设置线程优先级1-10,优先级越高,获得cpu资源越多。并不能保证优先级高的线程先运行优先级具有继承性,一般情况采用默认的优先级即可,默认值5
myThread.interrupt();//打断睡眠线程,依赖异常处理机制,会抛出异常
myThread.setDaemon(true);//设置线程为守护线程:当线程只剩下守护线程的时候,JVM就会退出,必须在myThread.start()之前设置
注意:在子线程的run方法中,如果有编译时异常需要处理,只能捕获处理,不能抛出异常
线程生命周期
getState()获取线程当前生命周期,有以下几种:
- NEW新建状态,创建线程对象,在调用start()之前的状态
- RUNNABLE()可运行状态,包括READY(就绪)和RUNNING(运行),READY状态可以被线程调度器进行调度至RUNNING状态。Thread.yield()可以将状态从RUNNING转换至READY
- BLOCKED阻塞状态,线程发起阻塞的I/O操作,或者申请由其他线程占用的独占资源,线程会转换为BLOCKED阻塞状态,此时不会占用CPU资源,当阻塞I/O执行结束或线程获得了申请的资源,线程可以转换为RUNNABLE
- WAITING等待状态,线程执行了object.wait(),thread.join()方法会把线程转换为WAITING等待状态,执行object.notify()方法,或者加入的线程执行完毕,当前线程会转换为RUNNABLE 状态
- TIMED_WAITING状态,与WAITING类似,都是等待状态,区别在于此状态的线程如果没有在指定时间内完成期望的操作,该线程会自动转换为RUNNABLE
- TERMINATED终止状态,线程结束
线程状态图
多线程编程优势及存在的风险
优势:
- 提高系统吞吐率,多线程使一个进程有多个并发
- 提高响应性,Web服务器采用专门的线程负责用户的请求处理,缩短用户等待时间
- 充分利用多核处理器资源,多线程的方式。
问题与风险:
- 线程安全问题,多线程共享数据时,如果没有采取正确的并发访问控制措施,就可能产生数据一致性问题,如读取脏数据,丢失数据更新
- 线程活性问题,由于程序自身的缺陷或者由资源稀缺性导致线程一直处于非RUNNABLE状态,常见活性故障:
- 死锁(Deadlock)
- 锁死(Lockout)
- 活锁(Livelock)
- 饥饿(Starvation)
- 上下文切换,处理器从执行一个线程转到执行另外一个线程
- 可能会由一个线程导致JVM意外终止,其他线程也无法执行
线程安全问题:原子性,可见性,有序性
- 原子性(Atomic) :
- 使用锁,锁具有排他性,保证共享变量在某一时刻只能被一个线程访问
- 利用处理器的CAS指令,直接在硬件(处理器和内存)层次实现,看作是硬件锁
- 可见性:一个线程对共享变量进行更新后,其他线程可能无法立即读取到更新后的结果,如果其他线程可以读取到,称这个线程对共享变量的更新对其他线程可见,否则称为不可见
- 有序性:多线程下,重排序会引起问题
什么时候数据在多线程并发的环境下会存在安全问题呢?
- 多线程并发
- 有共享数据
- 共享数据有修改的行为
Java三大变量:
- 实例变量:在堆中
- 静态变量:在方法区中
- 局部变量:在栈中
哪些变量存在线程安全问题?
1、局部变量永远不会存在线程安全问题,因为局部变量不共享,常量也不存在线程安全问题
2、实例变量在堆中,堆只有一个 静态变量在方法区中,方法区也只有一个。所以他们都是多线程共享,存在线程安全问题
开发中解决线程安全问题:
-
尽量使用局部变量
-
创建多个对象,一个线程对应一个对象,对象不共享
-
synchronized,使用线程同步机制----这种会降低程序运行速率
synchronized(){ }//小括号中填写想要让哪些多线程同步,填写他们共享的数据
synchronized出现在实例方法上缺点?
- 这时候只能锁this,所以这种方法不灵活
- 此时整个方法体都需要同步,可能无故扩大同步范围,降低程序效率。
synchronized出现在实例方法上优点?
- 代码简洁
- 如果此时共享对象就是this,而且需要同步的代码块是整个方法体,建议用这种方式
线程安全举例 Vector Hashtable
线程不安全举例 ArrayList HashMap HashSet
**类锁:**synchronized出现在静态方法上,此时无论创建多少个对象,都需要进行排队等待,类锁,一个类只有一把锁,所有对象都是根据类创建的,所以他们都需要进行等待。
死锁 鹬蚌相争,这种不会报错也没有异常
终断线程方法:
- stop方法,会丢失数据,古老方法。过时,不适用
- interrupt,
- 处于运行状态,需要设置和接收interrupt判断是否终断
- 处于阻塞状态,会抛出异常,catch并不会自动结束程序,因为它处理了异常,程序还是会按照正常顺序运行并判断是否结束
- 两种状态都会执行结束他们程序之后才会结束
- 设置属性,如flag,需要终断时,设置为false
守护线程:
myThread.setDaemon(true);//设置线程为守护线程,当线程只剩下守护线程的时候,JVM就会退出,必须在myThread.start()之前设置
定时器实现
- 多线程的sleep()方法
- java.util.Timer类
- 实现Callable接口----优点:可以获取线程返回值,缺点:效率低,在获取执行结果时,当前线程受阻塞
Timer类,定时器实现代码:
public class TimerTest {
public static void main(String[] args) throws ParseException {
//定义时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date parse = sdf.parse("2020-12-22 19:06:40");
//创建定时器对象
Timer timer = new Timer();
// Timer timer=new Timer(true);守护线程的方式
//注意定时任务TimerTask是抽象类,需要单独写定时任务类,第一个参数是定时任务对象,第二个是执行时间,第三个是多久间隔执行一次
timer.schedule(new DingShi(), parse);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("这是匿名内部类的方法");
}
}, parse);
}
}
class DingShi extends TimerTask {
@Override
public void run() {
System.out.println("定时器执行任务");
}
}
用Callable接口实现定时器代码:
public class TimerCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//必须传入一个Callable接口实现类对象
FutureTask futureTask = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {//call方法相当于run()只是这个有返回值
int a = 10;
Thread.sleep(1000);
return a;
}
});
Thread thread = new Thread(futureTask);
thread.start();
Object o = futureTask.get();//获取返回值
System.out.println(o);
System.out.println("main");
}
}
Object类的wait方法和notify方法,注意不是线程对象的方法,是普通java对象都有的方法
wait方法让正在此对象上活动的线程进入无限期等待状态,直到被唤醒
notify方法唤醒对象上处于等待状态的线程,notifyAll是唤醒对象上所有处于等待状态的线程
两种方法都建立在synchronized线程同步的基础上
注意:
- wait方法会释放之前占有的锁
- notify方法只会通知,不会释放之前占有的锁
生产者和消费者模式一个生产,一个消费,可以用wait和notify结合使用实现
代码练习