5. Java高级特性增强笔记

  1. Java多线程基本知识
    1.1. 进程
    它是内存中的一段独立的空间,可以负责当前应用程序的运行。当前这个进程负责调度当前程序中的所有运行细节。
    1.2. 线程
    它是位于进程中,负责当前进程中的某个具备独立运行资格的空间。
    进程是负责整个程序的运行,而线程是程序中具体的某个独立功能的运行。一个进程中至少应该有一个线程。
    1.3. 多线程
    1.3.1. 概述
    在一个进程中,我们同时开启多个线程,让多个线程同时去完成某些任务(功能)。多线程的目的:提高程序的运行效率。
    1.3.2. 多线程原理
    cpu在线程中做时间片的切换。
    其实真正电脑中的程序的运行不是同时在运行的。CPU负责程序的运行,而CPU在运行程序的过程中某个时刻点上,它其实只能运行一个程序。而不是多个程序。而CPU它可以在多个程序之间进行高速的切换。而切换频率和速度太快,导致人的肉看看不到。
    每个程序就是进程, 而每个进程中会有多个线程,而CPU是在这些线程之间进行切换。
    了解了CPU对一个任务的执行过程,我们就必须知道,多线程可以提高程序的运行效率,但不能无限制的开线程。
    1.3.3. Python中的伪多线程,注意要和Java中的区分(*****)
    Python中的多线程没有真正实现多现程! 为什么这么说,我们了解一个概念,全局解释器锁(GIL)。
    Python代码的执行由Python虚拟机(解释器)来控制。
    Python在设计之初就考虑要在主循环中,同时只有一个线程在执行,就像单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但任意时刻,只有一个程序在CPU中运行。
    同样地,虽然Python解释器可以运行多个线程,只有一个线程在解释器中运行。

对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。在多线程环境中,Python虚拟机按照以下方式执行。
1.设置GIL。
2.切换到一个线程去执行。
3.运行。
4.把线程设置为睡眠状态。
5.解锁GIL。
6.再次重复以上步骤。
python 每执行100个字节码,GIL锁就会解锁一次,让其它线程执行,所以,python多线程环境,是交替执行,上下文切换,并没有同一时刻执行代码.
1.3.4. 线程实现的方式
a. 继承Thread
b. 实现Runnable接口
c. 实现Callable接口
2. Java同步关键词
2.1. synchronized
synchronized是java中的一个关键字,也就是说是Java语言内置的特性。
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
  a. 获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
b. 线程执行发生异常,此时JVM会让线程自动释放锁。
2.2. lock
lock和synchronized的区别
Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
  Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized
代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导
致出现死锁现象。
2.3. Lock类的分析
2.3.1. java.util.concurrent.locks包下常用的类
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
}
2.3.2. 四个获取锁方法的区别:
  a. lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。

b.	tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。

c. tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

d. lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,
假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程(很少用)。
  注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。
  因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。
  而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
2.4. ReentrantLock
ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法,ReentrantLock,意思是“可重入锁”。
2.5. ReentrantReadWriteLock
ReentrantReadWriteLock–顾名思义是可重入的读写锁,允许多个读线程获得ReadLock,但只允许一个写线程获得WriteLock
实际生产环境中的缓存案例

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CacheDemo {
/**
* 缓存器,这里假设需要存储1000左右个缓存对象,按照默认的负载因子0.75,则容量=750,大概估计每一个节点链表长度为5个
* 那么数组长度大概为:150,又有雨设置map大小一般为2的指数,则最近的数字为:128
*/
private Map<String, Object> map = new HashMap<>(128);
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {

}
public Object get(String id){
    Object value = null;
    rwl.readLock().lock();//首先开启读锁,从缓存中去取
    try{
           if(map.get(id) == null){  //如果缓存中没有释放读锁,上写锁
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try{
                if((map.get(id) == null){ //防止多写线程重复查询赋值
                    value = "redis-value";  //此时可以去数据库中查找,这里简单的模拟一下
                }
                rwl.readLock().lock(); //加读锁降级写锁,不明白的可以查看上面锁降级的原理与保持读取数据原子性的讲解
            }finally{
                rwl.writeLock().unlock(); //释放写锁
            }
        }
    }finally{
        rwl.readLock().unlock(); //最后释放读锁
    }
    return value;
}

}

备注:读写锁的相关链接:https://www.cnblogs.com/liang1101/p/6475555.html?utm_source=itdadao&utm_medium=referral
2.6. lock和synchronized的选择
2.6.1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2.6.2. synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
2.6.3. Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
2.6.4. 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
2.6.5. Lock可以提高多个线程进行读操作的效率。在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
2.7. 其他一些经典同步案例
备注: 有条件阻塞Condition应用 链接:http://www.cnblogs.com/liang1101/p/6522375.html
备注: 信号灯Semaphore应用 链接:http://www.cnblogs.com/liang1101/p/6526039.html
备注: 到时计数器CountDownLatch应用 链接:https://www.cnblogs.com/liang1101/p/6526967.html
备注: 循环路障CyclicBarrier应用 链接:http://www.cnblogs.com/liang1101/p/6526121.html
3. 线程池
3.1. Single Thread Executor : 只有一个线程的线程池,因此所有提交的任务是顺序执行,
代码: Executors.newSingleThreadExecutor()
3.2. Cached Thread Pool : 线程池里有很多线程需要同时执行,老的可用线程将被新的任务触发重新执行,如果线程超过60秒内没执行,那么将被终止并从池中删除,
代码:Executors.newCachedThreadPool()
3.3. Fixed Thread Pool : 拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待,
代码: Executors.newFixedThreadPool(4)
在构造函数中的参数4是线程池的大小,你可以随意设置,也可以和cpu的核数量保持一致,获取cpu的数量int cpuNums = Runtime.getRuntime().availableProcessors();
3.4. Scheduled Thread Pool : 用来调度即将执行的任务的线程池,
代码:Executors.newScheduledThreadPool()
3.5. Single Thread Scheduled Pool : 只有一个线程,用来调度执行将来的任务,代码:Executors.newSingleThreadScheduledExecutor()
3.6. 线程池的使用
所谓给线程池提交任务,就是:
3.6.1. 你将任务(业务处理逻辑)写到一个runnable或者callable的执行方法<run() | call()>
3.6.2. 将这个runnable对象提交给线程池即可
3.7. Java线程池(Callable+Future模式)
链接:https://www.cnblogs.com/myxcf/p/9959870.html
4. JAVA JMS技术
4.1. JMS(Java Messaging Service)是Java平台上有关面向消息中间件(MOM)的技术规范,它便于消息系统中的Java应用程序进行消息交换,并且通过提供标准的产生、发送、接收消息的接口简化企业应用的开发,翻译为Java消息服务。
4.2. Java消息服务应用程序结构支持两种模型
点对点队列模型
发布者/订阅者模型
4.3. 常用的JMS实现
Apache ActiveMQ
5. Java反射
/**

  • getResourceAsStream这个方法可以获取到一个输入流,这个输入流会关联到name所表示的那个文件上。
    */

personClass.getResourceAsStream("/log4j.properties")

  1. 动态代理
    在不修改原业务的基础上,基于原业务方法,进行重新的扩展,实现新的业务。
    =====================================================================================================================================================================
    原业务接口IBoss
    public interface IBoss {//接口
    int yifu(String size);
    }

原业务实现类
public class Boss implements IBoss{
public int yifu(String size){
System.err.println(“天猫小强旗舰店,老板给客户发快递----衣服型号:”+size);
//这件衣服的价钱,从数据库读取
return 50;
}
public void kuzi(){
System.err.println(“天猫小强旗舰店,老板给客户发快递----裤子”);
}
}

原业务调用
public class SaleAction {
@Test
public void saleByBossSelf() throws Exception {
IBoss boss = new Boss();
System.out.println(“老板自营!”);
int money = boss.yifu(“xxl”);
System.out.println(“衣服成交价:” + money);
}

}

代理类
public static IBoss getProxyBoss(final int discountCoupon) throws Exception {
Object proxedObj = Proxy.newProxyInstance(Boss.class.getClassLoader(),
new Class[] { IBoss.class }, new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
# 在这里可以根据method的名字判断调用的是哪个方法
Integer returnValue = (Integer) method.invoke(new Boss(),
args);// 调用原始对象以后返回的值
return returnValue - discountCoupon;
}
});
return (IBoss)proxedObj;
}
}

