博客背景:最近在做项目,经常会使用到GitHub,常用的需求包含:拉取远端GitHub文件保存到本地,从本地上传文件到GitHub仓库等等...命令经常会忘,因此整理一篇博客,
一、从GitHub上拉取文件到本地
使用命令:git clone
git clone 仓库地址
操作步骤:
第1步:进入到本地机中想要存储文件的目录中,右键,点击“Git Bash Here”
第2步:在GitHub上找到想要拉取的项目,点击Code,复制仓库地址,在git bash中输入"git clone 仓库地址"
如果出现下面错误:
可以输入下面代码,关闭证书校验:
git config --global http.sslVerify false
第3步:成功将资源从GitHub下载到本地文件夹,检查项目的完整性
二、从本地上传文件到GitHub
使用命令:git -c http.sslVerify=false push -u 仓库地址 分支名称
操作步骤:
1.GitHub上创建新Repository
在GitHub上创建名为springcloud-config的新Repository:
复制HTTPS地址,比如:git@github.com:pbjlovezjy/springcloud-config.git
2.推送本地仓库的项目到GitHub
在spring-config文件夹下的空白处单击右键,进入Git Bash
然后输入:git -c http.sslVerify=false push -u 仓库地址 分支名称
然后会弹出一个界面,选择 sign in with your browser,选择授权git系统,然后输入密码即可:
可以直接将文件夹内容提交至仓库:
三、更新GitHub仓库
假如我们现在删掉了本地文件夹里的README.md,我们想把这一修改同步到GitHub上,要如何做呢?
首先我们输入:git add . (注意最后有个点)将所有修改的内容放入缓冲区:
然后输入:git commit -m '随便填写'
即可实现GitHub同步本地文件夹的内容。
假设我们现在修改了GitHub上的内容,添加了一个README.mdd文件夹:
此时,我们本地没有README.md文件,假如我们想同步远程仓库和本地:
只需要输入:git -c http.sslVerify=false pull --rebase origin master
文件就被我们同步到了本地:
四、创建秘钥并设置
第3步: 如果报错,需要创建并设置秘钥,见第四节
解释:如果想从GitHub的仓库中拉取文件到本地,需要上报自己的用户名、邮箱、密码等信息,这些信息会被存储为秘钥,作为访问GitHub的权限。
假如GitHub的用户名为:aabbccdd
密码:11223344
邮箱:12345@qq.com
第1步:打开Git bash设置用户名和邮箱
git config --global user.name "填你GitHub的用户名"
git config --global user.email "填你自己GitHub注册的邮箱"
示例如下:
git config --global user.name "aabbccdd"
git config --global user.email "12345@qq.com"
第2步:在Git Bash中输入以下代码,创建密钥:
1. 生成新的SSH密钥对(这一步是生成私钥):ssh-keygen -t rsa -b 4096 -C "你的邮箱"
示例:sh-keygen -t rsa -b 4096 -C "12345@qq.com"
2. 启动 SSH agent:eval "$(ssh-agent -s)"
3.添加你的私钥到SSH agent中:ssh-add ~/.ssh/id_rsa
4.复制公钥到剪贴板:clip < ~/.ssh/id_rsa.pub
公钥如下:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCzEFpg4hNhzVRdmq/9At/hEealmjiprYUWFfkvuGSdU9PFQokWTLwOeR6z8yVjn0VrDFIGIyQ9q805Clcev1VqGN9CAy50adBTamW3mV1U8twTWS7VNc89WidA5OK5HyfL4xkuSg/NaOtOjj0m7+31+w3JANgavFjQaUi03peuGaCnp3xVsFWFO1r1msNTw0QwRnAPERwA3/w1jT7gNcJ/wS3yHJiNbKpwbJcLKAUJUFsijJ80C25b4/0jSjKyBl+W9ctDMW/2gTUMoMCFXiXgVHhdLJcbyHaqwC3xXP0hi8K20Yaqr4f8vxxEw3Wnfn/5Dzj4fZ24saMwff4RV8HwJ8BrQx+4la6RQhLYrWWCay6AA3+2jawhW/eW6FFnnywr4GnRnVXcc0F3iLlnEfPW5qOdLrG903vjL9xYe3bty8CTWRsF1ruTI2PjD06Om0hPK9mvTCPnoDzBktlsVX907qNaCVqjbwpfgtHae0fEDIWZXCkBaqaYvOm9wL5cPD0F6R9CqDs6BtrI4HSrh9tUvBKO9xHnFLAF2BLHuYcrzU62M1KGzLln4kPC9hlh8pMoxqQwkfccz3Z0F84b7q5ppg3PutoLfb+gLyCTfOTGKjuw3DWFaYPalGCYdQAPchsp8DylAnv4pecxGH79XnbZ46ExjkQ3znnotkPDqN/TIxfGaB7hQ== 3499045941@qq.com
第3步:打开GitHub粘贴密钥,完成关联:
点击右上角的头像,选择 "Settings":
在左侧菜单中选择 "SSH and GPG keys":
点击 "New SSH key":
在 "Title" 字段中,为你的密钥添加一个描述性的名称:
在 "Key" 字段中,粘贴你之前复制的公钥:
点击 "Add SSH key":
简单解释原理:私钥是存储在你自己本地电脑上的,公钥是放在GitHub上的,通过私钥来匹配公钥,使得本地机具有访问GitHub的权限。
五、使用IDEA拉取和推送
一、从Gitee上拉取文件到本地
二、从本地上传文件到Gitee
1.Gitee上创建新仓库
2.推送本地仓库的项目到Gitee
比如我现在想把JUC高并发这个文件夹下的JUCdemo项目上传到Gitee上。
1. 首先要进行Git的全局设置:
git config --global user.name "这里填你的用户名"
git config --global user.email "这里填你的邮箱"
2. 然后要创建git仓库:
选取一个本地git仓库所在的位置,可以是任意的文件夹。
mkdir 仓库名称
输入上面命令后会在当前文件夹下新建一个本地仓库,如下图:
3.仓库初始化
这一步包含下面3步:
进入仓库:
cd 仓库名
初始化仓库:
git init
创建README.md文件(可选)
touch README.md
4.上传文件到本地仓库
比如:我想要上传的是JUCdemo文件,我需要先把JUCdemo文件给复制到本地仓库里,如下图:
然后输入下面命令,这个命令是将当前文件夹下的内容全部存入到缓冲区:
git add .
最后输入下面这个命令,这个命令可以把缓冲区中的数据提交到本地仓库:
git commit -m "提交名称"
注意,此时只是将数据提交到仓库的历史记录中,可以通过上面设定的“提交名称”来找到每一次提交的记录。
5.设置远程仓库
设置远程仓库的地址,输入如下命令:
git remote add origin 远程仓库地址
这里的origin指向的是Gitee仓库的原始位置(URL),相当于远程仓库的坐标,通过这个坐标可以定位到远程仓库。
6.提交本地仓库数据到远程仓库
输入下面的命令,将本地仓库的内容提交到远程仓库:
git push origin master
分支在Git中用于隔离工作环境,不同的分支之间不会相互影响,能够独立工作。
上面这段代码的意思大概是:推送本地仓库的数据(git push -u)到远程仓库(名字为origin)的某个分支(默认分支名称)。
总结一下:想把本地的项目推送到Gitee上,流程:首先要把项目保存到本地仓库,然后再从本地仓库推送到远程仓库。
三、更新Gitee仓库
创建分支
git branch "分支名称"
推送本地仓库数据到分支:
git push -u origin "分支名称"
四、使用IDEA拉取和推送
P226 ScheduledThreadPoolExecutor 定时执行
ScheduledThredPoolExecutor可以定时执行任务。
假如设置每隔1秒执行一次,因为休眠时间是2秒,所以会2秒执行一次。
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
log.debug("start...");
pool.scheduleAtFixedRate(()->{
log.debug("running...");
sleep(2);
},2,1,TimeUnit.SECONDS);
}
}
每次任务开始的时间是从上一次任务结束时间点开始,睡眠时间+延迟时间:
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
log.debug("start...");
pool.scheduleWithFixedDelay(()->{
log.debug("running...");
sleep(2);
},1,1,TimeUnit.SECONDS);
}
}
P227 正确处理线程池异常
方法1:用try-catch块捕获。
方法2:用submit方法然后返回为一个Future对象
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
Future<Boolean> f = pool.submit(() -> {
log.debug("task1");
int i = 1 / 0;
return true;
});
log.debug("result:{}", f.get());
}
}
如果没有异常返回true,有异常返回错误。
P228 线程池应用 定时任务
使用now来计算当前时间,使用time来计算目标时间,使用initialDelay来计算距离第1次执行的时间,period用来设置每次执行的时间间隔。
public class Test3 {
public static void main(String[] args) {
//获取当前时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
//获取周四时间
LocalDateTime time = now.withHour(12).withMinute(3).withNano(0).with(DayOfWeek.SUNDAY);
//如果当前时间>本周周四,必须找到下周周四
if(now.compareTo(time)>0){
time = time.plusWeeks(1);
}
System.out.println(time);
long initialDelay = Duration.between(now,time).toMillis();
long period = 1000;
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
pool.scheduleAtFixedRate(()->{
System.out.println("running...");
},initialDelay,period, TimeUnit.MILLISECONDS);
}
}
P229 线程池应用 定时任务 测试
下图是效果:
P230 tomcat 线程池
LiMitLatch:用来限流,控制最大连接数,避免服务器被压垮。
Acceptor:负责接收新的socket连接
Poller:监听Socket channel连接上是否有可读的事件发生。一旦可读,封装一个任务对象socketProcessor提交给Executor线程池处理。
Executor线程池中的工作线程(worker)最终负责处理请求。
Tomcat线程池扩展了ThreadPoolExecutor,行为稍有不同:
P231 tomcat 线程池 配置
P232 forkjoin 概念
P233 forkjoin 使用
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinPool pool = new ForkJoinPool(4);
System.out.println(pool.invoke(new MyTask(5)));
}
}
@Slf4j(topic = "c.MyTask")
//1~n之间整数的和
class MyTask extends RecursiveTask<Integer>{
private int n;
public MyTask(int n) {
this.n = n;
}
@Override
public String toString(){
return "(" + n + ")";
}
@Override
protected Integer compute(){
//终止条件
if(n==1) {
log.debug("join(){}",n);
return 1;
}
MyTask t1 = new MyTask(n - 1);
t1.fork();//让一个线程去执行此任务
log.debug("fork(){}+{}",n,t1);
int result = n + t1.join();
log.debug("join(){}+{}",n,result);
return result;
}
}
中间会用到多线程来执行任务的拆分和合并:
P234 forkjoin 任务拆分优化
P235 aqs 概述
aqs的全称是:AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架。
其它同步器工具都是作为aqs的实现子类。
提供阻塞式锁,更类似于悲观锁。
特点:
1.用state属性来表示资源的状态,分为独占模式和共享模式,独占式式只有一个线程能够访问资源,共享式式允许多个线程访问资源。
2.提供了基于FIFO的等待队列,类似于Monitor的EntryList。
3.条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Monitor的WaitSet。
子类主要实现了下面这些方法:
tryAcquire
tryRelease
tryAcquireShared
tryReleaseShared
isHeldExclusively
P236 aqs 自定义锁
不可重入锁,本线程加了锁,下次还是同一个线程来了不能重复加。
//自定义锁(不可重入锁)
class MyLock implements Lock{
//独占锁,同步器类
class Mysync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0,1)){//其它线程可能也想修改状态
//加上了锁,并设置owner为当前线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState()==1;
}
public Condition newCondition(){
return new ConditionObject();
}
}
private Mysync sync = new Mysync();
public MyLock() {
super();
}
@Override //加锁,加锁不成功会放入队列等待
public void lock() {
sync.acquire(1);
}
@Override //加锁,可打断
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override //尝试加锁,只会尝试一次
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override //尝试加锁,带超时时间
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));
}
@Override //解锁
public void unlock() {
sync.release(1);
}
@Override //创建条件变量
public Condition newCondition() {
return sync.newCondition();
}
}
P237 aqs 自定义锁 测试
//自定义锁(不可重入锁)
class MyLock implements Lock{
//独占锁,同步器类
class Mysync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0,1)){//其它线程可能也想修改状态
//加上了锁,并设置owner为当前线程
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState()==1;
}
public Condition newCondition(){
return new ConditionObject();
}
}
private Mysync sync = new Mysync();
public MyLock() {
super();
}
@Override //加锁,加锁不成功会放入队列等待
public void lock() {
sync.acquire(1);
}
@Override //加锁,可打断
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override //尝试加锁,只会尝试一次
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override //尝试加锁,带超时时间
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));
}
@Override //解锁
public void unlock() {
sync.release(1);
}
@Override //创建条件变量
public Condition newCondition() {
return sync.newCondition();
}
}
P238 reentrantlock 加锁成功流程
P239 reentrantlock 枷锁失败流程
P240 reentrantlock 解锁竞争成功流程
下面是释放锁release的流程:
P241 reentrantlock 解锁竞争失败流程
P242 reentrantlock 锁重入原理
P243 reentrantlock 可打断原理
不可打断模式:即使被打断,仍会驻留在AQS队列中,等待获得锁后方能继续运行(是继续运行,只是打断标记被设为true)。
可打断模式:被打断之后直接抛出异常,不会再进入for循环。
P244 reentrantlock 公平锁原理
下图是非公平锁实现,任意节点都能竞争:
下图是公平锁实现,只有头节点的后一个节点能竞争:
P245 reentrantlock 条件变量 await
调用await,会进入addConditionWaiter流程,创建新的Node状态为-2
fullyRelease会把所有的锁都释放掉:
P246 reentrantlock 条件变量 signal
doSignal中transferForSignal返回true则转移成功,不会继续循环。如果转移失败的原因可能是线程放弃了对锁的竞争,因为超时等原因,就不会被加入队列。
waitStatus改为-1,表示它有责任去唤醒链表的下一个元素。
P247 reentrantreadwritelock 使用
ReentrantReadWriteLock支持重入的读写锁
当读操作远远高于写操作时,这时候要使用读写锁让读-读可以并发,提高性能。
但是写-写和读-写是互斥的。
加上读锁读取,隔了100毫秒后开始获取写锁,但是读休眠1秒,一秒后读锁释放,才能获取到写锁开始写入:
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) throws InterruptedException {
DataContainer dataContainer = new DataContainer();
new Thread(()->{
dataContainer.read();
},"t1").start();
Thread.sleep(100);
new Thread(()->{
dataContainer.write();
},"t1").start();
}
}
@Slf4j(topic = "c.DataContainer")
class DataContainer{
private Object data;
private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock r = rw.readLock();
private ReentrantReadWriteLock.WriteLock w = rw.writeLock();
public Object read(){
log.debug("获取读锁...");
r.lock();
try {
log.debug("读取");
sleep(1);
return data;
} finally {
log.debug("释放读锁");
r.unlock();
}
}
public void write(){
log.debug("获取写锁...");
w.lock();
try {
log.debug("写入");
} finally {
log.debug("释放写锁");
w.unlock();
}
}
}
P248 reentrantreadwritelock 注意事项
读锁不支持条件变量。
1.重入时不支持升级:比如:不允许持有读锁的情况下去获取写锁,会导致获取写锁永久等待。
2.重入时支持降级:比如:持有写锁的情况下去获取读锁。
一开始持有读锁,在获取写锁前要释放读锁。
写锁可以降级为读锁,最后再释放锁。
P249 reentrantreadwritelock 应用之缓存
P250 reentrantreadwritelock 应用之缓存 问题分析
先清缓存,再查数据库设置缓存值,如果后面有新数据写入数据库,缓存中一直是旧值。
要求先更新数据库再清空缓存:
P251 reentrantreadwritelock 应用之缓存 实现
加写锁,保证原子:
双重检查,先查缓存再查数据库:
P252 reentrantreadwritelock 应用之缓存 补充
P253 reentrantreadwritelock 原理 t1 w.lock
写锁状态占state的低16位,读锁使用的是state的高16位。
P254 reentrantreadwritelock 原理 t2 r.lock
P255 reentrantreadwritelock 原理 t3 r.lock & t4 w.lock
P256 reentrantreadwritelock 原理 t1 w.unlock
P257 reentrantreadwritelock 原理 t1 w.unlock
P258 reentrantreadwritelock 原理 t2 r.unlock t3 r.unlock
P259 stampedlock 作用
P260 stampedlock 演示
乐观读锁并没有加读锁,只是验证戳,只有当戳被更改过才会真正加读锁。可以提升并发读的性能。
缺点:不支持条件变量,不支持可重入。
P261 semaphore 作用
Semaphore是信号量,用来限制能同时访问共享资源的线程上限。
P262 semaphore 演示
@Slf4j(topic = "c.Test1")
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("running...");
sleep(1);
log.debug("end...");
}finally {
semaphore.release();
}
}).start();
}
}
}
限制访问共享资源的线程数: