多线程

多线程个人总结

多线程口诀:
多线程口诀

一.多线程的生命周期

在这里插入图片描述

二.创建多线程的四种方式

1.继承Thread类 步骤:

  • 创建一个继承Thread的类
  • 重写run()方法,将需要执行的代码写在其中
  • 创建实现Thread的类的实例 Thread t1 = new MyThread();
  • 开启线程

2.实现Runnable接口

  • 创建一个实现Runnable接口的类
  • 实现run()方法,将需要的代码写在其中
  • 创建Thread类实例,并将MyThread传入 Thread t1 = new Thread(new MyThread1());
  • 开启线程 t.start()

这两种创建多线程方法异同:

  • 实现Runnable接口比继承Thread类扩展性更强,可利用性更好,因为java只支持单继承
  • 都重写了run()方法

3.实现Callable接口

  • 创建一个类实现Callable接口的类
  • 实现call()方法,将需要的代码写入其中,可以有返回值
  • 创建一个FutureTask实例,然后new Thread(futureTask).start()启动线程

**相比实现Runnable接口有什么好处:**call()可以抛出异常,call()可以有返回值,支持泛型

4.通过线程池创建多线程

  • 创建一个类实现Runnable接口的类

  • 实现run(方法,将需要的代码写入其中

  • 提供指定线程数量的线程池 ExecutorService executorService = Executors.newFixedThreadPool(10);

  • 启动线程池,需要提供实现Runnable接口的类

    executorService.execute(new Example2());  //适合与Runnable
    //executorService.submit(new Example2());   //适合于Callable
    
  • 关闭线程池 executorService.shutdown();

**这种方式的好处:**提高相应速度(减少创建新线程的时间),降低资源消耗,重复使用,便于线程的管理

三.多线程的常用方法

1.run()方法在什么时候执行:

  • 当线程启动,即t.start()时会自动调用run()方法
  • main方法结束,程序不一定停止,因为可能有其他的线程还在运行

2.Thread.currentThread()获得当前线程对象getName(),setName()

3.t.getPriority()拿到线程的优先等级

4.Thread.sleep()使线程阻塞,创建一个t线程,然后t.sleep()与Thread.sleep()没区别,比如都在main线程中,阻塞的都是main线程

5.t.interrupt()通过异常来打断线程的休眠,会产生 java.lang.InterruptedException: sleep interrupted 异常

6.怎么很好的终止一个线程:p.run = false,在Processor中定义一个boolean run = false,如终止一个循环

7.Thread.yield()该方法是一个静态方法 作用:给同一个优先级的线程让位。但是让位时间不固定。

8.t.join(); //t和主线程合并. 单线程的程序

9.wait(),notify(),notifyAll()只能使用在同步代码块或者同步方法中,wait()使线程阻塞,当notify()时唤醒线程,wait()归还锁,notify()唤醒一个线程,notifyAll()唤醒所有等待池中的线程。

**start()和run()的区别:**start()是开启线程,是线程处于就绪状态,run()是线程已经开始运行,只要开启线程,会自动执行run()方法,且不用等待run()方法中的代码执行完成,会继续执行下面的代码,实现多线程,但是不能通过调用的方法来使用run(),这样就相当于调用了一个普通的类方法,不能达到多线程的效果。

wait()和sleep()的异同:

  • wait()和sleep()都能使线程阻塞
  • wait(),notify(),notifyAll()都是Object类中的方法,只能在同步代码块或者同步方法中使用,sleep()可以在所有的地方调用
  • 在同步代码块中,wait()会释放锁,而sleep()并不会释放锁
  • sleep()需要抓取异常,而wait()不需要

**notify()和notifyAll()的区别:**notify()只会唤醒线程池中的一个线程,而notifyAll()会唤醒线程池中的所有线程

四.线程安全,线程同步

synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。

1.同步代码块

​ 同步监视器:任何一个类的对象都可以充当锁,但是多个线程只能共用一个锁

​ 共享数据:多个线程共同处理的数据

2.同步方法

3.Lock锁

卖票问题:三个窗口卖100张票

继承Thread类

public class MaiPiao1 {
    public static void main(String[] args) {
        Thread t1 = new Window();
        Thread t2 = new Window();
        Thread t3 = new Window();
        t1.start();
        t2.start();
        t3.start();
    }
}
//窗口卖票问题:创建三个窗口卖票,卖100张
//继承Thread类,创建了三个窗口对象,所以每次都会新建一个ticket,需要将ticket定义为static,全局变量
//但是这样还是有问题,还是会出现重票问题,需要加一个同步代码块,因为是继承Thread类,创建了三个窗口对象
//我们需要确定几个窗口线程是共用一个锁的
//通过实现Runnable接口,只创建了一个窗口对象,所以每次操作的是同一个ticket,就不需要加static
class Window extends Thread{
    private static int ticket = 100;
    static Object obj = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (obj){
                if (ticket >0) {
                    ticket--;
                    System.out.println(getName() + "---->" +ticket);
                }else{
                    break;
                }
            }

        }
    }
}

