Java多线程随笔记

        创建线程的几种方式

        1. 继承Thread类

        2. 实现Runnable接口

        3. 实现Callable接口( JDK1.5>= )

        4. 线程池方式创建

采用实现Runnable、Callable接口的方式创建线程的优缺点

        优点:线程类只是实现了Runnable或者Callable接口,还可以继承其他类。这种方式下,多个线程 可以共享一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想。

        缺点:编程稍微复杂一些,如果需要访问当前线程,则必须使用 Thread.currentThread() 方法

采用继承Thread类的方式创建线程的优缺点

优点:编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this即可获取当前线程

缺点:因为线程类已经继承了Thread类,Java语言是单继承的,所以就不能再继承其他父类了。

        sleep()和wait() 有什么区别?

        对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类 中的。

        sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持 者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象 锁。

        当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。

        volatile 是什么

        一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语 义

        1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对 其他线程来说是立即可见的,volatile关键字会强制将修改的值立即写入主存。

        2)禁止进行指令重排序。

        什么是线程安全

        我给出一个个人认为解释地最好的:如果你的代码 在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。

        Thread类中的yield方法有什么作用?

        Yield方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。它是一个静态方法 而且只保证当前线程放弃CPU占用而不能保证使其它线程一定能占用CPU,执行yield()的线程有可 能在进入到暂停状态后马上又被执行。

        说一说自己对于 synchronized 关键字的了解

        synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它 修饰的方法或者代码块在任意时刻只能有一个线程执行。 另外,在 Java 早期版本中, synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一 个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核 态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较 大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优 化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

        常用的线程池有哪些?

        newSingleThreadExecutor:创建一个单线程的线程池,此线程池保证所有任务的执行顺序按 照任务的提交顺序执行。

        newFixedThreadPool:创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线 程达到线程池的最大大小。

        newCachedThreadPool:创建一个可缓存的线程池,此线程池不会对线程池大小做限制,线 程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

        newScheduledThreadPool:创建一个大小无限的线程池,此线程池支持定时以及周期性执行 任务的需求。

        newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任 务的需求。

        线程池的理解

        第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

        第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

        第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降 低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

        Java程序是如何执行的

        我们日常的工作中都使用开发工具(IntelliJ IDEA 或 Eclipse 等)可以很方便的调试程序,或者是通 过打包工具把项目打包成 jar 包或者 war 包,放入 Tomcat 等 Web 容器中就可以正常运行了,但你 有没有想过 Java 程序内部是如何执行的?其实不论是在开发工具中运行还是在 Tomcat 中运行, Java 程序的执行流程基本都是相同的,它的执行流程如下:

        先把 Java 代码编译成字节码,也就是把 .java 类型的文件编译成 .class 类型的文件。这个过程 的大致执行流程:Java 源代码 -> 词法分析器 -> 语法分析器 -> 语义分析器 -> 字符码生成器 -> 最终生成字节码,其中任何一个节点执行失败就会造成编译失败;

        把 class 文件放置到 Java 虚拟机,这个虚拟机通常指的是 Oracle 官方自带的 Hotspot         JVM; Java 虚拟机使用类加载器(Class Loader)装载 class 文件;

        类加载完成之后,会进行字节码效验,字节码效验通过之后 JVM 解释器会把字节码翻译成机器 码交由操作系统执行。但不是所有代码都是解释执行的,JVM 对此做了优化,比如,以 Hotspot 虚拟机来说,它本身提供了 JIT(Just In Time)也就是我们通常所说的动态编译器, 它能够在运行时将热点代码编译为机器码,这个时候字节码就变成了编译执行。Java 程序执行 流程图如下:

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

        1. 互斥条件:一个资源每次只能被一个线程使用

        2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放

        3. 不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺

        4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系

        如何避免死锁?

        指定获取锁的顺序,举例如下:

        1. 比如某个线程只有获得A锁和B锁才能对某资源进行操作,在多线程条件下,如何避免死锁?

        2. 获得锁的顺序是一定的,比如规定,只有获得A锁的线程才有资格获取B锁,按顺序获取锁就可 以避免死锁!!!

        乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

        悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候 都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。 传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做 操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。

        乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是 在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。 乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机 制,其实都是提供的乐观锁。 在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实 现的。

什么是阻塞队列?阻塞队列的实现原理是什么?如何使用阻 塞队列来实现生产者-消费者模型?

        阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。

        这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元 素的线程会等待队列可用。 阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿 元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

        JDK7提供了7个阻塞队列。分别是: ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。 LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。 PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。 DelayQueue:一个使用优先级队列实现的无界阻塞队列。 SynchronousQueue:一个不存储元素的阻塞队列。 LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。 LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

        Java 5之前实现同步存取时,可以使用普通的一个集合,然后在使用线程的协作和线程同步可以实 现生产者,消费者模式,主要的技术就是用好,wait ,notify,notifyAll,sychronized这些关键字。

        而 在java 5之后,可以使用阻塞队列来实现,此方式大大简少了代码量,使得多线程编程更加容易, 安全方面也有保障。 BlockingQueue接口是Queue的子接口,它的主要用途并不是作为容器,而是作为线程同步的的工 具,因此他具有一个很明显的特性,当生产者线程试图向BlockingQueue放入元素时,如果队列已 满,则线程被阻塞,当消费者线程试图从中取出一个元素时,如果队列为空,则该线程会被阻塞, 正是因为它所具有这个特性,所以在程序中多个线程交替向BlockingQueue中放入元素,取出元 素,它可以很好的控制线程之间的通信。

         阻塞队列使用最经典的场景就是socket客户端数据的读取和解析,读取数据的线程不断将数据放入 队列,然后解析线程不断从队列取数据解析。

        搞几个基础的小练习 干说不练可不行

package com.sinosoft.claim;

/**
 * 利用线程顺序打印出 t1 t2 t3
 */
public class ThreadTest {
    public static void main(String[] args) {
        //t1
        Thread thread = new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("t1");
                    }
                }
        );

        Thread threadt2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    thread.join();

                }catch(Exception e) {

                }
                System.out.println("t2");
            }
        });

        Thread threadt3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    threadt2.join();

                }catch(Exception e) {

                }
                System.out.println("t3");
            }
        });
        threadt3.start();
        threadt2.start();
        thread.start();
    }
}

假设有一个银行账户,初始余额为0。现在有两个线程,一个是存款线程,负责向账户中存入一定数量的金额;另一个是取款线程,负责从账户中取出一定数量的金额。要求实现以下功能:

package com.sinosoft.claim;

/**
 * 假设有一个银行账户,初始余额为50。现在有两个线程,
 * 一个是存款线程,负责向账户中存入一定数量的金额;另一个是取款线程,负责从账户中取出一定数量的金额。要求实现以下功能:
 */
public class ThreadTest1 {
    private double balance;

    public ThreadTest1() {
        this.balance = 50;
    }

    public synchronized void deposit(double amount) {
        balance += amount;
        System.out.println("Deposit: " + amount);
        notify(); // 唤醒等待的线程
    }

    public synchronized void withdraw(double amount) {
        try {
            if (balance < amount) {
                System.out.println("Insufficient balance. Waiting...");
                wait(); // 进入等待状态
            }
            balance -= amount;
            System.out.println("Withdraw: " + amount);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        ThreadTest1 account = new ThreadTest1();

        // 存款线程
        Thread depositThread = new Thread(() -> {
            account.deposit(100);
        });

        // 取款线程
        Thread withdrawThread = new Thread(() -> {
            account.withdraw(110);
        });

        withdrawThread.start();
        depositThread.start();


    }
}

 来了来了他俩了 生产者消费者问题 

package com.sinosoft.claim;

import java.util.LinkedList;

public class ProducerConsumerSolution {
    private LinkedList<Integer> buffer = new LinkedList<>();
    private int capacity = 5;

    public void produce() throws InterruptedException {
        int value = 1;
        while (true) {
            synchronized (this) {
                while (buffer.size() == capacity) {
                    wait();
                }

                System.out.println("存货啦: " + value);
                buffer.add(value++);
                notify();

                // Sleep for random time before producing next item
                Thread.sleep((int) (Math.random() * 1000));
            }
        }
    }

    public void consume() throws InterruptedException {
        while (true) {
            synchronized (this) {
                while (buffer.isEmpty()) {
                    wait();
                }

                int value = buffer.removeFirst();
                System.out.println("取货啦: " + value);
                notify();

                // Sleep for random time before consuming next item
                Thread.sleep((int) (Math.random() * 1000));
            }
        }
    }

    public static void main(String[] args) {
        ProducerConsumerSolution solution = new ProducerConsumerSolution();

        Thread producerThread = new Thread(() -> {
            try {
                solution.produce();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread consumerThread = new Thread(() -> {
            try {
                solution.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        producerThread.start();
        consumerThread.start();
    }
}

不要问这个程序为什么不停 因为他们热爱工作!!!!

  • 46
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值