新业务调用
public class ProxySaleAction {
@Test
public void saleByProxy() throws Exception {
IBoss boss = ProxyBoss.getProxyBoss(20);// 将代理的方法实例化成接口
System.out.println(“代理经营!”);
int money = boss.yifu(“xxl”);// 调用接口的方法,实际上调用方式没有变
System.out.println(“衣服成交价:” + money);
}
}

1、书写代理类和代理方法,在代理方法中实现代理Proxy.newProxyInstance
2、代理中需要的参数分别为:被代理的类的类加载器soneObjectclass.getClassLoader(),被代理类的所有实现接口new Class[] { Interface.class },句柄方法new InvocationHandler()
3、在句柄方法中复写invoke方法,invoke方法的输入有3个参数Object proxy(代理类对象), Method method(被代理类的方法),Object[] args(被代理类方法的传入参数),在这个方法中,我们可以定制化的开发新的业务。
4、获取代理类,强转成被代理的接口
5、最后,我们可以像没被代理一样,调用接口的认可方法,方法被调用后,方法名和参数列表将被传入代理类的invoke方法中,进行新业务的逻辑流程。

  1. Python装饰器
    python装饰器(fuctional decorators)就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名(或类名)的情况下,给函数增加新的功能。
    这个函数的特殊之处在于它的返回值也是一个函数,这个函数是内嵌“原“”函数的函数。
    链接:https://www.cnblogs.com/yuzhanhong/p/9180212.html
  2. Socket编程 *****
    8.1. 传统socket编程(BIO 同步阻塞)
    基本编程模型:
    服务端
    核心API ServerSocket
    流程: 先创建一个服务,然后绑定在服务器的IP地址和端口
    等待客户端的连接请求
    收到连接请求后,接受请求,建立了一个TCP连接
    从建立的连接中获取到socket输入、输出流(两个流都是同步阻塞的)
    通过两个流进行数据的交互

客户端
核心API Socket
流程: 先向服务端请求连接
一旦被服务器接受,连接就创建好了
从tcp连接中获取socket输入、输出流
通过两个流进行数据的交互
8.2. NIO(同步非阻塞)原理
通过selector(选择器),管理所有的IO事件:
客户端的connection事件
服务端的accept事件
客户端和服务端的读写事件

selector如何进行事件管理?
当IO事件注册给我们的选择器的时候,选择器会给他们分配一个key(可以简单的理解成一个事件的标签)
当IO事件就绪后,可以通过key值来找到相应的管道channel,然后通过管道发送数据和接收数据等操作

数据缓冲区:
通过bytebuffer来实现,提供了很多读写的方法 put() get()
流行框架:netty

8.3. AIO异步非阻塞IO(待续)
9. 轻量级RPC框架开发(待续)

发布了85 篇原创文章 · 获赞 12 · 访问量 3734

猜你喜欢

转载自blog.csdn.net/fanjianhai/article/details/103800193
今日推荐