多线程_学习笔记

1. 核心概念

  • 线程就是独立的执行路径;
  • 在程序运行时,即使没有自己创建线程, 后台也会有多个线程, 如主线程,gc线程;
  • main() 称之为主线程, 为 系统的入口, 用于执行整个程序;
  • 在一个进程中,如果开辟了多个线程,线程的运行有调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为干预的;
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

2. 创建线程的三种方式

2.1 继承Thread类

继承Thread类,重写run()方法编写线程执行体,调用start()开启线程;

注: 线程不一定立即执行

public class Demo1 extends Thread{
    
    
    @Override
    public void run() {
    
    
       for (int i=0;i<200;i++){
    
    
           System.out.println("我在看代码==========="+i);
       }
    }

    public static void main(String[] args) {
    
    
        Demo1 t = new Demo1();
        t.start();
        for (int i=0;i<1000;i++){
    
    
            System.out.println("我在学习多线程========="+i);
        }
    }
}

2.2 实现Runnable接口

1.定义一个类实现Runnable接口;
2.实现run()方法,编写线程执行体
3.创建线程对象,调用start()方法启动线程 //new Thread(xx).start();

public class TestThread2 implements Runnable {
    
    
    @Override
    public void run() {
    
    
        for (int i=0;i<200;i++){
    
    
            System.out.println("我在看代码==========="+i);
        }
    }

    public static void main(String[] args) {
    
    
        TestThread2 testThread2 = new TestThread2();
        new Thread(testThread2).start();
        for (int i=0;i<1000;i++){
    
    
            System.out.println("我在学习多线程========="+i);
        }
    }
}

实现Callable接口

  1. 实现Callable接口, 需要返回值类型
  2. 重写call方法,需要抛异常
  3. 创建目标对象
  4. 创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
  5. 提交执行: Future result1 = ser.submit(t1);
  6. 获取结果: boolean r1 = result1.get();
  7. 关闭服务: ser.shutdownNow();

3.静态代理

静态代理模式总结:
1.真实对象和代理对象都要实现同一个接口;
2.代理对象要代理真实角色
好处:
1 代理对象可以做很多真实对象做不了的事情;
2 真实对象专注做自己的事情

public class StaticProxy {
    
    
    public static void main(String[] args) {
    
    
//        WeddingCompany weddingCompany = new WeddingCompany(new You());
//        weddingCompany.HappyMarry();
        new WeddingCompany(new You()).HappyMarry();
    }
}

interface Marry{
    
    
    void HappyMarry();
}
//真实角色,你去结婚
class You implements Marry{
    
    
    @Override
    public void HappyMarry() {
    
    
        System.out.println("xx结婚了...");
    }
}
//代理角色,  帮助你结婚
class WeddingCompany implements Marry{
    
    
    //目标对象
    private Marry target;

    public WeddingCompany(Marry target) {
    
    
        this.target = target;
    }

    @Override
    public void HappyMarry() {
    
    
        before();
        this.target.HappyMarry();
        after();
    }

    private void after() {
    
    
        System.out.println("结婚后干的事....");
    }

    private void before() {
    
    
        System.out.println("结婚前干的事.....");
    }
}

4. Lambda表达式

4.1为什么要使用lambda表达式

  1. 避免匿名内部类定义过多
  2. 可以让你的代码看起来很简洁
  3. 去掉一堆没有意义的代码,只留下核心的逻辑

4.2 函数式接口的定义

任何接口,如果只包含唯一一个抽象方法, 那么它就是一个函数式接口 --Functional Interface;

public interface Runnable{
    
    
	public abstract void run();
}

对于函数式接口,可以通过lambda表达式来创建该接口的对象:

  1. lambda 表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹;
  2. 前提: 接口为函数式接口
  3. 多个参数也可以去掉参数类型, 要去掉就都去掉, 必须加上括号

4.3 局部内部类,匿名内部类

public class TestLambda {
    
    
    //3. 静态内部类
    static class Like2 implements ILike{
    
    
        @Override
        public void lambda() {
    
    
            System.out.println("i like lambda2");
        }
    }

