JAVA多线程笔记总结
线程简介
进程与线程
| 进程(process) | 线程(Thread) |
| —————————————————————————————— | —————————————————————————————— |
| 进程是程序的一次执行,是系统进行资源分配和调度的一个独立单位 | 线程作为资源调度的基本单位,是程序的执行单元,执行路径 |
线程的3个基本状态
执行
就绪
阻塞
线程的5种基本操作
创建
可运行
阻塞
调度
被终止
线程的两个基本类型
用户级线程 :管理过程全部由用户程序完成,操作系统内核心只对进程进行管理。
系统级线程(核心级线程):由操作系统内核进行管理。
何为多任务?
生活中有很多例子,如:在开车时打电话吃零食;即一个对象同时进行多项任务。
何为多线程?
在打游戏时,游戏中出现的视频、音频、字幕、震动反馈等都是一个个的线程,在游戏这个应用程序下分支为多个线程,而游戏就是一个进程!
Java实现多线程
- 方法一:通过继承Thread类调用线程
继承Thread类
public class MyThread extends Thread { //线程体 @Override//重写run方法 public void run() { for (int x = 0; x < 200; x++) { System.out.println(x); } } }
start()方法启动线程
public static void main(String[] args) { //main线程 //TextThread01该代码的类 //创建一个线程对象 TextThread01 textThread01 = new TextThread01(); //调用start()方法启动线程 textThread01.start(); for (int i = 0; i < 2000; i++) { System.out.println("打游戏 "+i); } }
- 方法二:实现Runnable接口
实现Runnable接口
public class TextThread03 implements Runnable { @Override public void run() { //run方法线程体 for (int i = 0; i < 200; i++) { System.out.println("正在敲代码---"+i); } }
在主线程下创建Runnable接口实现类对象 ,并使用new Thread(对象名).start();启动线程
public static void main(String[] args) { //创建Runnable接口实现类对象 TextThread03 thread03 = new TextThread03(); //启动方式一: // Thread thread = new Thread(); // // thread.start(); //启动方式二: new Thread(thread03).start(); }
- 方式三:实现Callable接口,需要返回值类型
实现callable接口,重写call方法
public class Text_Callable implements Callable<Boolean> {//此处的返回值类型为布尔类型 private String url;//网络图片地址 private String name; public Text_Callable(String url, String name) { this.url = url; this.name = name; } @Override public Boolean call() { WebDownloader webDownload = new WebDownloader(); webDownload.downloader(url, name); System.out.println("下载了文件名为" + name + "的文件"); return true; }
创建线程对象(在main方法中)
Text_Callable t1 = new Text_Callable("https://pic2.zhimg.com/80/v2-517bd06e7b6fdaec9b9a0b3df525fc29_1440w.jpg", "1.jpg"); Text_Callable t2 = new Text_Callable("https://pic3.zhimg.com/80/v2-c9b19e6c66d08934922ed266d71fca5d_720w.jpg", "2.jpg"); Text_Callable t3 = new Text_Callable("https://pic4.zhimg.com/80/v2-c5eeefcb0c6c9f86553ace622a7e8329_720w.jpg", "3.jpg");
创建执行服务:创建线程池
ExecutorService ser = Executors.newFixedThreadPool(3);
配合future接口执行服务
Future<Boolean> r1 = ser.submit(t1); Future<Boolean> r2 = ser.submit(t2); Future<Boolean> r3 = ser.submit(t3);
获取结果
boolean rs1 = r1.get(); boolean rs2 = r2.get(); boolean rs3 = r3.get();
6.关闭服务
ser.shutdownNow();
并发与并行
- 并行:
* 并行性是指**同一时刻**发生两个或多个事件 * 并行是在不同的实体上的多个事件
- 并发:
* 并发性是指**同一时间间隔内**发生两个或多个事件 * 并发是**同一实体**上的多个事件
Thread类源码分析
设置线程名
Thread.currentThread().getName()
守护线程
thread.setDaemon(true);//默认为false,表示用户线程 thread.start();//启动守护线程 new Thread(you).start();//启动用户线程
优先级线程
调用Thread.currentThread().getPriority()获得优先级线程的级别(int)
创建MyPriority实现runnable接口
class MyPriority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority()); } }
new一个MyPriority对象,创建线程
MyPriority myPriority = new MyPriority(); Thread t2 = new Thread(myPriority);
先设置优先级再启动线程
t2.setPriority(1);//线程优先级范围1-10 t2.start();
线程生命周期
线程休眠(Thread.sleep())
调用sleep方法会进入计时等待状态,等时间到了,进入的是就绪状态而并非是运行状态!
//模拟延时 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
线程礼让(Thread.yield())
让当前执行的线程执行但不阻塞,若让CPU重新调度,不一定会礼让
此时线程由运行态变为就绪态
public class TextYield { public static void main(String[] args) { MyYield myYield = new MyYield(); new Thread(myYield,"a").start();//线程a new Thread(myYield,"b").start();//线程b } } class MyYield implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程开始执行"); Thread.yield();//线程礼让 System.out.println(Thread.currentThread().getName()+"线程停止执行"); } }
线程霸占(Thread.join())
- java合并线程(即插队),其他线程阻塞,调用join方法,会等待该线程执行完毕后再执行别的线程
public class TextJoin implements Runnable { @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("线程vip来了"+i); } } //vip线程霸占主线程 public static void main(String[] args) throws InterruptedException{ //main线程 TextJoin textJoin = new TextJoin(); Thread thread = new Thread(textJoin); thread.start(); for (int i = 0; i < 200; i++) { if(i == 100){ thread.join();//插队 } System.out.println("main"+i); } } }
interrupt方法
我们一般使用的是interrupt来请求终止线程~
- 要注意的是:interrupt不会真正停止一个线程,它仅仅是给这个线程发了一个信号告诉它,它应该要结束了(明白这一点非常重要!)
- 也就是说:Java设计者实际上是想线程自己来终止,通过上面的信号,就可以判断处理什么业务了。
- 具体到底中断还是继续运行,应该由被通知的线程自己处理
Thread t1 = new Thread( new Runnable(){ public void run(){ // 若未发生中断,就正常执行任务 while(!Thread.currentThread.isInterrupted()){ // 正常任务代码…… } // 中断的处理代码…… doSomething(); } } ).start();
线程同步
同步方法
public synchronized void method(int args){}
synchronized
方法都必须获得该方法的对象的锁才能执行,否则线程会出现阻塞- 方法一旦执行就会独占该锁,指导该方法返回才释放锁
同步块
synchronized (obj){}
- obj称为同步监视器
- obj可以是任何对象但推荐使用共享资源作为同步监视器
死锁(DeadLock)
造成死锁的原因可以概括成三句话:
- 当前线程拥有其他线程需要的资源
- 当前线程等待其他线程已拥有的资源
- 都不放弃自己拥有的资源
1.1 锁顺序死锁
Makeup(int choice,String girlname){
this.choice=choice;
this.girlname=girlname;
}
@Override
public void run() {
//化妆
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆:互相持有对方的锁,就是需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice == 0){
synchronized (lipstick){
System.out.println(this.girlname+"获得口红的锁");
Thread.sleep(1000);
synchronized (mirror){
System.out.println(this.girlname+"获得镜子的锁");
}
}
}else {
synchronized (mirror){
System.out.println(this.girlname+"获得镜子的锁");
Thread.sleep(2000);
synchronized (lipstick){
System.out.println(this.girlname+"获得口红的锁");
}
}
}
}
1.2 避免死锁的方法
避免死锁可以概括成三种方法:
- 固定加锁的顺序(针对锁顺序死锁)
- 开放调用(针对对象之间协作造成的死锁)
- 使用定时锁—>
tryLock()
- 如果等待获取锁时间超时,则抛出异常而不是一直等待!
1.3线程池
线程池可以看做是线程的集合。在没有任务时线程处于空闲状态,当请求到来:线程池给这个请求分配一个空闲的线程,任务完成后回到线程池中等待下次任务(而不是销毁)。这样就实现了线程的重用。