并发编程学习笔记(一)——初始并发编程

版权声明:本文为博主原创,未经博主允许不得转载。 https://blog.csdn.net/weixin_36904568/article/details/90339246

一:并发编程

1. 基本概念

(1)同步和异步

  • 同步:同步方法一旦调用,调用者必须等待方法调用返回后,才能继续后面的行为
  • 异步:异步方法在另外一个线程执行,方法调用立即返回,调用者可以继续后面的操作,操作完成后通知调用者,返回结果

(2)并发与并行

  • 并发(1个CPU):多个任务交替进行(可能串行)
  • 并行(多个CPU):多个任务同时进行

(3)临界区

表示一个公共资源,可以被多个线程使用。同一时刻只能被一个线程使用。

(4)阻塞与非阻塞

  • 阻塞:线程等待临界区资源,被挂起
  • 非阻塞:所有线程都会尝试不断前行

(5)死锁与活锁

  • 死锁:线程之间彼此占有对方需要的资源,却不释放,还去请求对方的资源,导致多个线程无法获取完整资源而等待
  • 活锁:线程之间彼此占有对方需要的资源,都想要释放,并且去请求对方的资源,导致多个线程无法获取完整资源而等待
  • 饥饿:线程由于某些原因无法获取资源,导致一直无法执行

2. 并发级别

(1)阻塞(悲观策略)

在其他线程释放资源之前,当前线程无法继续执行

(2)无饥饿

  • 对于非公平锁:系统允许高优先级的线程先执行,导致低优先级线程饥饿
  • 对于公平锁:按照先来先服务原则,所有线程都有机会执行

(3)无障碍(乐观策略)

认为线程之间不会发生冲突,多个线程可以没有障碍的进入临界区。如果检测到数据异常,则回滚自己的操作,确保数据安全(可能导致没有线程离开临界区)

一致性标记
  1. 线程在操作之前,读取并保存标记
  2. 如果需要更改临界区数据,则需要更新该标记,告诉其他线程不安全
  3. 操作完成后,再次读取,检查标记是否被改变。如果一致则证明没有冲突,如果不一致则证明有冲突需要重试

(4)无锁

在无障碍的基础上,要求必然有一个线程能够在有限时间内离开临界区(可能导致重试的线程出现饥饿)

(5)无等待

在无锁的基础上,要求所有线程都能够在有限时间内离开临界区

RCU
  • 读操作:不加控制
  • 写操作:通过修改数据的副本,在合适的时机写回数据

3. 并发定律

(1)Amdahl定律

为了提高系统速度,需要在增加CPU处理器的同时,提高系统内可并行化的模块比重

(2)Gustafson定律

如果串行化比例很小,并行化比例很大,则增加CPU处理器就能提高系统的速度

4. Java的内存模型JMM

(1)原子性

一个操作是不可中断的,不会被其他线程干扰。
(对于32位虚拟机,如果对64位的数据进行操作,则不是原子性的)

(2)可见性

当一个线程修改了一个共享变量的值,其他线程立即知道这个修改
(缓存优化、硬件优化、指令重排、编辑器优化可能导致操作不可见)

(3)有序性

程序按顺序执行

指令重排

可以保证串行语义的一致性,但无法保证多线程语义的一致性
可以减少中断流水线的次数

Happen-Before原则

  • 程序顺序原则(单个线程内)
  • volatile原则(写先于读)
  • 锁原则(解锁先于随后的加锁)
  • 传递性
  • 线程的start() 先于它的每一个动作
  • 线程的每一个动作先于它的终结
  • 线程的中断interrupt()先于被中断线程的代码
  • 对象的构造函数先于它的finalize()

二:Java的并行设计

1. 线程的生命周期

  • 新建 New:创建完成,还没有启动
  • 运行 Runable:就绪或正在执行
  • 无限等待 Waiting:等待被其他线程唤醒
  • 限期等待 Timed Waiting:等待一段时间后自动唤醒
  • 阻塞 Blocked:等待获取排他锁
  • 结束 Terminated:线程结束执行

2. 线程的基本操作

(1)新建

