一.线程与进程
现代操作系统调度的最小单位是线程,也叫轻量级进程,在一个进程里可以创建多个线程,线程是程序程序内部的一个执行路径,而一个进程可以有多个执行路径。线程都拥有各自的计数器,堆,栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。
二.并行和并发
并行:指两个或两个以上的活动在同一时刻发生。
并发:指在某一时刻只有一个事件或活动发生,某个时间段内由于CPU的交替执行,可以发生多个时间。
三.开启多线程的方法
1.继承Thread类,重写run方法
class MyThread extends Thread{
@Override
public void run(){
System.out.println("hello Thread !");
}
}
public class Main{
public static void main(String[] args){
MyThread thread = new MyThread();
thread.start(); //调用start()方法开启多线程,会自动调用run()方法
}
}
注意事项:
到这里大家可能会发现一个问题,为什么我们不直接去调用对象的run方法,而是要通过start方法去间接调用呢?
需要注意的是,当我调用start方法时,他不仅会帮我们去调用run方法,更重要的是会去创建一个新的线程,然后该线程去调用run方法。而直接去调用run方法,则不会创建一个新的线程。
2.实现Runable接口,重写run方法
class runimpl implements Runable{
@OverRide
public void run(){
System.out.println("hello runable");
}
}
//主线程
public class Main{
public static void main(String[] args){
runimpl r = new runimpl();
Thread t = new Thread(r); //这个地方可以使用Lambda表达式简写 new Thread(()->{Ststem.out.println("hello runnable")},"新线程");
t.start();
}
}
四.为什么要使用多线程?
1.更多的处理核心
线程是大多数操作系统调度的基本单元,一个程序作为一个进程来运行,程序运行过程中能够创建多个线程,而一个线程在一个时刻只能运行在一个处理核心上。试想一下一个单线程程序在运行时只能使用一个处理核心,那么再多的处理核心加入也无法显著提高该程序的执行效率。相反,如果该程序使用多线程技术,再将计算逻辑分配到多个处理器核心上,就会显著减少程序的处理时间,并且随着更多的处理核心的加入而变得更有效率。
2.更快的响应时间
有时我们会编写一些较为复杂的代码,例如一笔订单的创建,它包括插入订单数据,生成订单快照,发送邮件通知卖家和记录货品的销售数量。用户从点击“订购”按钮开始,就要等待这些操作全部完成才能看到订购成功的效果。但是这么多业务操作,如何能够让其更快的完成呢?可以使用多线程技术,即将数据一致性不强的操作派发给其他线程处理,如生成订单快照,发送邮件等。这样做的好处是相应用户请求的线程能够尽快的处理完成,缩短了响应时间。
3.更好的编程模型
java多线程编程提供了良好,考究并且一致的编程模型,使开发人员更加专注于问题的解决,即为遇到的问题建立合适的模型,而不是绞尽脑汁的考虑如何将其多线程化。
五.线程的生命周期
1.NEW 初始状态,线程被构建,但但还是没有调用start()方法
2.RUNNABLE 运行状态,java线程将操作系统中的就绪和运行两种状态笼统的称为"运行中"
3.BLOCKED 阻塞状态,表示线程阻塞于锁
4.WAITING 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定的"动作"
5.TIME_WAITING 超时等待状态,该状态不同于WAITING,它是可以在指定的时间特定返回的
6.TERMINATED 终止状态,表示当前线程已经执行完毕
六.线程中常用的方法
1.getName/setName(String) 获取和设置当前线程对象的名字
2.currentThread 获取当前线程的对象,静态方法
3.getPriority/setPriority(int) 设置或获取线程的优先级,java线程的优先级有10个,为1 - 10
4.join 等待该线程终止
5.yield 暂停当前正在执行的线程对象,并执行其他线程。静态方法。
6.sleep 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。静态方法
public class Main{
public static void main(String[] args){
//结构lambda表达式创建一个线程t,线程的名字为A
Thread t = new Thread(()->{System.out.println(Thread.currentThread().getName());},"A");
//输出主线程的优先级
System.out.println(Thread.currentThread().getPriority());
//主线程休眠1秒
Thread.sleep(1000);
}
}
七.线程安全问题及其解决方案
当多个线程去争夺同一个资源的时候就会产生线程安全问题,为了避免这样的问题的发生。我们就引入的线程的同步机制,对于一些共享的资源,在某个时刻只能有一个线程去访问。那么如何才能保证某一时刻只能有一个线程去访问共享资源呢?使用synchronized关键字是解决该问题的方法之一。
为了更加理解我们的多线程安全问题,在这里我们结合经典的"买票问题"进行分析。
/*
问题分析:
票的资源类
卖票的线程(假设多个)
同步方法解决线程安全问题
*/
class Ticket{
private int number = 100; //假设初始有100张票
//卖票的的方法,必须要加synchronized关键字
public synchronized void saleTicket(){
//只有当票的数量大于0的时候才可以买票
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--));
}
}
}
/*
//同步代码块解决多线程安全问题
class Ticket{
private int number = 100;
private static Object obj = new Object();
//卖票的方法,同步代码块解决此问题,注意锁对象必须是同一个,所以在这里我们将obj声明为静态对象
public void saleTicket(){
synchronize(obj){
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--));
}
}
}
}
*/
public class Main{
public static void main(String[] args){
Ticket ticket = new Ticket(); //票的资源类
new Thread(()->{for(int i=0;i<=101;i++){ticket.saleTicket();}},"A").start(); //卖票线程A
new Thread(()->{for(int i=0;i<=101;i++){ticket.saleTicket();}},"B").start(); //卖票线程B
new Thread(()->{for(int i=0;i<=101;i++){ticket.saleTicket();}},"C").start(); //卖票线程C
}
}
多线程安全问题产生原因的分析:
当没有对共享变量进行访问控制时,就会出现多个进程对同一个资源的争夺,这便会产生线程安全问题
当对共享变量进行了访问控制后
注意事项:
1.普通同步方法的锁对象是this,而静态同步方法的锁对象是当前类的Class对象。
2.那些操作会释放锁对象,那些操作不会释放锁对象?