实现Runnable接口

public class MiaoPiao2 {
    public static void main(String[] args) {
        Window2 w = new Window2();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);
        t1.start();
        t2.start();
        t3.start();
    }
}
//这种会有问题,会出现重票,出现线程安全问题
class Window2 implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            if (ticket > 0){
                ticket--;
                System.out.println(Thread.currentThread().getName() + "--->" + ticket);
            }else
                break;
        }
    }
}
//加锁(Lock)解决线程安全问题
class Window3 implements Runnable{
    private int ticket = 100;
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try {
                //加锁
                lock.lock();
                if (ticket > 0){
                    System.out.println(Thread.currentThread().getName() + "--->" + ticket);
                    ticket--;
                }else
                    break;
            }finally {
                //去锁
                lock.unlock();
            }
        }
    }
}
//同步方法解决
class Window4 implements Runnable{
    private int ticket = 100;
    @Override
    public synchronized void run() {
        while (true){
            if (ticket >0){
                System.out.println(Thread.currentThread().getName() +"--->" + ticket);
                ticket--;
            }else
                break;
        }
    }
}

**synchronized和Lock的异同:**1.两者都能解决线程的安全问题 2.synchronized执行完后自动释放同步监视器,而Lock需要手动启动(lock()),以及手动释放(unlock()),Lock比较灵活,所以用的更多。

**三者使用优先级:**Lock锁 ----> 同步代码块 ----> 同步方法

五.线程通信

交替输出100以内的数

public class TongXin {
    public static void main(String[] args) {
        MyTest m = new MyTest();
        Thread t1 = new Thread(m);
        Thread t2 = new Thread(m);
        Thread t3 = new Thread(m);
        t1.start();
        t2.start();
        t3.start();
    }
}
class MyTest implements Runnable{
    private int number = 1;
    @Override
    public void run() {
        while (true){
            synchronized (this){
                notify();
                //实现交替输出100
                if (number<=100){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "--->" +number);
                    number++;
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

六.死锁产生原因

**原因:**不同线程各自占用对方的同步资源不放弃,形成一种僵持状态,导致死锁。

死锁产生的4个必要条件?

产生死锁的必要条件:

  1. 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  2. 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  4. 环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

解决死锁的基本方法

预防死锁:

  • 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
  • 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
  • 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
  • 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

1、以确定的顺序获得锁

如果必须获取多个锁,那么在设计的时候需要充分考虑不同线程之前获得锁的顺序。按照上面的例子,两个线程获得锁的时序图如下:
在这里插入图片描述
如果此时把获得锁的时序改成:

在这里插入图片描述
那么死锁就永远不会发生。 针对两个特定的锁,开发者可以尝试按照锁对象的hashCode值大小的顺序,分别获得两个锁,这样锁总是会以特定的顺序获得锁,那么死锁也不会发生。问题变得更加复杂一些,如果此时有多个线程,都在竞争不同的锁,简单按照锁对象的hashCode进行排序(单纯按照hashCode顺序排序会出现“环路等待”),可能就无法满足要求了,这个时候开发者可以使用银行家算法,所有的锁都按照特定的顺序获取,同样可以防止死锁的发生,该算法在这里就不再赘述了,有兴趣的可以自行了解一下。

2、超时放弃

当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。 还是按照之前的例子,时序图如下:
在这里插入图片描述
避免死锁:

  • 预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
  • 银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。

解除死锁:

当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:

  • 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
  • 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

死锁检测

1、Jstack命令

jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。

2、JConsole工具

Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。

七.程序,进程,线程

程序就比如一个app,向我们日常使用的qq就是一个程序,当qq启动后,成为一个线程,而这时我们使用qq中的多个功能,就相当于开启了多个线程,每个线程都有一条执行线,从创建到消亡。

八.乐观锁和悲观锁

悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现

乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量

九.写一个线程安全的单例模式

1.饿汉模式

public class Erhan {
    //只在类加载时执行一次
    private static Erhan erhan = new Erhan();
    //构造方法私有化
    private Erhan(){}
    public static Erhan gerErHan(){
        return erhan;
    }
}

2.懒汉模式

public class LanHan {
    //只在要用的时候在创建
    private static LanHan lanhan;
    //构造方法私有化
    private LanHan(){}
    public static LanHan gerErHan(){
        synchronized (LanHan.class){
            if (lanhan == null){
                new LanHan();
            }
            return lanhan;    
        }
        
    }
}

十.JUC并发操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值