多线程
进程和线程的概念和区别
- 进程: 应用程序的执行实列,有独立的内存空间和系统资源
- 线程: CPU调度和分配的基本单位,进程中执行最小的单位,可完成一个独立的顺序控制流程.
多线程
- 一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程.
- 多线程交替占用CPU资源,而非真正的并行执行
- 好处:
-
- 充分利用CPU的资源.
- 简化编程模型.
- 带来良好的用户体验
-
主线程
- main() 即为主线程入口.
- 产生其他子线程的线程
- 必须最后完成执行,因为它执行各种关闭动作.
创建线程的两种方式:
- 继承Thread 类. 重写run方法.
- 实例化编写类对象, 调用 start()方法 开启线程.
/**
* 编写 MyThread类 继承 Thread类的方式创建线程.
* @author MateBook
*
*/
public class MyThread extends Thread{
// 重写run()方法
public void run() {
for(int i = 0 ; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class MyThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// start() 启动线程.
t1.start(); // 实现了多线程.交替执行.
t2.start(); // 交替执行.系统cpu随机分配时间段.从0开始依次执行到循环结束.
// t1.run();
// t2.run(); // 1. 只有主线程一条执行路径. 2.依次调用了2次run方法.
}
}
- 实现Runnable接口 重写run方法
- 实例化实现类, 实例化 Thread类,
- 将实现类作为 Thread构造的参数. 调用 strat() 方法 开启线程.
package com.thread;
/**
* 实现 Runnable接口的方式创建线程.
* @author MateBook
*
*/
public class MyRunnable implements Runnable {
public void run() {
for(int i = 0 ; i < 100 ; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
package com.thread;
/**
*
* @author MateBook
*
*
* 创建线程的2种方式 :
* 1. 编写一个类,继承 Thread类. 重写 run 方法.
* 在测试类里实例化编写的类,调用 strat()方法.启动线程.
*
* 2. 编写一个类,实现 Runnable接口, 重写 run 方法.
* 实例化编写类, 再实例化 Thread 对象. 将实现的编写类, 作为 Thread构造参数.
* 再调用 strat() 方法.启动线程.
*
*/
public class MyRunnableTest {
public static void main(String[] args) {
// 1. 创建线程对象
MyRunnable t1 = new MyRunnable();
Thread t = new Thread(t1); // 将 Runnable实现类的对象传入 Thread带参构造里.
Thread tt = new Thread(t1,"MyBaby");// 再Thread 构造里修改线程名.
// 2. 调用start() 方法启动线程.
tt.start();
}
}
比较两种创建线程的方法
- 继承 Thread 类
- 编写简单,可直接操作线程.
- 适用于单继承.
- 实现Runnable接口
- 避免单继承局限性
- 便于共享资源
线程的状态
线程的5种状态分为
- 新建状态
- 就绪状态
- 运行状态
- 堵塞状态
- 死亡状态
package com.threadDemo4;
public class MyRunnable implements Runnable{
public void run() {
System.out.println("线程A正在运行"); // 运行状态
try {
Thread.sleep(500);// 线程休眠sleep(毫秒数)
System.out.println("线程A休眠,处于阻塞状态!"); // 堵塞状态(休眠状态)
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("线程A被中断!"); // 死亡状态.
}
}
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable(),"A");
System.out.println("线程A处于新建状态"); // 新建状态
t.start();
System.out.println("线程A处于就绪状态"); // 就绪状态.
}
}
线程的优先级
- 线程的优先级由 1~10表示, 1最低. 默认优先级为 5.
- setPriority() 设置优先级 将静态常量作为参数. 如:
- Thread.Max_Priority (表示最大优先级) 10
- Thread.Min_Priority (表示最小优先级.) 1
package com.threadDemo5;
public class MyRunnable implements Runnable{
// 实现接口 重写 run方法.
public void run() {
for(int i = 0 ; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
package com.threadDemo5;
/**
* 设置线程的优先级.
* @author MateBook
*
*/
public class MyRannableTest {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable(),"BabyA");
Thread t2 = new Thread(new MyRunnable(),"BabyB");
t1.setPriority(Thread.MAX_PRIORITY); // 设置线程优先级 10
t2.setPriority(Thread.MIN_PRIORITY); // 1
// 开启线程
t1.start();
t2.start();
}
}
线程休眠
- 静态方法: public static void sleep(long millis)
- millis 为休眠时长, 以毫秒为单位.
- 调用sleep() 方法需要处理 InterruptedException 异常
- 让线程暂时睡眠指定时长,线程进入阻塞状态.
- 睡眠时间过后线程会在进入可运行状态.
package com.threadDemo6;
/**
*
* @author MateBook
* 实现休眠
*/
public class Wait {
// 编写一个静态方法
public static void bySec(long s) { // 参数为long类型.
for(int i = 0 ; i < s;i++) { // for循环遍历. 将传进来的参数作为 判断条件.
System.out.println(i+1+"秒"); // 每循环依次输出
try {
Thread.sleep(1000); // 设置休眠时间. 1000 毫秒 = 1秒. 捕获异常.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.threadDemo6;
/**
* 主线程休眠5秒.
* @author MateBook
*
*/
public class Test {
public static void main(String[] args) {
System.out.println("主线程开始休眠");
Wait.bySec(10); // 让主线程休眠10秒.
System.out.println("主线程休眠结束");
}
}
线程的强制运行
- 使当前线程暂停执行,等待其他线程执行结束后再继续执行本线程.
- 方法
- public final void join()
- public final void join(long mills)
- public final void join(long mills,int nanos).
- mills:以毫秒为单位的等待时长,
- nanos:要等待的附加纳秒时长.
- 需要处理 InterruptedException 异常.
package com.threadDemo7;
public class MyRunnable implements Runnable{
// 实现Runnable 接口, 重写 run方法.
public void run() {
for(int i = 0 ; i < 10 ; i++) {
try {
Thread.sleep(100); // 每循环依次休眠100毫秒.
} catch (InterruptedException e) {
e.printStackTrace();
}// 输出信息.
System.out.println(Thread.currentThread().getName()+"运行."+i);
}
}
}
package com.threadDemo7;
/**
* 测试 join()方法.
* @author MateBook
*
*/
public class ThreadJoinDemo {
public static void main(String[] args) {
System.out.println("线程强制执行");
// 1 创建线程对象.并命名.
Thread temp = new Thread(new MyRunnable(),"temp");
// 开启线程.
temp.start();
for(int i = 0 ; i < 20;i++) {
// 当主线程执行到 i = 5的时候.暂停主线程, 让子线程 temp执行完毕之后 主线程再执行.
if(i==5) {
try {
temp.join();// 强制执行. 执行结束后 主线程再接着执行.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(100); // 每循环依次休眠100毫秒.
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出提示.
System.out.println(Thread.currentThread().getName()+"运行,"+i);
}
}
}
线程的礼让
- 暂停当前线程,允许其他具有相同优先级的线程获得运行机会.
- 该线程处于就绪状态,不转为阻塞状态.
- 方法名:
- public static void yield()
- 注意: 只是提供一种可能,但是不能保证一定会实现礼让.
package com.threadDemo8;
public class MyRunnable implements Runnable{
public void run() {
for(int i = 0 ; i < 5 ; i++) {
System.out.println(Thread.currentThread().getName()+"运行:"+i);
// 当i == 3 时, 线程礼让. 当前线程将cpu资源让出.
if(i==3) {
Thread.yield();// 线程礼让. 静态方法可以类名.方法直接调.
System.out.print("线程礼让:");
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.threadDemo8;
/**
* 测试线程礼让.
* @author MateBook
*
*/
public class Test {
public static void main(String[] args) {
// 创建2个线程.
Thread t1 = new Thread(new MyRunnable(),"线程A");
Thread t2 = new Thread(new MyRunnable(),"线程B");
// 开启2个线程.
t1.start();
t2.start();
}
}
线程同步方法和代码块
-
当两个或多个线程要同时访问同一资源时,需要以某种顺序来确保该资源在某一时刻只能被一个线程使用的方式称为线程同步.
-
采用同步来控制线程的执行由两种方式,即同步方法和同步代码块.这两种方法都是用 synchronized 关键字实现.
-
关键字 synchronized
- 应用在修饰符前或后都可以
- 使用 synchronized 定义一个静态方法, 代码如下.
package com.sale1; /** * * @author MateBook * * 模拟网站 使用同步方法. * * */ public class Site implements Runnable{ private int count = 10; // 记录剩余票数. private int num = 0; // 记录当前抢到第几张票. private boolean flag = false; // 记录票是否售完. public void run() { // 循环 , 当剩余票数为0时,循环结束. while(!flag) { sale(); } } // 同步方法:售票 public synchronized void sale() { if(count<=0) { flag = true; return; } // 修改票数. count--; num++; try { // 设置休眠. Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 输出抢到的第几场票.和剩余的票数. System.out.println(Thread.currentThread().getName()+"抢到第"+num+"张票,剩余:"+count+"张票"); } }- 使用 synchronized 代码块方法如下.
package com.sale2;
/**
*
* @author MateBook
*
* 模拟网站 使用同步代码块.
*
*
*/
public class Site implements Runnable {
private int count = 10; // 记录剩余票数.
private int num = 0; // 记录当前抢到第几张票.
public void run() {
// 循环 , 当剩余票数为0时,循环结束.
while (true) {
synchronized (this) { // synchronized (this) 通常为this 需要同步的对象. 效果与同步方法相同.
if (count <= 0) {
break;
}
// 修改票数.
count--;
num++;
try {
// 设置休眠.
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出抢到的第几场票.和剩余的票数.
System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余:" + count + "张票");
}
}
}
}
- 多个并发线程访问同一资源的同步代码时. (同步方法亦是如此.)
- 同一时刻只能有一个线程进入 synchronized(this) 同步代码块
- 当一个线程访问一个 synchronized(this)同步代码块时, 其他 synchronized(this)同步代码块同样被锁定.
- 当一个线程访问一个 synchronized(this) 同步代码块时,其他线程可以访问该资源的非 synchronized(this) 同步代码块.
线程安全的类型
| 方法是否同步 | 效率比较 | 适合场景 | |
|---|---|---|---|
| 线程安全 | 是 | 低 | 多线程并发共享资源 |
| 非线程安全 | 否 | 高 | 单线程 |
- ArrayList 非线程安全.
- Hashtable 线程安全, 效率低 键和值不允许为 null.
- HashMap 非线程安全. 效率高. 键和值都允许为 null
- StringBuffer 线程安全. 效率低
- StringBuilder 非线程安全. 效率高.
死锁
什么是死锁.
- 两个线程都在等待对方先完成,造成程序停滞
- 死锁的条件
- 两个或两个以上的线程在活动.
- 某个线程拿到一个锁以后,还想拿到第二个锁,造成锁的嵌套.
- 如何避免死锁.
- 当前线程先释放自己的锁. 线程之间就能解锁.
- 尽量减少同步方法.同步代码块的嵌套.
线程池
-
线程池所在的包是 java.util.concurrent
-
顶级接口是 Executor, 真正的线程池接口是 ExecutorService
-
java.util.concurrent.Executors 类提供创建线程池的方法.
-
不使用线程池
- 线程缺乏统一管理,占用过多系统资源
- 缺乏更多功能,如定期执行,定时执行等.
-
使用线程池的好处.
- 重用存在的线程,减少对象的创建,消亡和开销
- 有效控制最大的并发数,提高系统资源使用率.
- 定时执行,定期执行.
| 方法名 | 说明 |
|---|---|
| newCachedThreadPool() | 创建一个可缓存的线程池,有任务时才创建新任务 |
| newSingleThreadExecutor() | 创建一个单线程池 |
| newFixedThreadPool(int nThreads) | 创建一个固定长度的线程池,空闲线程会一直保留,参数nThreads设定线程池中的线程的数目 |
| newScheduledThreadPool(int corePoolSize) | 创建一个谷固定长度的线程池,而且以延迟或定时的方式来执行任务. |
ThreadPoolExecutor 类
构造器中各个参数的含义.
- corePoolSize : 核心池的大小.
- maximumPoolSize: 线程池最大线程数.
- keepAliveTime: 表示线程没有执行任务时最多保持多久时间会终止.
- unit: 参数 keepAliveTime 的时间单位.
- workQueue: 一个阻塞队列. 用来存储等待执行的任务.
- handler: 表示当拒绝处理任务时的策略.
10万+

被折叠的 条评论
为什么被折叠?



