多线程
基础概念
-
程序(program):一段静态代码,一组指令的集合
-
进程(process):程序的一次执行过程,或是一个正在运行的程序,资源分配的单位
-
线程(thread):一个进程可以有多个线程,是独立调度和分派的基本单位
-
线程共享方法区和堆,stack和program couter Register(程序计数器)单独
-
并行:多个cpu同时执行多个任务
-
并发:一个cpu采用时间片轮转法执行多个任务
-
多线程优点:提高程序响应性,提高cpu利用率,改善程序结构(单核cpu多线程执行有可能速度反而变慢)
什么时候需要多线程
-
需要同时执行两个货多个任务
-
程序需要实现一些要等待的任务
-
需要一些后台运行程序
实现多线程方式
继承Thread类
-
不可以直接在主线程调用run方法,这样并没有启动新的线程
-
同一个子类线程对象只能执行一次start()方法,否则会抛出异常,
-
要多个线程只能创建多个对象
if (threadStatus != 0)
throw new IllegalThreadStateException();
/**
* @description:
* @author: liSen
* @time: 2021/6/4 19:40
*/
public class ThreadTest {
public static void main(String[] args) {
//3.创建子类对象执行Start方法
new MyThread().start();//start()方法执行步骤1.启动当前的线程2.执行run方法
for (int i = 0; i <100; i++) {
System.out.println(Thread.currentThread().getName());
System.out.println("主线程");
}
//匿名子类
new Thread() {
实现Runnable接口
package com.company.Thread;
/**
* @description:
* @author: liSen
* @time: 2021/6/4 22:07
*/
public class ThreadTest2 {
public static void main(String[] args) {
//3.创建实现类对象
MThread mThread = new MThread();
//4.创建Thread对象
Thread thread = new Thread(mThread);
//5.调用start方法
thread.start();
}
}
//1.创建一个时间Runnable接口的类
class MThread implements Runnable{
//2.生成run方法
比较实现Runnable更好
-
没有单继承限制
-
能解决多线程数据共享问题
实现Callable接口
/**
* @description: 使用Callable实现多线程,jdk5.0
* @author: liSen
* @time: 2021/6/9 16:13
*/
public class ThreadNew {
public static void main(String[] args) {
//3.生成sum实例
Sum sum = new Sum();
//4.生成FutureTask实例
FutureTask<Integer> futureTask = new FutureTask(sum);
//5.new 一个新的线程
new Thread(futureTask).start();
Integer o = null;
//6.可以获取call的返回值
try {
o = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(o);
}
}
//1。实现Callable接口(可以使用泛型)
class Sum implements Callable<Integer>{
private Integer sum = 0;
//2.实现call方法(可以有返回值)
比Runnable好
-
可以使用泛型
-
call可以有返回值
-
可以抛出异常被捕获
线程池
方法
-
String getName()获取某个线程的名字
Thread.currentThread().getName()//获取当前线程的名字
Thread thread1 = new MyThread()
thrad1.getName();//获取thread1线程的名称 -
new Mythread().start();
-
run()线程在被调度时的执行操作
-
void setName() 设置该线程的名称
1.给主线成命名
Thread.currentThread().setName("123");
2.线程子类实现构造器,
//Thread类自带的命名构造器
public Thread(String name) {
init(null, null, name, 0);
}
//在自己子类实现就好
public MyThread(String threadName){
super(threadName);
} -
static Thread currentThread() 返回当先线程,在子类中就是this,通常在主线下和Runnable实现
-
yield()释放当前cpu的执行权,重新分配执行线程,可能被一个线程连续多次抢到执行权
//1.创建继承Thread子类
class MyThread extends Thread{
//2.重写run方法
-
join()在线程A调用线程B的join(),线程A进入阻塞状态,直到线程B执行完后,线程A才结束阻塞
-
stop()已过时,强制结束线程生命
-
sleep(long millitme)让当前线程数目指定毫秒,指定时间内线程时阻塞的
-
isAlive()判断线程是否存在
-
setPriority/getPriority()设置/获取线程的优先级,高优先级抢占只是概率问题(线程的优先权高度依赖于系统,例如win有七个优先权,java的优先级映射到操作系统的优先级中,但是在Oracle未linux提供的ava虚拟机会忽略线程优先级---所有线程优先级相同。现在不推荐使用优先级)
-
Thread.State getState()获取当前线程的状态,NEW、RUNNABLE...
线程调度策略
-
调度策略:时间片,特点:抢占式(高优先级的线程抢占cpu)
-
Java调度方式:
-
同级线程组成先进先出的队列(先到先服务),使用时间片策略
-
对高优先级,室友优先级的抢占策略
-
-
线程的优先级:
-
MAX_PRIORITY =10
-
MIN_PRIORITY = 1
-
NORM_PRIORITY = 5
-
设置优先级:
-
setPriority()
-
getPriority()
-
-
线程安全问题
-
多个线程方位同一个变量如买票时票的总数,如果使用extends Thread实现多线程,应将该变量设置为static,多个线程访问同一份变量,Runnble是实现是因为只有子类一份实现类,所以无需使用。
线程的生命周期
public enum State {
NEW,//新建:new Thewad(r),线程刚创建还未启动
RUNNABLE,//可运行的,调用start()方法就是处于可运行状态,因为操作系统采用的是抢占式的调度,时间片用完剥夺运行权给其他线程,所以是线程是在运行,也可能没在运行
BLOCKED,//阻塞:当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态
WAITING,//等待:一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒
TIMED_WAITING,//计时等待:同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态,这一状态将一直保持到超时期满或者接收到唤醒通知,带有超时参数的常用方法有Thread.sleep、锁对象.wait()
TERMINATED;//终止:因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
}
-
新建(new):线程被创建时
-
就绪:新建线程被start()后,线程进入线程队列等待cpu时间片,此时已经具备运行条件,只是没有分配资源
-
运行:就绪线程被调度获得cpu资源,进入运行状态,run()定义了线程的操作和功能
-
阻塞:在某种特殊情况下,被人为挂起或者执行输入输出操作时,让出cpu并临时终止自己的执行,进入阻塞
-
死亡:线程完成了他的全部工作或者线程被提前强制性终止或异常导致结束
线程同步
同步代码块
//1.创建一个时间Runnable接口的类
class MThread implements Runnable{
private Integer t =100;
Object obj = new Object();//多个线程必须用同意把锁
//2.生成run方法
//1.创建继承Thread子类
class MyThread extends Thread{
private static Integer t =100;
//2.重写run方法
被同步的代码块相当于单线程
同步方法
//1.创建继承Thread子类
class MyThread extends Thread{
private static Integer t =100;//static多个对象访问同一个元素
//1.创建一个时间Runnable接口的类
class MThread implements Runnable{
private Integer t =100;
Lock(锁)
class Window implements Runnable{
private Integer t = 100;
private ReentrantLock rt = new ReentrantLock(true);//使用可重用锁 (fair:ture,线程按照先进先出的结构使用资源,而非抢占,默认fair:false)
synchronized与Lock
-
推荐使用顺序:Lock---同步代码块---同步方法
-
不同:Lock需要手动添加同步锁(lock),手动释放同步锁(unlock)
同步锁解决懒汉式线程安全问题
//同步方法
class Person{
private static Person p1 = null;
private Person(){//私有不让在外部new对象
System.out.println("对象");
}
public static synchronized Person getPerson(){//同步方法,默认指向this
if(p1 == null){
p1 = new Person();
}
return p1;
}
}
//同步代码块
class Person{
private static Person p1 = null;
private Person(){
System.out.println("对象");
}
public static Person getPerson(){
if(p1 == null){//防止已经生曾对象,多个对象还在等待进去
synchronized(Person.class){
if(p1 == null){//有多个对象在排队到同步区域,判断是否已经存在
p1 = new Person();
}
}
}
return p1;
}
}
死锁
-
不同的线程分别占用对方需要同步的资源不放弃,都在等对方放弃自己同步的资源,造成死锁
线程通信
class Number implements Runnable{
private Integer t = 100;
Object obj = new Object();
方法
-
wait():等待,一但执行此方法线程进入组赛状态,释放同步监视器
-
notify():执行此方法,有一个线程被唤醒,有多个线程的时候根据优先级唤醒一个
-
notifyAll():执行此方法,唤醒所有等待的线程
说明
-
三个方法必须在同步代码块中使用
-
三个方法必须与同步锁指向同一个对象,否则出现异常
-
三个方法继承自Object对象
wait()和sleep()异同
相同:
-
两者都可以使得线程进入阻塞状态
不同:
1. sleep()属于Thread()方法,wait()属于Object()
2. sleep()不依赖与同步代码监视器synchronized,wait()依赖与synchronized
3. sleep()不会释放lock,wait()会释放lock
4. sleep()不用手动唤醒(到时间自动唤醒),wait()需要手动唤醒
生产者消费者问题
package com.company.Thread;
/**
* @description:
* @author: liSen
* @time: 2021/6/9 15:42
*/
public class ProductTest{
public static void main(String[] args) {
Clerk clerk = new Clerk();
Product product = new Product(clerk);
Consumer consumer = new Consumer(clerk);
product.setName("生产者1");
consumer.setName("消费者1");
product.start();
consumer.start();
}
}
class Product extends Thread{
private Clerk clerk = null;
public Product(Clerk clerk) {
this.clerk = clerk;
System.out.println("开始生产");
}