![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiNx8FesU2cfdGLwczX0xiRGZkRGZ0Xy9GbvNGLwIzXlpXazxCcH9WNNd0S2kUbaZTQClGVF5UMR9Fd4VGdsATNfd3bkFGazxSUhxGatJGbwhFT1Y0Mk9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5gTN0YzM5kjYjljMhFWZ4gjNxQzMjRDM5gjZkRWZxIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
程序、进程、线程
- 在操作系统中运行的程序就是进程,一个进程里面可以有多个线程;
- 程序 是指令和数据的有序集合其本身没有任何运行的含义,是一个静态的概念;
- 进程 则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位;
线程 是CPU调度和执行的单位;
注意 :大多数线程都是模拟出来的,真正的多线程是指有多个CPU,即多核,如果是模拟出来的多线程,在同一时间点,CPU只能执行一个代码,因为切换的快,才有了同时执行的错觉。
- 线程就是的独立执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程、gc线程
- main() 称为主线程,因其是系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
- 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制;
- 线程会带来额外的开销,入CPU调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
注意
java默认有几个线程?
2个,一个main,一个GC
java真的可以开启线程吗?
不行,java无法直接操作硬件,是利用底层c++,
private native void start0();
线程的创建
线程的创建的3种方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
继承Thread类
步骤:
- 自定义线程类继承Thread类
- 重写**run()**方法,编写线程执行体
创建线程对象,调用**start()**方法启动线程
线程不一定立即执行,等待CPU调度
//todo 继承Thread类
public class Test1 extends Thread{
public static void main(String[] args) throws InterruptedException {
//todo 创建线程对象
Test1 test1 = new Test1();
//todo 调用start对象
test1.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程"+i);
sleep(1);
}
}
@Override
public void run() {
//super.run();
//todo 重写run方法
for (int i = 0; i < 100; i++) {
System.out.println("重写的run方法"+i);
try {
sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
实现Runnable接口
- 自定义线程类实现Runnable接口
- 实现**run()**方法,编写线程执行体
创建线程对象,调用**start()**方法启动线程
推荐使用 Runnable,因为java单继承的局限性
import static java.lang.Thread.sleep;
//todo 实现Runnable接口
public class Test1 implements Runnable{
public static void main(String[] args) throws InterruptedException {
//todo 创建 Runnable接口实现类对象
Test1 test1 = new Test1();
//todo 创建线程对象,通过线程对象来开启我们的线程
new Thread(test1).start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程"+i);
sleep(1);
}
}
@Override
public void run() {
//super.run();
//todo 重写run方法
for (int i = 0; i < 100; i++) {
System.out.println("重写的run方法"+i);
try {
sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
例子(抢票):
//todo 实现Runnable接口
public class Test1 implements Runnable{
public static int tickeNums=10;
public static void main(String[] args) throws InterruptedException {
//创建实现Runnable接口实现类
Test1 test1 = new Test1();
new Thread(test1,"t1").start();
new Thread(test1,"t2").start();
new Thread(test1,"t3").start();
}
@Override
public void run() {
while(true){
if(tickeNums==0){
break;
}
//Thread.currentThread().getName()获取调用这个线程的对象名
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+tickeNums--+"票;");
}
}
}
上述操作带来的问题:发现有数据重复,这就是多个操作针对同一资源所带来得数据混乱即线程不安全;
实现Callable接口
实现Callable接口,需要返回值类型
implements Callable< T>
- 重写call方法(),需要抛出异常
- 创建目标对象
- 创建执行服务:
ExecutorService ser=Executors.newFixedThreadPool(1);
- 提交执行:
Future<Boolean> result1=ser.submit(t1);
- 获取结果:
boolean r1=result1.get()
- 关闭服务:
ser.shutdownNow();
import java.util.concurrent.*;
//todo 实现Callable接口
public class Test1 implements Callable<Boolean> {
public static int tickeNums=10;
public static void main(String[] args) throws ExecutionException, InterruptedException {
Test1 test1 = new Test1();
Test1 test2 = new Test1();
Test1 test3 = new Test1();
//创建执行服务
ExecutorService executorService = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> submit1 = executorService.submit(test1);
Future<Boolean> submit2 = executorService.submit(test2);
Future<Boolean> submit3 = executorService.submit(test3);
//获取结果
Boolean aBoolean1 = submit1.get();
Boolean aBoolean2 = submit2.get();
Boolean aBoolean3 = submit3.get();
System.out.println(aBoolean1+":"+aBoolean2+":"+aBoolean1);
//关闭服务
executorService.shutdownNow();
}
@Override
public Boolean call() {
while(true){
if(tickeNums==0){
break;
}
//Thread.currentThread().getName()获取调用这个线程的对象名
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+tickeNums--+"票;");
}
return true;
}
}
package com.cx;
import java.util.Calendar;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class Test11 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//new Thread(new Runnable()).start();Thread不能直接使用callable
//new Thread(new FutureTask<>()).start();Thread可以放FutureTask
//new Thread(new FutureTask<>(Callable)).start();//FutureTask可以放Callable
Mythread mythread = new Mythread();
FutureTask futureTask = new FutureTask(mythread);//适配类
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();//会打印出一个Acall,因为有缓存的原因
Object o = futureTask.get();//获取Callable的返回值,此方法可能会产生阻塞,解决此问题一般是将其放在最后,或者是使用异步通信
System.out.println(o);
}
}
//返回值自定义,重写方法也需一样
class Mythread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName()+"call()");
return "ok";
}
}
线程状态
线程可以处于以下状态之一:
- new :尚未启动的线程处于此状态
- runnable:在java虚拟机中执行的线程处于此状态
- blocked:被阻塞等待监视器锁定的线程处于此状态
- waiting:正在等待另一个线程执行特定动作的线程处于此状态
- timed_waiting:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
- terminated:已退出的线程处于此状态
public class Test1 implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Test1());
//观察线程状态 new
Thread.State state = thread.getState();
System.out.println(state);
//观察线程状态 runnable
thread.start();
state = thread.getState();
System.out.println(state);
while (state!=Thread.State.TERMINATED) {
Thread.sleep(100);
state = thread.getState();
System.out.println(state);
}
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行");
}
}
}
常用方法
Thread thread = new Thread();
//更改线程优先级
thread.setPriority(10);
//类方法,在指定的毫秒数内让当前正在执行的线程体休眠
Thread.sleep(10);
//等待该线程终止
thread.join();
//类方法,暂停当前正在执行的线程对象,并执行其他线程
Thread.yield();
//中断线程,不建议使用
thread.interrupt();
//测试线程是否处于活动状态
boolean alive = thread.isAlive();
停止线程flag
- 不推荐使用JDK提供的stop()、destory()方法
- 推荐线程自己停止
- 建议使用一个标志位进行终止变量
线程休眠sleep
- sleep 指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep 可以模拟网络延迟、倒计时等
- 每一个对象都有一个锁,sleep不会释放锁;
礼让线程yield
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态(重新竞争)
- 让cpu重新调度,礼让不一定成功,主要看cpu
线程强制执行join
- join合并线程,等待此线程执行完成后,再执行其他线程,其他线程阻塞
public class Test1 implements Runnable {
public static void main(String[] args) throws InterruptedException {
Test1 test1 = new Test1();
Thread thread = new Thread(test1);
thread.start();
for (int i = 0; i < 1000; i++) {
if (i==500){
thread.join();
}
System.out.println("main执行"+i);
}
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("vip插队"+i);
}
}
}
线程优先级
- java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程优先级用数字表示,范围从1~10.
1. Thread.MIN_PRIORITY=1
2.Thread.MAX_PRIORITY=10
3.Thread.NORW_PRIORITY=5
使用getPriority()来获取优先级,使用setPriority(int x)来设置优先级
注意:优先级只是意味获得调度的概率低,并不是优先级低就不会被调用,关键在于CPU的调度。
public class Test1{
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
Test2 test2 = new Test2();
Thread thread1 = new Thread(test2);
Thread thread2 = new Thread(test2);
Thread thread3 = new Thread(test2);
Thread thread4 = new Thread(test2);
//设置线程优先级,要先设置再启动
thread1.start();
thread2.setPriority(5);
thread2.start();
thread3.setPriority(7);
thread3.start();
thread4.setPriority(3);
thread4.start();
}
}
class Test2 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
守护(daemon)线程
- 线程分为** 用户线程** 和守护线程
- 虚拟机必须保证用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 例如:后台日志、监控内存等
public class Test1{
public static void main(String[] args) {
Test2 test2 = new Test2();
Test3 test3 = new Test3();
Thread thread1 = new Thread(test3);
//默认所有的线程都是用户线程
thread1.setDaemon(true);
thread1.start();
Thread thread = new Thread(test2);
thread.start();
}
}
class Test2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("用户线程");
}
System.out.println("用户线程结束");
}
}
class Test3 implements Runnable {
@Override
public void run() {
while(true){
System.out.println("守护线程");
}
}
}
线程同步
由于同一进程的多个线程共享同一块存储空间,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个 线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,但是会存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
同步方法
public synchronized void method(int args){}
synchronized方法控制“对象”的访问,每一个对象对应一把锁,每一个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程就会阻塞,方法一旦执行,就独占该锁,直到返回才能释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺点:若将一个大的方法声明为sychronized将会影响效率
public class Test1{
public static void main(String[] args) {
Test2 test2 = new Test2();
new Thread(test2,"你").start();
new Thread(test2,"我").start();
new Thread(test2,"他").start();
}
}
class Test2 implements Runnable {
private int num=1000;
boolean flag=true;
@Override
public void run() {
while (flag){
try {
num_();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//加上synchronized
public synchronized void num_() throws InterruptedException {
if (num<=0){
flag=false;
return;
}
//检查并发性
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"获得了数字"+num--);
}
}
同步块
sychronized(obj){}
- obj称为同步监视器
- obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
public class Test1{
public static void main(String[] args) {
Test3 test3 = new Test3(1000);
Test2 test2 = new Test2(test3);
Thread you = new Thread(test2, "你");
Thread me = new Thread(test2, "我");
you.start();
me.start();
}
}
class Test2 implements Runnable {
private Test3 test3;
boolean flag=true;
public Test2(Test3 test3) {
this.test3 = test3;
}
@Override
public void run() {
while (flag){
try {
//synchronized默认锁的是this,现在用块锁住test3对象
synchronized (test3){
num_();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void num_() throws InterruptedException {
if (test3.getNum()<=0){
flag=false;
return;
}
//检查并发性
Thread.sleep(100);
test3.setNum(test3.getNum()-1);
System.out.println(Thread.currentThread().getName()+"获得了数字"+test3.getNum( ));
}
}
//存放数字100
class Test3{
private int num;
public Test3(int num) {
this.num = num;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
CopyOnWriteArrayList(JUC集合)
import java.util.concurrent.CopyOnWriteArrayList;
public class Test1{
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<String> strings = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100; i++) {
new Thread(()->{
strings.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(100);
System.out.println(strings.size());
}
}
死锁
产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源儿阻塞时,对方已获得的资源保持不变
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系
LOCK(锁)
- 从jdk5.0开始,java提供了更加强大的线程同步机制-通过显示定义同步锁对象来实现同步,同步锁使用Lock对象来充当
- java.util.concurrent.locks.Lock接口是控制多个线程共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentranLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁
lock三部曲
- new ReentrantLock();
- lock,lock()//加锁
- lock.unlock()//解锁
import java.util.concurrent.locks.ReentrantLock;
public class Test1 implements Runnable{
int num=10;
//定义lock
private final ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Test1 test1 = new Test1();
Thread thread1 = new Thread(test1);
Thread thread2 = new Thread(test1);
Thread thread3 = new Thread(test1);
thread1.start();
thread2.start();
thread3.start();
}
@Override
public void run() {
while(true){
try {
lock.lock();//加锁
//将会锁的对象放入try中
if(num==0){
break;
}else {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+num--);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
}
}
synchronized与lock的对比
- Lock是显示锁(手动开启和关闭),synchronized是隐式锁,出了作用于自动释放
- synchronized是java内置关键字,Lock是一个java类
- synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
- lock只有代码块锁,synchronized有代码锁和方法锁
- 使用lock锁,jvm将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性
- synchronized 会一直等待下去,lock则不会
- synchronized 可重入锁,不可以中断,非公平的;Lock 是可重入锁,可以判断锁,非公平的(但是可以自己设置)
- synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码!
- 优先使用顺序:Lock->同步代码块->同步方法
线程通信
注意:均是object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException
sleep与wait区别
- wait会释放锁,sleep不会释放锁
- wait来自object类,sleep来自thread类
- wait只能在同步代码块中使用,sleep可以在任何地方使用
- sleep需要捕获异常,wait不需要捕获异常
管程法(生产者-消费者)
- 生产者:负责生产数据的模块
- 消费者:负责处理数据的模块
缓冲区:消费者不能直接使用生产者的数据,他们之间有一个缓冲区
,生产者将生产的数据放入缓冲区,消费者重缓冲区拿数据
public class TestPC {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
Productor productor = new Productor(synContainer);
Consumer consumer = new Consumer(synContainer);
productor.start();
consumer.start();
}
}
//todo 生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container=container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产:"+i);
try {
container.push(new Thing(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//todo 消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container=container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
System.out.println("消费:"+container.pop().id);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//todo 产品
class Thing{
int id;//编号
public Thing(int id) {
this.id = id;
}
}
//todo 缓冲区
class SynContainer{
//放产品的容器
Thing[] things=new Thing[10];
//容器计数器
int count=0;
//生产者放入产品
public synchronized void push(Thing thing) throws InterruptedException {
//如果容器满了,等待消费者生产
if(count==things.length){
//通知消费者消费,生产等待
this.wait();
}
//如果没有满,继续生产产品
things[count]=thing;
count++;
//通知消费者消费
this.notifyAll();
}
public synchronized Thing pop() throws InterruptedException {
Thing thing = null;
//判断是否可以消费
if(count==0){
//等待生产者生产,消费者等待
this.wait();
}
//如果可以消费
count--;
thing=things[count];
//通知生产者生产
this.notifyAll();
return thing;
}
}
信号灯法(标志位)
线程池
- 背景:经常创建和销毁、使用量特别大的资源
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回线程池中
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程)
JDK5.0起提供了线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
1. void Execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
2. < T >Future< T > submit(Callable< T > task):执行任务,有返回值,一般用来执行Callable
3. void shutdown():关闭连接池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPC {
public static void main(String[] args) {
//创建线程池 newFixedThreadPool参数为线程池的大小
ExecutorService executorService = Executors.newFixedThreadPool(10);
//执行
executorService.execute(new Productor());
executorService.execute(new Productor());
executorService.execute(new Productor());
//关闭连接
executorService.shutdown();
}
}
class Productor implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
推荐学习狂神说