多线程(不全)
多线程
程序、进程、线程的概念
程序:硬盘上的一个静态的可执行文件
进程:
正在内存中运行的程序(是动态的), 一个程序可以启动若干进程(像一个类创建若干对象)
进程间通信很复杂, 不好控制.
线程:进程中的子任务(通过多线程实现的多任务, 好处是线程间通信非常容易)
ps:
每个Java程序都有一个隐含的主线程: main 方法
因为线程直接可以被CPU调度, 所以多线程是最大化利用CPU, 并提升任务效率.
何时需要多线程:
-
程序需要同时执行两个或多个任务。
-
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
-
需要一些后台运行的程序时。
多线程也称为高并发
Java中多线程的创建和使用
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来实现。
-
Thread类的特性
-
每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
-
通过该Thread对象的start()方法来调用这个线程
-
-
构造方法
-
Thread():创建新的Thread对象
-
Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
-
Thread(Runnable target, String name):创建新的Thread对象
-
创建并启动线程(), 只学前2种
* 1) 实现的方式, 实现Runnable接口, 这种方式好, 灵活, 避免单继承
* 1) 写一个具体类, 实现Runnable接口, 并实现接口中的run()方法, 这个方法就是线程入口(线程体)
* 2) 创建上面的具体类对象, 并把它作为实参, 再创建Thread对象, 这是最关键的线程对象(是一个栈)
* 3) 调用Thread对象的start()方法, 启动子线程.
*
* 2) 继承的方式, 继承Thread类, 不好, 虽然简单, 但是结构不灵活.
* 1) 写具体类, 继承Thread, 并且必须重写run(), 因为父类Thread中的run什么也没有做.
* 2) 创建具体类对象, 是一个子类对象, 所以就相当于创建了Thread对象
* 3) 调用对象的start.
#### 线程中的常用方法
创建并启动线程(), 只学前2种
线程的停止:通过通知的方式将子线程停止
-
实现Runnable接口 (线程的一个规范):不确定
这种方式好, 灵活, 避免单继承
-
1) 写一个具体类, 实现Runnable接口, 并实现接口中的run()方法, 这个方法就是线程入口(线程体)
public class HelloRunner implements Runnable {
private int count = 500;
-
2) 创建上面的具体类对象, 并把它作为实参, 再创建Thread对象, 这是最关键的线程对象(是一个栈)
-
3) 调用Thread对象的start()方法, 启动子线程.
public static void main(String[] args) {
//2) 创建上面的具体类对象, 并把它作为实参, 再创建Thread对象, 这是最关键的线程对象(是一个栈)
Runnable runner = new HelloRunner();
Thread thread = new Thread(runner); // 创建子栈
thread.setName("子线程"); //设计线程的名称
//3) 调用Thread对象的start()方法, 启动子线程.
//thread.run(); //如果调用了run就是一个普通的方法调用, 不会多线程
thread.start(); // 激活子栈, 并把run()压入子栈的栈中.
//Thread curr = Thread.currentThread(); // 此方法压入哪个栈, 就返回哪个栈的线程对象 用来获取栈的名称
//curr.setName("主线程");
// 主线程继续
/*
for (int i = 0; i < 500; i++) {
System.out.println(curr.getName() + " : " + i);
}*/
HelloRunner2 r2 = new HelloRunner2();
Thread thread2 = new Thread(r2);
thread2.setName("子线程2");
thread2.start();
} -
-
继承Thread类 线程的实体类
不好, 虽然简单, 但是结构不灵活
Thread类的主要方法
-
void start(): 启动线程(激活子栈),并执行对象的run()方法
-
void run() : 线程体, 它必须是被动调用.
-
void setName(String name)
-
String getName()
// 静态方法, 就是类方法, 和调用者无关.
-
static Thread currentThread(); 返回把此方法压入的栈的线程对象
-
int getPriority() :返回线程优先值
-
void setPriority(int newPriority) :改变线程的优先级
-
join(): 线程进行插队行为
-
在线程A中, 调用B线程的join 方法, 会导致线程A阻塞 , 直到线程B结束
-
-
static void sleep(long millis) 让线程进入睡眠状态
-
解除sleep睡眠有2种方式 :
-
1) 时间到了自然醒
-
2) 必须由别的线程调用我的interrupt(), 打断我的睡眠, 我被打断时会抛出异常
-
线程的调度与设置优先级
线程的调度
调度策略:
时间片(大锅饭式):理想主义每个线程都是平等的,在给定的时间内,每个线程都能参与,并且与优先级无关,大家将时间平分
java调度方法:
同优先级线程组成先进先出队列(先到先服务),仍然使用抢占式策略 对高优先级,使用优先调度的抢占式策略
线程的优先级
线程的优先级控制
-
MAX_PRIORITY(10);
-
MIN _PRIORITY (1);
-
NORM_PRIORITY (5);
-
涉及的方法:
-
getPriority() :返回线程优先值
-
setPriority(int newPriority) :改变线程的优先级
-
线程创建时继承父线程的优先级
-
综合练习
/*
编写程序,在main方法中创建一个线程。线程每隔一定时间(200ms以内的随机时间)产生一个0-100之间的随机整数,
打印后将该整数放到集合中;
共产生100个整数,全部产生后,睡眠30秒,然后将集合内容打印输出;
在main线程中,唤醒上述睡眠的线程,使其尽快打印集合内容。(打断子线程的30秒睡眠)
*/
//线程每隔一定时间(200ms以内的随机时间)产生一个0-100之间的随机整数,
public class RandomRunner implements Runnable {
private boolean flag = false;
public boolean isFlag() {
return flag;
}
线程的生命周期
-
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
-
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件
-
运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能
-
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
-
死亡:线程完成了它的全部工作或线程被提前强制性地中止
线程分类::
一种是守护线程,一种是用户线程
-
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
-
守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
-
Java垃圾回收就是一个典型的守护线程。
-
若JVM中都是守护线程,当前JVM将退出。
线程同步(关键字:synchronized。一种同步锁)
不同步产生的问题:并发:同一个对象被多个线程同时操作,例如:火车的最后一张票,银行取钱
synchronized重点:需要再整理
同步方法:
关键字:synchronized 保证数据对象只能被方法访问
包括synchronized方法和synchronized块
synchronized 控制对对象的访问 每个对象对应一把锁 方法一旦执行 就独占该锁
锁的对象应该是变化的量,需要增添改
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
同步块:synchronized(obj){}
obj称之为同步监视器
-
obj可以是任何对象,但是推荐使用共享资源作为同步监视器
-
同步方法中无需指定同步监视器 因为同步方法的同步监视器就是这个对象本身,就是this,或者class
-
同步监视器执行过程:
1.第一个线程访问,锁定同步监视器,执行其中代码
2.第二个线程访问,发现同步监视器被锁定,无法访问
3.第一个线程访问完毕,解锁同步监视器
4.第二个线程访问。发现同步监视器没有锁,然后锁定并访问
JUC
死锁
Lock(锁)
Lock对象 reentrantlock
不安全
浙公网安备 33010602011771号