多线程 备忘

164 阅读6分钟

Thread

常用方法

  • Thread.yield(); 线程礼让

  • Thread.join(); 线程插队

  • Thread.State

    • thread.getState() 观察线程状态
Thread thread = new Thread(() -> {
            for (int i = 0; i < 6; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
		// 观察创建后状态
        Thread.State state = thread.getState();
        System.out.println(state);

		// 观察线程启动后状态
        thread.start();
        state = thread.getState(); // 更新状态

        while (state != Thread.State.TERMINATED) { // 只要线程不终止,就会一直执行
            state = thread.getState(); // 更新线程状态
            System.out.println(state); // 输出状态
        }

线程的优先级

  • 线程的优先级用数字表示,范围1~1

    • Thread.MIN_PRIORITY = 1
    • Thread.MAX_PRIORITY = 10
    • Thread.NORM_PRIORITY = 5
  • 使用以下方式改变或获取优先级

    • getPriority()

    • setPriority(int xxx)

    Thread.currentThread.getPriority();

    Thread.currentThread.setPriority(5);

守护线程

线程分为用户线程和守护线程

虚拟机必须确保用户线程执行完毕

虚拟机不用等待守护线程执行完毕

如:后天记录操作日志,监控内存,垃圾回收等;

thread.setDaemon(true) // 默认是fales用户线程

线程同步

  • 多个线程操作同一个资源
  • 同一个资源,多人想使用;解决办法排队,一个个来
  • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用

线程同步

  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized ,当一个线程获得对象的排他锁,独占资源,其他线程必须等待使用后释放锁即可。存在一下问题

    • 一个线程持有锁会导致其他所需要此锁的线程挂起;
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题;

同步块

  • 同步块:synchronized(obj){}

  • Obj称之为同步监视器

    • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身或者是class
  • 同步监视器的执行过程

    1. 第一线程访问,锁定同步监视器,执行其中代码;
    2. 第二个线程访问,发现同步监视器被锁定,无法访问;
    3. 第一个线程访问完毕,解锁同步监视器;
    4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问;
// case 1
class Test extends Thread {
    private int sum = 10;
    
    /**
    * 对当前类属性同步直接使用synchronized,默认为this
    */
    @Override
    public synchronized void run() {
        if (sum <= 0) {
            return;
        }
        sout(Thread.currentThread().getName() + "is" + sum --);
    }
}

// case2
class Test extends Thread {
    private int sum = 10;
    private TestData testData;
    
    public Test(TestData testData) {
        this.testData = testdata;
    }
    
    /**
    * 如果想要同步的对象不是当前类则需要写同步代码块。
    */
    @Override
    public void run() {
        synchronized(testData) {
            if (testData.sum <= 0) {
            return;
        }
        testData.sum = testData.sum - sum;
        sout(Thread.currentThread().getName() + "is" + testData.sum);
        }
    }
}

JUC安全类集合

ArrayList() -> CopyOnWriteArrayList()

源码有两个关键字

  • transient
  • volatile

死锁

  • 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对象释放资源,都停止执行的情形。某一个同步块同时拥有两个以上对象的锁时,就可能会发生死锁的问题。
// 死锁 
private void makeup() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(1000);
            synchronized (mirror) {
                System.out.println(this.girlName + "获得镜子的锁");
            }
            }
        }
        if (choice == 1) {
            synchronized (mirror) {
                System.out.println(this.girlName + "获得镜子的锁");
                Thread.sleep(1000);
            synchronized (lipstick) {
                System.out.println(this.girlName + "获得口红的锁");
            }
            }
        }
    } 
// 解决
private void makeup() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(1000);
            }
            synchronized (mirror) {
                System.out.println(this.girlName + "获得镜子的锁");
            }
        }
        if (choice == 1) {
            synchronized (mirror) {
                System.out.println(this.girlName + "获得镜子的锁");
                Thread.sleep(1000);
            }
            synchronized (lipstick) {
                System.out.println(this.girlName + "获得口红的锁");
            }
        }
    }
  • 产生死锁的四个必要条件:
    1. 互斥条件:一个资源每次只能被一个进程使用;
    2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
    3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺;
    4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;

Lock(锁)

  • 从JDK1.5开始,Java提供了更强大的线程同步机制-----通过显示定义同步锁对象来实现同步。同步锁使用Locak对象充当
class TestThread implements Runnable {
    ReentrantLock reentrantLock = new ReentrantLock();
    @Override
    public void run() {
        reentrantLock.lock(); // 加锁
        reentrantLock.unlock(); // 解锁
    } 
}

  • synchronized 和 Lock的对比
    1. Lock是显示锁(手动开启和手动关闭,别玩及关闭锁),synchronized是隐式锁,出了作用域自动释放
    2. Lock只有代码块,synchronized有代码块锁和方法锁
    3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性

线程通讯

  • 应用场景:生产者和消费者问题
    • 假设厂库中只能存放意见产品,生产者将生产出来的产品放入厂库,消费者将厂库中产品取走消费
    • 如果厂库中没有产品,则生产者将产品放入厂库,否则停止生产并等待,知道厂库中的产品被消费者取走为止
    • 如果厂库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到厂库中再次放入产品为止
  • 线程通讯-分析
    • 对于生产者,没有生产产品之前,要通知消费者等待,而生产类产品之后,又需要马上通知消费者消费
    • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
    • 在生产者消费者问题中,仅有synchronized是不够的
      • synchronized可阻止并发更新同一个资源,实现了同步
      • synchronized不能用来实现不同线程之间的消息传递(通信)

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


// 缓冲区
class SynContainer {
    // 需要一个容器大小
    Chicken[] chickens = new Chicken[10];
    // 容器计数器
    int count = 0;
    
    // 生产者放入产品
    public synchronized void push(Chicken chicken) {
        // 如果容器满了,就需要等待消费者消费
        if (count == chickens.length) {
            try {
                this.wait();
            } catch (InterruptedExceptioin e) {
                e.printStackTrace();
            }
        }
        // 如果没有满,我们就需要丢入产品
        chickens[count] = chicken;
        count ++;
        
        // 可以通知消费者
        this.notifyAll();
    }
    
    // 消费者消费产品
    public synchronized Chicken pop() {
        // 判断能否消费
        if (count == 0) {
            // 等待生产者生产,消费者等待
             try {
                this.wait();
            } catch (InterruptedExceptioin e) {
                e.printStackTrace();
            }
        }
        
        // 如果可以消费
        count --;
        Chicken chicken = chickens[count];
        
        // 吃完了通知生产者生产
        this.notifyAll();
    }
}