    public static void main(String[] args) {
    
    
        ILike like = new Like();
        like.lambda();

        ILike like2 = new Like2();
        like2.lambda();

        //4.局部内部类
        class Like3 implements ILike{
    
    
            @Override
            public void lambda() {
    
    
                System.out.println("i like iambda3");
            }
        }
       ILike like3 = new Like3();
        like3.lambda();

        //5.匿名内部类,没有类的名称,必须借助几口或者父类
        ILike like5 = new ILike() {
    
    
            @Override
            public void lambda() {
    
    
                System.out.println("i like lambda5");
            }
        };
        like5.lambda();

        //6. 用lambda简化
        ILike lamLike = ()->{
    
    
            System.out.println("i like lambda :()->{}");
        };
        lamLike.lambda();

    }
}
//1. 定义一个函数式接口
interface ILike{
    
    
    void lambda();
}
//2. 实现类
class Like implements ILike{
    
    
    @Override
    public void lambda() {
    
    
        System.out.println("i like lambda");
    }
}

5. 线程状态

在这里插入图片描述

5.1 停止线程

  • 不推荐使用jdk提供的stop() , destroy() 方法;
  • 推荐线程自己停下来;建议使用一个标志位来终止 :
    当flag=false,则终止线程运行
public class StopThread implements Runnable{
    
    
    //1.线程中定义线程体使用的表示
    boolean flag = true;

    @Override
    public void run() {
    
    
    //2.线程体使用该标识
        while (flag){
    
    
            System.out.println("run  thread ");
        }
    }
    //3. 对外提供方法改变标识
    public void stop(){
    
    
        this.flag=false;
    }
    public static void main(String[] args) {
    
    
        StopThread st = new StopThread();
        new Thread(st).start();
        for (int i = 0; i < 1000; i++) {
    
    
            System.out.println("------"+i);
            if(i==988){
    
    
                st.stop();  //主线程让st线程停止
            }
        }
    }
}

5.2 线程休眠

  • sleep(时间) : 当前线程阻塞的毫秒数
  • sleep存在Interru’ptedException;
  • 时间到达后线程进入就绪状态;
  • 可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁

5.3 线程礼让–Yield

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞;
  • 将线程从运行状态装维就绪状态;
  • 让cpu重新调度,礼让不一定成功,看cpu心情;

5.4 Join

  • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
  • 可以把它想象成强制插队
Thread th = new Thread();
th.start();
th.join();// 插队执行

5.5 线程状态观测

Thread.State

  • NEW : 尚未启动的线程处于此状态;
  • RUNNABLE: 在Java虚拟机中执行的线程处于此状态
  • BLOCKED : 被阻塞等待监视器锁定的线程出此状态;
  • WAITING : 正在等待另一个线程执行特定动作的线程处于此状态;
  • TIMED_WAITING: 正在等待另一个线程指定动作达到指定等待时间的线程处于此状态
  • TERMINATED : 已退出的线程处于此状态;

5.6 线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行.
  • 线程的优先级用数字表示,范围从1~10
    Thread.MIN_PRIORITY=1;
    Thread.MAX_PRIORITY=10;
    Thread.NORM_PRIORITY=5;
  • 使用一下方式改变或获取优先级
    getPriority() setPriority(int xx)
    优先级的设置在start()调度前

5.7 守护线程 --daemon

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不必等待守护线程执行完毕
    Thread.setDaemon(true);
    虚拟机在用户线程跑完后程序停止, 不会考虑守护线程

6 线程同步

