多线程基础
进程与线程
- 进程和线程
a) 程序没运行之前就叫做应用程序,当应用程序开始运行,此时就叫做进程。
b) 迅雷开始运行(开始下载多个任务)进程
迅雷进程内部同时在执行多个任务,这多个任务就叫做线程。
线程:进程内部的一个顺序执行流
多线程使用的场景:
线程通常用于在一个程序中需要同时完成多个任务的情况。我们可以将每个任务定义为一个线程,使他们得以一同工作。也可以用于在单一线程中可以完成,但是使用多线程可以更快的情况。比如下载文件。
多线程的并发执行原理:
多线程的并发执行其实并不是多个线程在真正的同时执行。
某一时刻,只有一个线程在执行。
多线程并发执行的的特点:
抢占式的执行:多个线程同时抢占一个时间片,谁抢到谁执行,过了这个时间片之后,多个线程再同时抢占下个时间片的内存资源。
线程是用来做什么的? 执行任务的
线程的生命周期:
5种生命状态:
新建状态:new
就绪状态:Runnable 万事俱备,只欠东风。
运行状态:Running
阻塞状态:Blocked
死亡状态:Dead
通过代码来操作线程:
线程的创建:2种
方式一:继承Thread类 - 写自定义类继承Thread类
- 重写run方法(定义任务)
- 创建线程对象
- 启动 start()
注意点:线程的启动是调用start()而不是直接调用run方法
/**
- 写一个线程类用于执行任务
- @author adminitartor
*/
public class ThreadDemo extends Thread {
@Override
public void run() {
//定义要执行的任务
for(int i=1;i<=50;i++){
System.out.println(i);
}
}
}
public class Test {
public static void main(String[] args) {
//创建线程对象执行任务
ThreadDemo demo = new ThreadDemo();
ThreadDemo demo2 = new ThreadDemo();
//执行任务 - 启动线程
demo.start();
demo2.start();
}
}
方式二:实现Runnable接口
步骤:
- 自定义类实现Runnable接口 – 任务类
- 创建线程对象将要执行的任务添加进来
- 启动线程
/**
- 线程创建方式二:实现Runnable接口
- @author adminitartor
/
任务类
public class RunnableDemo implements Runnable {
@Override
public void run() {
for(int i=1;i<11;i++){
System.out.println(i);
}
}
}
测试类
public class Test {
public static void main(String[] args) {
/
- 执行RunnableDemo任务
/
//创建任务对象
RunnableDemo demo = new RunnableDemo();
//创建线程对象并添加要执行的任务
Thread thread = new Thread(demo);
//启动线程
thread.start();
}
}
两种方式的比较:
继承Thread类:定义好线程类,任务已经确定
实现Runnable接口:
实现了任务和线程的分离,可以将线程和不同的任务绑定,更灵活
Thread类和Runnable的关系
Thread implements Runnable
使用匿名内部类创建线程
定义2个线程对象同时启动执行
线程A:打印输出1-10之间的数字
线程B:打印输出20-30之间的数字
/* - 采用匿名内部类的方式创建线程
- @author adminitartor
*/
public class ThreadDemo02 {
public static void main(String[] args) {
//创建线程A对象
Thread thread = new Thread(){
@Override
public void run() {
for(int i=1;i<11;i++){
System.out.println(i);
}
}
};
//创建线程B对象
Runnable task = new Runnable() {
@Override
public void run() {
for(int i=20;i<31;i++){
System.out.println(i);
}
}
};
Thread thread2 = new Thread(task);
//启动两个线程
thread.start();
thread2.start();
}
}
线程操作API
相关操作方法:
- Thread.currentThread():Thread
- getName() :String
- 给线程重命名:
setName(String):void
new Thread(String name)
new Thread(Runnable,String name)
注意点:
任何一个进程中都会有一个主线程
Java中的main方法其实就是一个主线程,其线程名称是main
sleep(long ms)
该方法会使当前线程进入阻塞状态指定毫秒,当阻塞指定毫秒后,当前线程会重新进入就绪状态,等待分配时间片。
在调用sleep方法时会捕获InterruptedException
interrupt():唤醒线程
如果线程是运行状态,调用此方法无效
只有当线程是阻塞状态,调用此方法会唤醒线程 - yield():礼让
该方法用于使当前线程主动让出当次CPU时间片回到就绪状态,等待分配时间片,调用yield方法,当前线程暂停,进入就绪状态 - join():该方法可以协调多个线程之间同步运行.
多线程并发执行:各执行各的,互不影响 :异步。
同步:多个线程执行是按顺序执行。
同步与异步:
所谓同步执行指的执行有先后顺序.异步执行则执行没有顺序,各干各的.
多线程运行本身的设计是异步运行的.但在某些业务逻辑中需要他们执行各自任务时要有先后,这时就需要协调这些线程之间同步运行.
注意点:调用join方法,暂停的线程进入阻塞状态,当另一个线程执行结束后,阻塞解除。
案例:用局部内部类实现下载和显示两个线程,现要求必须下载完成之后才可以显示。
interrupt():唤醒线程(只能唤醒已经阻塞的线程)
注意:只有线程处于阻塞状态,interrupt()才有效。
join():实现多个线程间的同步执行。
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
t.join(); //调用join方法,等待线程t执行完毕
t.join(1000); //等待 t 线程,等待时间是1000毫秒。
boolean isAlive():测试线程是否处于活动状态
int getPriority():返回线程的优先级
线程优先级:
线程的优先级在线程抢占资源时起到作用,线程的优先级越高,抢占到时间片的几率越大.
线程调度:
Java中的线程调度是抢占式的
线程的切换是由线程调度控制的,我们无法通过代码来干涉,但是我们可以通过提高线程的优先级来最大程度的改善线程获取时间片的几率。
线程的优先级被划分为10级,值分别为1-10,其中1最低,10最高。线程提供了3个常量来表示最低,最高,以及默认优先级:
Thread.MIN_PRIORITY,
Thread.MAX_PRIORITY,
Thread.NORM_PRIORITY
设置优先级方法:
setPriority(int prority)
boolean isDaemon():测试线程是否为守护线程
setDaemon(boolean)
线程分为:前台线程 后台线程(守护线程)
GC就是一个后台线程,后台线程是用于维护前台线程的。
守护线程又称为后台线程. 创建出来的线程默认都是前台线程。
守护线程和前台线程的区别:
使用上守护线程与前台线程没有区别.区别在于结束时机.当一个进程中的所有前台线程都结束时,进程结束,无论该进程中的守护线程是否还在运行都要强制将它们结束。
GC就是一个守护线程。
线程同步
多个线程并发访问同一资源时可能会引起安全问题。
为了解决多线程并发访问安全问题,我们可以将异步操作变为同步操作
实现方式:
关键字:synchronized
Synchronized关键字的用法:
synchronized修饰方法:
多 个 线 程 并 发 访 问 同 一 方 法 资 源 , 如 果 想 实 现 同 步 效 果 , 给 作 为 公 共 资 源 的 方 法 添
加synchronized关键字,那么当线程A先访问此方法时,会获取调用该方法的对象锁,在此线程A访问过程中,其他线程不能获取此对象的锁,当线程A访问结束,对象的锁释放,其他对象可以获得锁对象从而访问同
步资源。
举个栗子:
定义公共资源
public class Task {
public synchronized void task(){
for(int i=1;i<11;i++){
System.out.println(Thread.currentThread().getName()+"–"+i);
}
}
}
测试类
public class SynchronizedDemo01 {
public static void main(String[] args) {
/*
- 创建2个线程对象同时访问Task对象中的task方法
/
Task task = new Task();
Thread thread = new Thread(“线程A”){
@Override
public void run() {
task.task();
}
};
Thread thread2 = new Thread(“线程B”){
@Override
public void run() {
task.task();
}
};
thread.start();
thread2.start();
}
}
注意点:以下情景实现不了线程同步:
情景一:
/* - 公共资源类
- @author adminitartor
/
public class Source {
public synchronized void task1(){
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"–"+i);
}
}
public void task2(){
System.out.println(“开始执行任务。。。”);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“任务执行结束”);
}
}
/*
- 不能实现线程同步情景一
- @author adminitartor
/
public class SynchronizedDemo02 {
public static void main(String[] args) {
/
- 创建2个线程分别访问Source对象中的公共资源
/
Thread thread = new Thread(“线程A”){
@Override
public void run() {
Source source = new Source();
source.task1();
}
};
Thread thread2 = new Thread(“线程B”){
@Override
public void run() {
Source source = new Source();
source.task1();
}
};
thread.start();
thread2.start();
}
}
情景二:
/* - 公共资源类
- @author adminitartor
/
public class Source {
public synchronized void task1(){
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"–"+i);
}
}
public void task2(){
System.out.println(“开始执行任务。。。”);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“任务执行结束”);
}
}
不能实现线程同步情景二
public class SynchronizedDemo03 {
/
- 2个线程对象访问Source对象中的资源
*/
public static void main(String[] args) {
Source source = new Source();
Thread thread = new Thread(“线程A”){
@Override
public void run() {
source.task1();
}
};
Thread thread2 = new Thread(“线程B”){
@Override
public void run() {
source.task2();
}
};
thread.start();
thread2.start();
}
}
结论:
- 多个线程访问的是同一个对象内部的同步资源时,才会实现线程同步
- 只有当线程访问的资源是同步资源时,才需要获得对象的锁
synchronized的互斥性
synchronized修饰多段代码,但是这些同步块的同步监视器对象是同一个时,那么这些代码间就是互斥的.调用这些方法的多个线程不能同时执行,必须等待。
Source类
public class Source02 {
public synchronized void task1() {
String name = Thread.currentThread().getName();
System.out.println(name+“开始执行。。。”);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+“执行结束!”);
}
public synchronized void task2(){
String name = Thread.currentThread().getName();
System.out.println(name+“开始执行任务2.。。”);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+“执行结束任务2.。”);
}
}
测试类
public class SynchronizedDemo04 {
public static void main(String[] args) {
/*
- 创建2个线程对象分别执行task1和task2
*/
Source02 source02 = new Source02();
Thread thread = new Thread(“线程A”){
@Override
public void run() {
source02.task1();
}
};
Thread thread2 = new Thread(“线程B”){
@Override
public void run() {
source02.task2();
}
};
thread.start();
thread2.start();
}
}
synchronized修饰代码块:
有效缩小同步范围可以在保证并发安全的前提下提高并发执行效率.
结构:
synchronized (要加锁的对象){
//代码块
}
通常要加锁的对象,常用的是this,也可以是其他任意对象
静态方法使用synchronized修饰后,该方法一定具有同步效果
此时加锁的对象是:给当前类的Class对象加锁。