多线程总结
什么是线程,进程,多线程
进程(Process):一个游戏,一个视频软件,一个QQ。进程是程序执行的过程
线程(Thread):游戏的画面,游戏的声音。线程是CPU调度和执行的单位
一个进程至少包含一个线程
线程
线程和进程的区别
- 地址空间:线程共享本进程的地址空间,而进程之间是独立的地址空间。
- 资源:线程共享本进程的资源如内存、I/O、cpu等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护。
- 健壮性:多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。
- 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口,执行开销大。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,执行开销小。
- 可并发性:两者均可并发执行。
- 切换时:进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。
- 其他:线程是处理器调度的基本单位,但是进程不是。
创建线程
Thread class-》继承Thread类 重点
Runable接口-》实现runable接口 重点
Callable接口-》实现Callable接口 了解
public class Thread01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("这是一个测试");
}
}
public static void main(String[] args) {
// 实例化包含多线程的类开启,使用start()方法开启线程
Thread01 thread01 = new Thread01();
thread01.start();
for (int i = 0; i < 500; i++) {
System.out.println("插入数值");
}
}
}
运行结果:
Runnable接口
runnable接口也可以实现多线程
runnable接口就是静态代理类
推荐使用runnable接口,避免单继承的局限性,方便同一个对象被多个线程使用
public class Thread03 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("case:"+i);
}
}
//runnable使用了静态代理
public static void main(String[] args) {
//创造线程
Thread03 thread03 = new Thread03();
//实例化线程体,把runnable目标导入
new Thread(thread03).start();
for (int i = 0; i < 100; i++) {
System.out.println("test:"+i);
}
}
}
并发
初识并发
/**
* 单个对象,多个线程
* 火车票模拟,问题:多线程,如果线程太多可能会导致高并发问题,不同的人抢到了同样的票
*/
public class Thread04 implements Runnable{
private int ticket = 10;
@Override
public void run() {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "->拿到了第" + ticket-- + "张票");
}
}
public static void main(String[] args) {
Thread04 thread04 = new Thread04();
new Thread(thread04,"小1").start();
new Thread(thread04,"小3").start();
new Thread(thread04,"小2").start();
}
}
龟兔赛跑
使用runnable接口进行一个对象多个线程比较谁先到终点
/**
* 多线程龟兔赛跑
*/
public class Race implements Runnable{
// 胜利者
private static String winner;
// 线程体
@Override
public void run() {
for (int i = 0; i <= 101; i++) {
//判断比赛是否结束
boolean flag = gameOver(i);
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
}
}
private boolean gameOver(int steps){
if (winner!=null){
return true;
}else {
if (steps>=101){
winner = Thread.currentThread().getName();
System.out.println("winner is "+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
线程状态
线程停止
线程停止不建议使用JDK提供的过时的stop(),destroy()
停止线程,自己创建一个标识符用来 停止线程更加的安全
标识符 flag=false,表示线程停止运行
//线程停止运行,标识符
public class ThreadStop implements Runnable{
//设置标识位
private Boolean flag = true;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("线程停止"+i++);
}
}
public static void main(String[] args) {
ThreadStop threadStop = new ThreadStop();
new Thread(threadStop).start();
for (int i = 0; i < 100; i++) {
if (i>50){
threadStop.isStop();
}
System.out.println(i);
}
}
//更改标识位
public void isStop() {
this.flag=false;
}
}
线程延迟sleep
线程延迟,可以使用Thread.sleep来进行线程延迟设定
线程需要抛出异常InterruptedException
sleep到了后进入就绪状态
sleep可以用来模拟网络延迟,倒计时等
每个对象都有一个锁,sleep不会释放锁
import java.text.SimpleDateFormat;
import java.util.Date;
//模拟实时获取当前时间
public class ThreadSleep01 {
public static void main(String[] args) {
//获取当前时间
Date date = new Date(System.currentTimeMillis());
while (true){
try {
//线程延迟
Thread.sleep(1000);
//更新时间
date = new Date(System.currentTimeMillis());
//格式化时间
System.out.println(new SimpleDateFormat("yyyy:HH:dd HH:mm:ss").format(date));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程礼让yield
优点:
-
礼让线程,让当前正在执行的线程暂停,但不阻塞
-
将线程从运行状态转为就绪状态
-
让cpu重新调度,礼让不一定成功,看cpu心情
//线程礼让,礼让不一定成功
public class ThreadYield implements Runnable{
public static void main(String[] args) {
ThreadYield threadYield = new ThreadYield();
new Thread(threadYield,"小白").start();
new Thread(threadYield,"小明").start();
new Thread(threadYield,"小黑").start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行线程");
Thread.yield(); //开启礼让
System.out.println(Thread.currentThread().getName()+"关闭线程");
}
}
礼让成功图:
礼让失败图:
线程插队join
join不建议使用,会造成线程阻塞
多线程同步执行,当使用Thread.join()时将会先将Thread.run()执行完毕才会执行其他线程
//join插队解析
public class ThreadJoin implements Runnable{
@Override
public void run() {
//run线程
for (int i = 0; i < 200; i++) {
System.out.println("插队的人->>>>>"+i);
}
}
public static void main(String[] args) {
ThreadJoin threadJoin = new ThreadJoin();
//静态代理导入对象
Thread thread = new Thread(threadJoin);
//开启多线程
thread.start();
//创建个主线程
for (int i = 0; i < 100; i++) {
System.out.println("排队的人->"+i);
if (i==50){
try {
//开始插队,执行run线程
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程状态state
线程状态。 线程可以处于以下状态之一:
NEW
尚未启动的线程处于此状态。RUNNABLE
在Java虚拟机中执行的线程处于此状态。BLOCKED
被阻塞等待监视器锁定的线程处于此状态。WAITING
正在等待另一个线程执行特定动作的线程处于此状态。TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。TERMINATED
已退出的线程处于此状态。
线程生命周期:
NEW(开启)->RUNNABLE(就绪)->TIMED_WAITING(阻塞)->TERMINATED(销毁)
完整代码:
public class ThreadState{
public static void main(String[] args) {
//使用lambda表达式执行runnable接口
Thread thread = new Thread(()->{
//循环5个线程
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程执行结束");
});
//获取线程的状态
Thread.State state = thread.getState();
//输入线程状态
System.out.println(state);
//开启线程NEW
thread.start();
//循环输出线程当前状态
while (state != Thread.State.TERMINATED){
try {
Thread.sleep(100);
state = thread.getState();
System.out.println(state);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程优先级priority
线程优先级低,只意味着概率降低,不是按照从低到高来算,具体还是看cpu的调度
线程的优先级范围:1~10
- Thread.MIN_PRIORITY=1;
- Thread.MAX_PRIORITY=10;
- Thtead.NORM_PRIORITY=5;
改变优先级:
- getPriority().setPriority(int xx)
完整代码:
//获取和设置线程优先级
public class ThreadPriority {
public static void main(String[] args) {
//获取主线程的优先级 主函数优先级默认为5
System.out.println(Thread.currentThread().getName() + "->>" + Thread.currentThread().getPriority());
MyThread myThread = new MyThread();
//创建多线程对象
Thread thread1 = new Thread(myThread);
Thread thread2 = new Thread(myThread);
Thread thread3 = new Thread(myThread);
Thread thread4 = new Thread(myThread);
//设置线程优先级,然后启动线程
thread1.setPriority(9);
thread1.start();
thread2.setPriority(1);
thread2.start();
thread3.setPriority(5);
thread3.start();
thread4.setPriority(3);
thread4.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
//输出所有创建的线程的优先级
System.out.println(Thread.currentThread().getName() + "->>" + Thread.currentThread().getPriority());
}
}
线程守护daemon
守护线程:
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕,如后台操作日志,监控内存和GC回收
完整代码:
//开启保护线程
public class ThreadDaemon implements Runnable{
public static void main(String[] args) {
//开启主线程
new Thread(new ThreadDaemon()).start();
Thread threadGod = new Thread(new God());
//修改用户进程为保护进程
threadGod.setDaemon(true); // 默认为false 为用户经常,改为true为保护进程
threadGod.start();
//开启人的线程
new Thread(new Man()).start();
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("->>>>>"+i);
}
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝保护着你");
}
}
}
class Man implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("你度过了->"+i+"天");
}
}
}
线程同步
线程同步形成条件:队列+锁
线程同步就像几个人一起去一个厕所,锁相当于门,锁上其他人就进不去,安全性强,效率会变低
在一个进程带来的多个线程共享一块存储空间,虽然方便,但是会造成冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized
,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在一些问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
不安全的线程实例:
//不安全的抢票多线程
public class ThreadSafety{
public static void main(String[] args) {
Buy buy = new Buy();
new Thread(buy,"第一人啊").start();
new Thread(buy,"第二人啊啊").start();
new Thread(buy,"第三人啊啊啊").start();
}
}
class Buy implements Runnable{
private int tickNumber = 10;
boolean flag = true;
@Override
public void run() {
while (true){
isBuy();
}
}
public void isBuy(){
if (tickNumber<0){
flag=false;
return;
}
System.out.println(Thread.currentThread().getName()+"拿到了"+tickNumber--);
}
}
结果图:
线程安全
synchronized
同步方法:
synchronized方法:
public synchronized void medhod(int agrs){}
synchronized块:
synchronized (要锁的内容){}
添加synchronized
方法和方法快可以完成同步
synchronized方法控制对"对象"的访问,每个对象对应着一把锁,每个synchronized方法都必须获得对象的锁,否则线程会阻塞。方法一旦执行,就独占该锁,直到该方法放回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
方法分为只读和修改,我们只需要对修改方法进行同步即可
synchronized方法默认锁的是this自己
synchronizad块可以锁任何对象
synchronizad方法完整代码:
package com.hdt.kuangshen.thread;
//线程延迟
public class ThreadSleep implements Runnable{
//设置票数
private static int piao =10;
@Override
public synchronized void run() {
while (piao > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "->" + piao-- + "票");
}
}
public static void main(String[] args) {
ThreadSleep threadSleep = new ThreadSleep();
new Thread(threadSleep,"小明").start();
new Thread(threadSleep,"小白").start();
new Thread(threadSleep,"小黑").start();
}
}
synchronizad块完整代码:
import java.util.ArrayList;
import java.util.List;
public class ThreadSafety01 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i <= 10000; i++) {
new Thread(()->{
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(list.size());
}
}
CopyOnWriteArrayList(了解)
测试juc安全类的集合
完整代码:
//测试juc安全类的集合
public class ThreadCopyOnWriteArraylist {
public static void main(String[] args) {
//创建安全集合
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
//添加进juc安全集合中
list.add(Thread.currentThread().getName());
}).start();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(list.size());
}
}
死锁
什么是死锁:
死锁指的是两个或两个以上的进程在执行过程中,由于竞争资源或彼此通信而造成的阻塞,诺没有外力作用,将会无法继续进行
产生死锁的4个重要条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
完整代码:
//线程锁,死锁
public class ThreadDieLock {
public static void main(String[] args) {
Lock lock1 = new Lock(0,"第一人");
Lock lock2 = new Lock(2,"第二人");
new Thread(lock1).start();
new Thread(lock2).start();
}
}
//笔
class Pen{}
//笔盖
class Cover{}
//抢锁
class Lock implements Runnable{
static Pen pen = new Pen();
static Cover cover = new Cover();
int choice;
String pName;
Lock(int choice,String pName){
this.choice=choice;
this.pName=pName;
}
@Override
public void run() {
try {
Grab();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//造成锁死
public void Grab() throws InterruptedException {
if (choice==0){
synchronized (pen){
System.out.println(this.pName+"要笔");
Thread.sleep(1000);
synchronized (cover){
System.out.println(this.pName+"要盖");
}
}
}else {
synchronized (cover){
System.out.println(this.pName+"要盖");
Thread.sleep(1000);
synchronized (pen){
System.out.println(this.pName+"要笔");
}
}
}
}
}
Lock锁
可重入锁,通过显式定义同步锁对象来实现同步,从jdk5.0开始
ReentrantLock实现Lock,他与synchronized的功能一样,比较常用ReentrantLock
实例:
class A{
private final ReentrantLock lock = new ReentrantLock();
public void b(){
lock.lock();
try {
//需要保护安全线程的代码
}finally {
lock.unlock();
//如果同步代码有异常,要把unlock()写入finally中
}
}
}
完整代码:
import java.util.concurrent.locks.ReentrantLock;
//线程锁的使用
public class ThreadLock {
public static void main(String[] args) {
LockTest lockTest = new LockTest();
//定义多个资源获取同一个对象
new Thread(lockTest).start();
new Thread(lockTest).start();
new Thread(lockTest).start();
}
}
class LockTest implements Runnable{
private int ticket = 10;
//锁的类
private final ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
try {
grab();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void grab() throws InterruptedException {
//锁的启动,锁使用最好加try{}finally{关闭锁}
reentrantLock.lock();
try {
while (ticket>0){
Thread.sleep(1000);
System.out.println(ticket--);
}
}finally {
//解锁
reentrantLock.unlock();
}
}
}
Lock与synchronized比较:
- Lock是显示锁(手动开启和关闭锁),synchronized是隐式锁(出了作用域自动释放)
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序:
- Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体外)
生产者/消费者问题
- 在缓冲区为空时,消费者不能再进行消费
- 在缓冲区为满时,生产者不能再进行生产
- 在一个线程进行生产或消费时,其余线程不能再进行生产或消费等操作,即保持线程间的同步
- 注意条件变量与互斥锁的顺序
利用缓冲区解决:管程法
线程池
由于经常创建和销毁,对性能的影响很大
解决:
提前创建多个线程,放入线程池中,使用时直接获取,使用完放回池中
好处:
- 提高相应速度
- 降低资源消耗
- 便于线程管理
- 线程配置解析
- int corePoolSize // 核心线程数量
- int maximumPoolSize // 最大线程数(核心线程数 + 临时线程数)
- long keepAliveTime // 临时线程的最大空闲时间
- TimeUnit unit // 时间单位(针对临时线程)
- BlockingQueue
workQueue // 任务队列 - ThreadFactory threadFactory // 线程工厂(可以为线程起名字),线程池中的线程默认名称为pool-m-thread-n
- RejectedExecutionHandler handler // 拒绝策略
ExecutorService接口
使用 ExecutorService
接口可以管理线程池,比如提交任务,停止任务
线程池常用接口:
-
void shutdown() 当任务队列中的所有任务都执行完成之后,销毁线程池 (不允许有新的任务提交)
- 正在运行的任务不会受到影响,继续运行
shutdown()
之后提交的任务会抛出RejectedExecutionException
异常,代表拒绝接收
-
List<Runnable> shutdownNow()
(可能会) 停止正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表shutdownNow()
之后提交的任务会抛出RejectedExecutionException
异常,代表拒绝接收- 对于正在运行的任务
- 如果此任务的代码可以抛出 InterruptedException 异常 (如wait、sleep、join方法等),则停止此任务,抛出 InterruptedException 异常
- 如果此任务的代码中无法抛出 InterruptedException 异常,则此任务会持续运行直到结束
-
<T> Future<T> submit(Callable<T> task)
提交带返回值的任务,返回一个表示该任务的 Future 对象 -
Future<?> submit(Runnable task)
提交 Runnable 任务,并返回一个表示该任务的 Future 对象 -
<T> Future<T> submit(Runnable task, T result)
提交 Runnable 任务,并返回一个表示该任务的 Future 对象
Executor
接口中有execute(Runnable task)
方法,可以用来提交 Runnable 任务
获取接口实例
- 获取 ExecutorService 实例可以利用 JDK 中的 Executors 类中的静态方法,常用的如下
- 带缓存线程池 static ExecutorService newCachedThreadPool()
- 固定大小线程池 static ExecutorService newFixedThreadPool(int nThreads)
- 单线程线程池 static ExecutorService newSingleThreadExecutor()
使用缓存线程池
- 使用方法
ExecutorService executorService = Executors.newCachedThreadPool();
特点:
- 核心线程数为0,所有线程都是临时线程,线程可重复利用
- 最大线程数为 Integer.MAX_VALUE ,表示 Java 中的最大整数,意味着线程可以无限创建
- 临时线程的最大空闲时间是60s
- 任务队列(阻塞队列)的方式为没有容量,来一个任务使用一个线程执行它,任务不会等待
- 适合任务数密集,但每个任务执行时间较短的情况
由于每个线程执行完之后会归还池中,在空闲时间内还可以执行其余任务,故起名为带缓存
完整代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool {
public static void main(String[] args) {
//设置线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new MyThreads());
service.execute(new MyThreads());
service.execute(new MyThreads());
service.execute(new MyThreads());
//关闭链接
service.shutdown();
}
}
class MyThreads implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}