多线程:
多线程是指在程序中包含多个执行流,也就是说在一个程序中同时运行多个不同的线程来执行不同的任务
线程与进程:
进程:系统资源分配的基本单位。一个在内存中运行的应用程序
线程:cpu调度和执行的单位。进程中的一个执行任务
同一进程下的线程共享进程的地址空间和资源
每个进程之间的地址空间和资源都是相互独立的
一个进程崩溃,在保护模式下不会对其他线程造成影响
一个线程崩溃,整个进程死掉
并发编程的三要素:原子性、可见性、有序性
用户线程与守护线程:
用户线程:运行在前台,执行具体任务。
守护线程:运行在后台,为用户线程服务。
main函数就是一个用户线程,main函数在启动的同时,JVM还启动了好多守护线程,例如垃圾回收线程。
当用户线程结束,JVM退出,不考虑有没有守护线程的存在。
package com.zql.demo02;
public class TestDeamon {
public static void main(String[] args) {
//创建守护线程
God god = new God();
Thread thread = new Thread(god);
//设置为守护线程
thread.setDaemon(true);
thread.start();
//创建用户线程
You you = new You();
new Thread(you).start();
}
}
//守护线程
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝守护你");
}
}
}
//用户线程
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("活了" + i + "年");
}
}
}
线程死锁:
死锁是指两个或者两个以上的进程(线程)在执行过程中,由于竞争资源或者是通信造成的阻塞现象。
造成死锁的四个必要条件:
1.互斥条件:线程(进程)对于分配到的资源具有排他性,一个资源只可以被一个线程(进程)占用
2.请求与保持条件:当一个线程(进程)因为请求的资源被占用而发生阻塞,对于已经获得的资源保持不放
3.不剥夺条件:线程(进程)已经获得的资源在未使用完成之前不能被其他线程强行剥夺
4.循环等待条件:发生死锁时,所等待的线程(进程)会形成一个环路,类似于死循环,造成永久堵塞
如何避免死锁:
破坏四个必要条件中任意一个即可
创建线程:
1.继承Thread类
1.创建Thread子类实现Thread类
2.重写run方法
3.创建线程对象,开启线程
package com.zql.demo02;
public class TestThread extends Thread {
@Override
public void run() {
System.out.println("run正在执行");
}
public static void main(String[] args) {
TestThread testThread = new TestThread();
testThread.start();
System.out.println("main正在执行");
}
}
2.实现Runnable接口
1.定义类实现Runnable接口,重写run方法
2.创建线程对象
3.丢入线程对象,开启线程
package com.zql.demo02;
public class TestRunnable implements Runnable {
@Override
public void run() {
System.out.println("run正在执行");
}
public static void main(String[] args) {
TestRunnable testRunnable = new TestRunnable();
new Thread(testRunnable).start();
System.out.println("main正在执行");
}
}
3.实现Callable接口
1.创建实现类实现Callable接口,定义返回值类型
2.new FutureTask创建线程对象
3.丢入线程对象,调用start()开启线程
package com.zql.demo02;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call执行");
return 1;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<Integer> integerFutureTask = new FutureTask<>(new TestCallable());
new Thread(integerFutureTask).start();
Thread.sleep(1000);
System.out.println("获得返回值:" + integerFutureTask.get());
System.out.println("main执行");
}
}
Runnable与Callable区别:
相同点:
1.都是接口
2.都可以编写多线程程序
3.都采用start()启动
不同点:
1.Runnable接口run方法没有返回值,Callable接口call方法有返回值,是个泛型
2.Runnable接口run方法只能抛出运行时异常,无法捕获;Callable接口call方法可以抛出异常
Callable接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主程序运行
run()与start()有什么区别:
start()是用来启动线程的,run()只是一个函数
start()只可以调用一次,run()可以调用无数次
调用start()启动线程,真正实现了多线程运行。
直接调用run(),相当于调用一个普通函数
调用 start() 方法时会执行 run() 方法,为什么不直接调用 run() 方法?
new一个Thread,线程处于新建状态。调用start()方法,启动线程并且使该线程进入就绪状态,当cpu分配时间片就可以运行。自动调用run()。
直接调用run(),就是将run()作为一个普通方法执行,不是多线程。
调用start()会使线程进入就绪状态,调用run()只是thread中的一个普通方法。
常用方法:
1.通过外部标识位让线程停止 .stop()
package com.zql.demo02;
public class TestStop implements Runnable {
//1.设置一个外部标识位
private boolean flag = true;
@Override
public void run() {
int j = 0;
System.out.println("run执行---" + j++);
}
//2.设置一个停止方法
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
//3.创建线程对象
TestStop testStop = new TestStop();
//4.开启线程
new Thread(testStop).start();
//5.在规定下停止线程
for (int i = 0; i < 50; i++) {
System.out.println("main执行---" + i);
if (i == 30){
testStop.stop();
System.out.println("线程停止");
}
}
}
}
2.线程休眠 模拟倒计时
package com.zql.demo02;
public class TestSleep {
public static void main(String[] args) {
try {
tenDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num <= 0){
break;
}
}
}
}
3.线程礼让 yield()
礼让不一定成功,看CPU调度
package com.zql.demo02;
public class TestYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---线程开始");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "---线程开始");
}
public static void main(String[] args) {
TestYield testYield = new TestYield();
new Thread(testYield,"thread1").start();
new Thread(testYield,"thread2").start();
}
}
4.强制执行 join()
package com.zql.demo02;
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("run线程---" + i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 100; i++) {
if (i == 30){
thread.join();
}
System.out.println("main线程---" + i);
}
}
}
线程池:
线程池就是事先创建若干个可执行的线程放入到一个容器中,需要的时候从容器中获取,使用完不需要销毁,从而减少创建和销毁对象的开销。
使用工具类Executors生成一些常用的线程池:
1.newSingleThreadExecutor 创建一个单线程的线程池
2.newFixedThreadPool 创建固定大小的线程池
3.newCachedThreadPool 创建一个可缓存的线程池
4.newScheduledThreadPool 创建一个大小无限的线程池
package com.zql.demo02;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
//创建线程池 参数为线程池大小
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new TestPool());
executorService.execute(new TestPool());
executorService.execute(new TestPool());
executorService.execute(new TestPool());
//关闭连接
executorService.shutdown();
}
}