让不同的程序块一起运行,如此一来可让程序运行更为顺畅,同时也可达到多任务处理的目的
程序、线程、进程的概念
程序:使用某种语言编写的一组指令(代码)的集合,静态
进程:运行的程序,表示程序一次的动态执行过程,经历了从代码加载、执行到执行完毕的一个完整过程,当程序运行完成,进程结束。
CPU的时间分片:抢占式
进程是进行系统资源分配、调度和管理的最小单位。
进程是每个独立执行的程序,每个进程都有自己独立的内存单元。因此进程间的信息交互比较麻烦。
线程是CPU调度和分派的基本单位
进程中,可以有多条执行链路,这些执行链路称为线程。同一个进程中,多个线程共享这个进程的内存资源。
每个线程独立拥有:虚拟机栈、程序计数器
所有线程共享:方法区static、堆 并发
多线程是是指一个进程在执行过程中可以产生多个线程
进程与线程的关系:
进程包含线程,一个进程包含多个线程,一个进程最小必须包含一个线程(主线程 main方法)
main方法创建过程,就是线程创建过程
一个进程死亡,这个进程中的所有线程死亡
线程销毁,进程未必会关闭
进程与线程的区别:
进程:每个进程都有自己独立的代码和数据空间,进程间的切换开销大
线程:一个进程内的多个线程,共享代码和数据空间,线程间的切换开销比较小
多线程是实现并发机制的
并发:一个CPU执行不同的任务 多个任务多个线程 (秒杀iphone14 多个用户进入程序占到 谁抢到是谁的
并行:多个CPU执行各种不同的任务 (你在扫地我在洗碗 不相关
多线程速度快吗?
不一定 没有直接联系 资源占用也会导致 CPU程序切换 由CPU核数决定 多个同步
Java实现多线程
多线程就是一个进程在执行过程中可以产生多个线程
jdk1.5前创建线程的方式:1. 继承Thread类 2. 实现Runnable接口
jdk1.5之后:1. 实现Callable接口 2.线程池
继承Thread
-
编写一个类继承Thread,该类是线程类
-
重写run(),编写该线程需要完成的任务
-
创建线程类对象
-
调用start()方法,启动线程
package Example;
public class MyThread extends Thread{//1.
public MyThread(String name) {
super(name);
}
@Override //2.重写run方法
public void run(){
System.out.println("执行了线程的run方法");
for (int i = 0; i < 10; i++) {
System.out.print(Thread.currentThread().getName());//得到线程的名字
System.out.println("输出"+i);
}
}
}
package Example;
public class Test {
public static void main(String[] args) {
//启动线程
//3.创建线程对象
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t1");
t1.start();//4.启动线程
// 线程与其他线程抢占CPU资源 谁抢到运行谁
t2.start();
t2.run();//run方法 是main线程
System.out.println("main线程执行完成...");
}
}
注意:
-
线程启动,一定是调用start(),不是调用run() ;如果调用run() 只是调用方法,没有创建线程。
-
一个线程一旦启动,就不能重复启动
对处于新建状态的线程两次调用start()方法会报
IllegalThreadStateException
异常,线程启动后,会调用 native start0() 方法,使得状态不为0当线程死亡以后,不能再次调用start()方法来启动该线程,调用也会返回IllegalThreadStateException异常。
模拟使用多线程从网络下载多个图片
使用第三方依赖:commons-io.jar 下载
java项目添加jar包
-
项目下创建lib文件夹
-
拷贝jar包到lib下
-
File- Projects Structure-Libraries- + - Java 设置依赖 设置lib目录为库
-
该jar添加给模块
package Download;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class MyThread extends Thread{
private String url;//网址
private String filename;//保存文件名
public MyThread(String url, String filename,String name) {
super(name);//线程名
this.url = url;
this.filename = filename;
}
@Override
public void run() {
downloadFile();
System.out.println(Thread.currentThread().getName()+"线程下载完成");
}
public void downloadFile(){
try {
FileUtils.copyURLToFile(new URL(url),new File(filename));
} catch (IOException e) {
e.printStackTrace();
}
}
}
package Download;
public class Test {
public static void main(String[] args) {
String url1 ="https://tse3-mm.cn.bing.net/th/id/OIP-C.LhlYOqLOYH08l-mpprGFSAAAAA?pid=ImgDet&rs=1";
String url2 ="https://pic3.zhimg.com/v2-fa1fa693bc0a7f631f87d442366fdd0e_r.jpg?source=1940ef5c";
MyThread t1 = new MyThread(url1,"1.jpg","t1");
MyThread t2 = new MyThread(url2,"2.jpg","t2");
t1.start();
t2.start();
}
}
实现Runnable接口
启动线程:都必须借助Thread的start()
Runnable实现类:一个线程的任务类
package RunnableDemo;
public class MyRunnable implements Runnable{//1.实现Runnable接口
@Override
public void run() { //2.重写run方法
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
package RunnableDemo;
public class Test {
public static void main(String[] args) {
//3.创建任务对象
MyRunnable task = new MyRunnable();
//4.创建线程
Thread t1 = new Thread(task, "t1");
Thread t2 = new Thread(task, "t2");
//5.启动线程
t1.start();
t2.start();
}
}
继承Thread类与实现Runnable接口的区别
-
继承Thread类不能继承别的类(java单继承);实现Runnable接口可以继承别的类
-
实现Runnable接口,可以让多个线程共享这个实现类对象
-
继承Thread类,启动简单;实现Runnable接口,必须依赖Thread类的start()方法
推荐使用Runnable接口
实现Callable接口
-
编写实现Callable接口的类,重写call()方法
-
启动线程
-
创建Callable实现类对象
-
创建一个FutureTask对象,传递Callable接口实现类对象, FutureTask异步得到Callable执行结果, (使用FutureTask类来包装Callable对象)提供get() FutureTask 实现Future接口,调用get方法实现Runnable接口
-
创建一个Thread对象, 把FutureTask对象传递给Thread, 调用start()启动线程
package CallableDemo;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {//1.编写实现Callable接口的类,重写call()方法
//线程睡眠 单位: 毫秒
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName()+"执行完成");
return "Callable";
}
}
package CallableDemo;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
MyCallable myCallable = new MyCallable();//3.创建Callable实现类对象
//4.封装成FutureTask
FutureTask<String> task = new FutureTask<>(myCallable);
Thread t1 = new Thread(task,"t1");//5.创建一个Thread对象
//启动线程
t1.start();
//获取结果
// String rs = task.get();//阻塞 是main执行
//设置超时时间 一旦到达,停止执行,抛出异常
String rs = task.get(1, TimeUnit.SECONDS);
//取消执行
task.cancel(true);
// System.out.println(rs);
System.out.println("main执行完成");
}
}
线程的生命周期
五个状态:
新生状态:线程new出来,没有调用start()
就绪状态:线程调用start();
运行状态:线程抢到cpu资源,执行run()方法
阻塞状态:调用sleep(),没有获取到锁,线程挂起
死亡状态:正常死亡、非自然死亡
拿到线程状态:getState()
线程优先级
范围:1-10 10优先级最高 正常设置为5
setPriority(数字):给线程设置优先级 getPriority():获取线程的优先级,
线程优先级高的与低的一起抢占资源 优先级高的抢占成功的几率大
礼让:yield()
A线程调用yield(),A线程暂停运行后与B一起抢占资源,如果还是A抢到,还是A继续执行;如果B抢到,给B执行。 (我礼貌一下,如果我厉害还是我抢到)
join():类似插队
在执行A线程过程中准备B线程,并让B线程就绪,A执行一半之后,B调用Join方法,使得A线程阻塞,B线程启动执行,B执行完后才到A。(野蛮人:我让你停你就得停)
线程的终止
通过给定属性,然后通过控制属性值来破坏继续执行的条件
设置boolean值或通过数值判断是否相等
线程的同步
用关键字synchronized给对象加互斥锁.
同步代码块
Object obj = new Object();
...
synchronized(this){//this被加锁,任何其他要锁this的方法和代码块被阻塞.
需要同步的代码;
}
...
synchronized(obj){//obj被加锁,任何其他要锁obj的代码块被阻塞.
需要同步的代码;
}
同步方法
public synchronized void method1(){
…
}
如果有一个线程进入了该方法,其他线程要想使用当前this对象的任何同步方法,都必须等待前一个线程执行完该同步方法之后
package Test2;
public class DeadLockTest implements Runnable{
private int flag;
public DeadLockTest(int flag) {
this.flag = flag;
}
static Object lock1 = new Object();
static Object lock2 = new Object();
@Override
public void run() {
System.out.println("当前锁标志:"+flag);
if (flag == 0){
synchronized (lock1){
try {
System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock1);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程"+Thread.currentThread().getName()+"等待获得锁"+lock2);
synchronized (lock2){
System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock2);
}
}
if (flag == 1){
synchronized (lock2){
try {
System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock2);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程"+Thread.currentThread().getName()+"等待获得锁"+lock1);
synchronized (lock1){
System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock1);
}
}
}
}
线程死锁
互斥使用,即当前资源被一个线程使用(占有)时,别的线程不能使用
例子:实例化了两个实现了Runnable接口的DeadLockTest对象test1和test2,test1的flag等于0,所以在thread1线程执行的时候执行的是run()方法的前半部分的代码,test2的flag等于1,所以在thread2线程启动的时候执行的是run()方法后半部分的代码,此时,出现了下列现象:thread1线程占有了lock1对象并等待lock2对象,而thread2线程占有了lock2对象并等待lock1对象,而lock1和lock2又被这俩个线程所共享,所以就出现了死锁的问题了。
package Test2; public class DeadLockTest implements Runnable{ private int flag; public DeadLockTest(int flag) { this.flag = flag; } static Object lock1 = new Object(); static Object lock2 = new Object(); @Override public void run() { System.out.println("当前锁标志:"+flag); if (flag == 0){ synchronized (lock1){ try { System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock1); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("线程"+Thread.currentThread().getName()+"等待获得锁"+lock2); synchronized (lock2){ System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock2); } } if (flag == 1){ synchronized (lock2){ try { System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock2); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("线程"+Thread.currentThread().getName()+"等待获得锁"+lock1); synchronized (lock1){ System.out.println("线程"+Thread.currentThread().getName()+"已获得锁"+lock1); } } } }
package Test2; public class Test { public static void main(String[] args) { DeadLockTest test1 = new DeadLockTest(0); DeadLockTest test2 = new DeadLockTest(1); Thread thread1 = new Thread(test1); Thread thread2 = new Thread(test2); thread1.start(); thread2.start(); } }
补充
- 为什么启动线程用run()不用start() ?
run() 是一种方法
start() 使得线程由创建态到就绪状态
通过start()调用run()方法
当用start()开始一个线程后,线程就进入就绪状态,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行;但是这并不意味着线程就会立即运行,只有当cpu分配时间片时,这个线程获得时间片时,才开始执行run()方法;start()方法去调用run(),而run()方法则是需要去重写的,其包含的是线程的主体(真正的逻辑)。
Java 中实现真正的多线程是 start 中的 start0() 方法,run() 方法只是一个包含业务逻辑普通方法;start是启动多线程的唯一方式,其使得线程由创建态到就绪态,而这个线程是否被运行是由系统调度所决定的。