多个线程操作同一个资源

  • 并发: 同一个对象被多个线程同时操作
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    还可以使用安全类型
    public static void main(String[] args) {
    
    
//        ArrayList<String> list = new ArrayList<>();
//        for (int i = 0; i < 1000; i++) {
    
    
//            new Thread(()->{
    
    
//                list.add(Thread.currentThread().getName());
//            }).start();
//        }
//        try {
    
    
//            Thread.sleep(3000);
//        } catch (InterruptedException e) {
    
    
//            e.printStackTrace();
//        }
//        System.out.println(list.size());

        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 1000; i++) {
    
    
            new Thread(()->{
    
    
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
    
    
            Thread.sleep(3000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(list.size());
    }

上面的ArrayList 未加锁,不安全,线面的CopyOnWriteArrayList类型也没有加锁,但是是安全的类型

7. 死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形. 某一个同步块同时拥有"两个以上对象的锁"时,就可能会发生死锁的问题;

多个线程互相抱着对方需要的资源,然后形成僵持

避免死锁的方法:
产生死锁的四个必要条件

  1. 互斥条件: 一个资源每次只能被一个进程使用
  2. 请求与保持条件: 一个进程因请求资源而阻塞时,对已获得的资源保持不放
  3. 不剥夺条件: 进程已获得的资源,在未使用完之前,不能强行剥夺
  4. 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系
    以上四个死锁的必要条件,只需破坏其中一个或多个就可避免死锁的发生

8. Lock(锁)

通过显示定义同步锁对象来实现同步. 同步锁使用Lock对象充当 (jdk5.0)
Lock接口是控制多个线程对共享资源进行访问的工具.
锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象.
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中, 比较常用的是ReentrantLock,可以显示加锁、释放锁。
未加锁的代码

public class LockTest {
    
    
    public static void main(String[] args) {
    
    
        TestLock2 testLock2 = new TestLock2();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}
//多线程对象
class TestLock2 implements Runnable{
    
    
    int ticketNums = 10;
    @Override
    public void run() {
    
    
        while (true){
    
    
            if(ticketNums > 0){
    
    
                try {
    
    
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(ticketNums--);
            }else {
    
    
                break;
            }
        }
    }
}

加锁后

//这个多线程加上lock锁后为什么只有线程a在执行???
public class LockTest {
    
    
    public static void main(String[] args) {
    
    
        TestLock2 testLock2 = new TestLock2();
        new Thread(testLock2,"a").start();
        new Thread(testLock2,"b").start();
        new Thread(testLock2,"c").start();
    }
}
//多线程对象
class TestLock2 implements Runnable{
    
    
    int ticketNums = 10;
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
    
    
        while (true){
    
    
            try {
    
    
                lock.lock();
                if(ticketNums > 0){
    
    
                    try {
    
    
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                    System.out.println(Thread.currentThread().getName());
                }else {
    
    
                    break;
                }
            }finally {
    
    
                lock.unlock();
            }
        }
    }
}

8.1 synchronized与Lock的对比

  • Lock是显示锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好. 并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序: Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

9.线程协作与通信

生产者消费者场景理解:

  • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
  • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止;
  • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止;

这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件

  • 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
  • 在生产者消费者问题中,仅有synchronized是不够的
    • synchronized可阻止并发更新同一个共享资源,实现了同步
    • synchronized 不能用来实现不同线程之间的消息传递(通信)

java提供了几个方法解决线程之间的通信问题
wait() : 表示线程一直等待,知道其他线程通知,与sleep不同,会释放锁

9.1利用缓冲区解决(管程法)

9.2 信号灯法(标志位解决)

10 . 线程池

public class TestPool {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService service = Executors.newFixedThreadPool(4);
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        
    }

}
class MyThread implements Runnable{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 100; i++) {
    
    
        System.out.println(Thread.currentThread().getName());
        }
    }
}

11 线程启动方式的总结

public class ThreadStudy {
    
    
    public static void main(String[] args) {
    
    
        new MyThread1().start();
        new Thread(new MyThread1()).start();
        new Thread(new MyThread2()).start();
        //启动实现Callable的方式
        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread3());
        new Thread(futureTask).start();
        try {
    
    
            Integer integer = futureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        }

    }
}
class MyThread1 extends Thread{
    
    
    @Override
    public void run() {
    
    
        System.out.println("MyThread1");
    }
}
class MyThread2 implements Runnable{
    
    
    @Override
    public void run() {
    
    
        System.out.println("MyThread2");
    }
}
class MyThread3 implements Callable<Integer>{
    
    
    @Override
    public Integer call() throws Exception {
    
    
        System.out.println("MyThread3");
        return 100;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_40084325/article/details/113804367