不使用thread.run()方法开启线程,它只会串行执行run()方法。
应该使用thread.start();方法新建线程

通过继承创建:
public class MyThread{
    static class Thread1 extends Thread {
        @Override
        public void run() {
            System.out.println("通过继承的方法创建线程");
        }
    }

    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        thread1.start();
       
    }
}

通过匿名内部类创建:
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                System.out.println("通过匿名内部类的方法创建线程");
            }
        };
        thread.start();
    }
通过实现接口创建:
public class MyThread{

    static class Thread2 implements Runnable{

        @Override
        public void run() {
            System.out.println("通过接口的方法创建线程");
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new Thread2());
        thread.start();
    }
}

(2)睡眠

通过Thread.sleep(时间),让线程休眠若干时间,如果在休眠中被中断,则会抛出InterruptedException

睡眠中仍然持有资源,无法被其他线程唤醒!

(3)终止

不用使用thread.stop()方法,会直接结束线程,导致数据不一致。
需要自己决定何时让线程结束(状态量)

public class MyThread {

    static class Thread1 extends Thread {

	//标志结束的信号
        private boolean stop;

        public void stopMe() {
            stop = true;
        }

        @Override
        public void run() {
            while (true) {
                System.out.println("实现线程");
                try {
                	//让线程慢一点,容易观察
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //接收到结束信号,停止循环
                if (stop) {
                    System.out.println("结束了");
                    break;
                }
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {
        Thread1 thread = new Thread1();
        thread.start();
//        thread.stop();
	//等待1秒后,让线程停止
        Thread.sleep(1000);
        thread.stopMe();
    }
}

(4)中断

告诉系统应该退出,由系统决定

  • 设置中断标志位:thread.interrupt(); (类似thread.stopMe();)

  • 检查中断标志位:Thread.currentThread().isInterrupted();(类似判断语句if(stop))

  • 检查中断标志位并清零:Thread.interrupted();

特别之处:

中断方法不仅提醒系统结束,也能处理睡眠或等待的特殊情况

public class MyThread {

    static class Thread1 extends Thread {

        private boolean stop;

        public void stopMe() {
            stop = true;
        }

        @Override
        public void run() {
            while (true) {
                System.out.println("实现线程");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                	//抛出异常的同时会清除中断标记
                    System.out.println("我在睡觉你还中断我?");
                    //再次设置标记
                    Thread.currentThread().interrupt();
                }
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("结束了");
                    break;
                }
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {
        Thread1 thread = new Thread1();
        thread.start();
//        thread.stop();
        Thread.sleep(1000);
//        thread.stopMe();
        thread.interrupt();
        
    }
}


(5)等待和通知

  • 线程A获取对象xx的监视器,调用了xx.wait()方法,则线程A停止运行,进入xx对象的等待队列,释放对象xx的监视器
  • 其他线程获取对象xx的监视器,调用了xx.notify()方法,则从xx对象的等待队列中随机选择线程并唤醒,释放对象xx的监视器
  • 其他线程获取对象xx的监视器,调用了xx.notifyAll()方法,则唤醒xx对象的等待队列中的所有线程,释放对象xx的监视器

等待时会释放所持资源,可以被其他线程唤醒!

public class WaitAndNotify {
    final static Object object = new Object();
    static class ThreadA extends Thread{

        private String name;

        public ThreadA(String name) {
            super(name);
            this.name = name;
        }

        @Override
        public void run() {
            synchronized (object){
                System.out.println(name+"持有了对象");
                try {
                    System.out.println("对象想等待了");
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name+"又持有了对象");
            }
        }
    }
    static class ThreadB extends Thread{
        @Override
        public void run() {
            synchronized (object){
                System.out.println("B持有了对象");
                System.out.println("对象准备好了");
                object.notifyAll();
            }
        }
    }

    public static void main(String[] args) {
        //一个线程等待
//        ThreadA a = new ThreadA("A");
//        a.start();
//        ThreadB b = new ThreadB();
//        b.start();
        //多个线程等待
        ThreadA[] as = new ThreadA[5];
        for (int i = 0; i < 5; i++) {
            as[i] = new ThreadA("A"+i);
            as[i].start();
        }
        ThreadB b = new ThreadB();
        b.start();
    }
}

(6)挂起和继续执行

  • 不应该使用thread.suspend()挂起线程,因为资源无法被释放
  • 不应该使用thread.resume()唤醒线程,如果在挂起前唤醒,则线程将永久挂起
public class SuspendAndResume {

    static class ThreadA extends Thread {
        private boolean suspend = false;

        public void suspendMe() {
            System.out.println("挂起线程");
            suspend = true;
        }

        //唤醒线程——通知
        public void resumeMe() {
            System.out.println("唤醒线程");
            suspend = false;
            synchronized (this) {
                notify();
            }
        }

        @Override
        public void run() {
            while (true) {
                synchronized (this) {

                    //被挂起了——等待
                    while (suspend) {
                        try {
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                        System.out.println("线程A开始工作");

                }
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadA a = new ThreadA();
        a.start();
        Thread.sleep(1000);
        a.suspendMe();
        Thread.sleep(1000);
        a.resumeMe();
    }
}


(7)等待结束

通过thread.join(),一个线程需要使用等待依赖的线程thread执行完毕,才能继续执行

  • join():无限等待,直到目标线程完成
  • join(时间):等待一段时间,如果目标线程还没有完成,则继续进行
public class Join {
    static volatile int i = 0;
    static class ThreadA extends Thread{
        @Override
        public void run() {
            for (i = 0; i < 10; i++) {

            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadA a = new ThreadA();
        a.start();
        a.join();
        System.out.println(i);
    }
}

(8)谦让

通过Thread.yield(),当前线程让出CPU,再重新进行争夺。
适合一个低优先级、占用CPU资源多的线程

3. Java的关键字

(1)volatile关键字

可以保证数据的可见性和有序性,不能保证原子性

(2)synchronized关键字

确保操作的原子性,线程之间的有序性和可见性

  • 指定加锁对象:进入同步块之前需要获得给定对象的锁
  • 作用于实例方法:进入同步块之前需要获得实例对象的锁
  • 作用于静态方法:进入同步块之前需要获得当前类的锁

4. 线程的优先级

Java中使用1到10或者3个静态变量表示优先级,一般高优先级在竞争资源时更有优势

  • Thread.MIN_PRIORITY
  • Thread.NORM_PRIORITY
  • Thread.MAX_PRIORITY
thread.setPriority(优先级);

5. 守护线程Daemon

  • 守护线程:在后台完成系统性的任务(如果只剩下守护线程,JVM会自然退出)
  • 用户线程:完成程序的业务操作
public class DaemonTest {
    static class Daemon extends Thread{
        @Override
        public void run() {
            while (true){
                System.out.println("我在进行后台工作...");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Daemon();
        //设置守护线程
        thread.setDaemon(true);
        thread.start();
        //休眠3秒后,主线程结束,所以守护线程也结束了
        Thread.sleep(3000);
    }
}

6. 线程组

如果线程数量多,并且功能明确,则可以把相同功能的线程放置在一个线程组中

创建

  • 线程组:ThreadGroup group = new ThreadGroup(线程组名);
  • 加入线程:Thread thread = new Thread(group,线程接口,线程名);

使用

  • 获取活动线程个数: System.out.println(“当前活跃线程:”+group.activeCount());
  • 获取线程组的线程个数:group.list()
public class ThreadGroupTest {
    static class Group implements Runnable{
        @Override
        public void run() {
            String group = Thread.currentThread().getThreadGroup().getName();
            String name = Thread.currentThread().getName();
            while (true){
                System.out.println(group+"--"+name);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) {
        ThreadGroup group = new ThreadGroup("Test");
        Thread t1 = new Thread(group,new Group(),"Thread1");
        Thread t2 = new Thread(group,new Group(),"Thread2");
        t1.start();
        t2.start();
        System.out.println("当前活跃线程:"+group.activeCount());
        group.list();
    }
}

7. Java的集合

用Vector代替ArrayList
用ConcurrentHashMap代替HashMap

猜你喜欢

转载自blog.csdn.net/weixin_36904568/article/details/90339246