文章目录

  • 前言
  • 什么是死锁
  • 发生死锁的条件
    • 发生死锁的必要条件
  • 如何定位修复和避免
    • 如何定位
      • 1、jstack
      • 2、ThreadMXBean
    • 如何修复
    • 如何避免
  • 活锁与饥饿
    • 活锁
    • 饥饿
  • 一些面试题
  • 总结

前言

前面几篇博客,算是对java多线程基础有了一个简单的介绍,但是没有总结一下死锁,针对死锁的问题,这一篇博客也详细总结一下

什么是死锁

死锁是发生在并发中,当两个(或者多个)线程(或者进程)相互持有对方所需要的资源,又不主动释放,导致所有请求都无法继续前进,导致程序陷入无尽的阻塞。

一个必然发生死锁的代码实例

/*** autor:liman* createtime:2021/9/23* comment:死锁示例*/
public class SimpleDeadLockDemo implements Runnable{int flag = 1;static Object object01 = new Object();static Object object02 = new Object();public static void main(String[] args) {SimpleDeadLockDemo deadLockDemo01 = new SimpleDeadLockDemo();SimpleDeadLockDemo deadLockDemo02 = new SimpleDeadLockDemo();deadLockDemo01.flag=1;deadLockDemo02.flag=0;Thread thread01 = new Thread(deadLockDemo01);Thread thread02 = new Thread(deadLockDemo02);thread01.start();thread02.start();}@Overridepublic void run() {System.out.println("当前的flag="+flag);if(1==flag){synchronized (object01){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (object02){System.out.println("escape dead lock,flag:1");}}}else{synchronized (object02){try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (object01){System.out.println("escape dead lock,flag:1");}}}}
}

上述两个代码其实就是两个线程,互相持有对方所需要的锁,又没有主动释放,导致都无法进一步运行下去

上述代码的实际运行交互图如下所示

如果多个线程之间的依赖关系出现环形,存在环路的锁的依赖,那么也可能发生死锁。 比如如下所示

线程1拥有锁A,想要获取锁B;线程2拥有锁B,想要获取锁C;线程3拥有锁C,想要获取锁A。这样三者就形成了一资源依赖环路,也可能会发生死锁。

死锁在不同系统中的影响也是不同的,因为这取决于不同系统对死锁的处理能力。比如数据中,其本身有对死锁有检测和处理的能力。但是在JVM中,并没有自动处理死锁的能力,这是JVM处于安全性的考虑。

死锁不一定会发生,但是如果没有从代码层面上完全避免死锁,则随着时间的推移,死锁一定会发生,一旦发生,其影响范围较广,极有可能造成系统崩溃。在我们应用开发流程中,压力测试其实也不一定能复现死锁的问题

发生死锁的条件

在熟悉了死锁是什么,以及死锁的危害之后,我们再在相关实例的基础上,探讨一下发生死锁的必要条件,先看一下几个实例

1、两人转账的实例,这种和死锁的实例逻辑上没有太大区别

为了安全,其实转账的时候,也是需要获取出账的锁和入账的锁。

/*** autor:liman* createtime:2021-10-30* comment:转账 会产生死锁的实例*/
public class TransferAccount implements Runnable {int flag = 1;static Account fromAccount = new Account(500);static Account toAccount = new Account(500);public static void main(String[] args) throws InterruptedException {TransferAccount transferAccountOne = new TransferAccount();TransferAccount transferAccountTwo = new TransferAccount();transferAccountOne.flag = 0;transferAccountTwo.flag = 1;//线程1,从to账号转到from账号Thread threadOne = new Thread(transferAccountOne);//线程2,从from账号转到to账号Thread threadTwo = new Thread(transferAccountTwo);threadOne.start();threadTwo.start();threadOne.join();threadTwo.join();System.out.println("账户from的余额:"+fromAccount.balance);System.out.println("账户to的余额:"+toAccount.balance);}@Overridepublic void run() {if (flag == 1) {//从from账号转到to账号transMoney(fromAccount, toAccount, 200);}if (flag == 0) {//从to账号转到from账号transMoney(toAccount, fromAccount, 200);}}public static void transMoney(Account fromAccount, Account toAccount, int amount) {synchronized (fromAccount) {//模拟通信耗时,有了这个就会造成死锁try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (toAccount) {if (fromAccount.balance - amount < 0) {System.out.println("余额不足,转账失败");}//转账操作fromAccount.balance -= amount;toAccount.balance = toAccount.balance + amount;System.out.println("转账成功" + amount + "元");}}}static class Account {public Account(int balance) {this.balance = balance;}int balance;}
}

上述代码如果没有模拟通信耗时的代码,不一定会产生死锁,但是有了模拟通信耗时的sleep操作,就会必然出现死锁,因为其中之一的线程持有了相关锁之后,在不释放持有锁的情况下去获取其他锁。

2、多个人之间随机转账,也会出现死锁

/*** autor:liman* createtime:2021-10-30* comment: 多人同时转账 依旧很危险*/
public class ManyPersonTransMoney {//账户个数,随机从500个账户中抽出多个进行转账private static final int NUM_ACCOUNTS = 500;private static final int ACCOUNT_MONEY = 1000;//模拟每秒在线转账的次数private static final int TRANS_MONEY_COUNT = 1000000;private static final int NUM_TRANS_THREAD = 20;public static void main(String[] args) {Random random = new Random();Account[] accounts = new Account[NUM_ACCOUNTS];for (int i = 0; i < accounts.length; i++) {accounts[i] = new Account(ACCOUNT_MONEY);}class TransferThread extends Thread{@Overridepublic void run() {//模拟每次在线的转账个数(固定)for(int i=0;i<TRANS_MONEY_COUNT;i++){//随机得到转账账户和金额(同一个人可以对不同的人转账)int fromAcct = random.nextInt(NUM_ACCOUNTS);int toAcct = random.nextInt(NUM_ACCOUNTS);if(fromAcct!=toAcct){int amount = random.nextInt(ACCOUNT_MONEY);//转账(复用的之前的转账代码,)transMoney(accounts[fromAcct],accounts[toAcct],amount);}}}}//启动多个线程,进行转账for(int i=0;i<NUM_TRANS_THREAD;i++){new TransferThread().start();}}public static void transMoney(Account fromAccount, Account toAccount, int amount) {synchronized (fromAccount) {synchronized (toAccount) {if (fromAccount.balance - amount < 0) {System.out.println("余额不足,转账失败");}//转账操作fromAccount.balance -= amount;toAccount.balance = toAccount.balance + amount;System.out.println("转账成功" + amount + "元");}}}
}

这里的转账没有加入sleep的逻辑,只是正常的转账操作,但是运行一段时间之后,程序依旧卡死不动了

其实可以看到,如果没有从代码层面规避死锁,死锁不一定会发生,但是如果随着程序运行久了之后,死锁必然会发送,这似乎满足墨菲定律。

3、哲学家就餐问题

这是个经典的死锁问题,计算机专业的同学应该在操作系统这门课中都听说过这个问题。

关于什么是哲学家就餐问题,这里不再赘述,只是简单代码模拟一下

/*** autor:liman* createtime:2021-10-31* comment:哲学家就餐问题模拟*/
public class DiningPhilosophers {public static class Philosopher implements Runnable{private Object leftChopstick;private Object rightChopstick;public Philosopher(Object leftChopstick, Object rightChopstick) {this.leftChopstick = leftChopstick;this.rightChopstick = rightChopstick;}@Overridepublic void run() {try {//哲学家无止境的做某些事情,除了思考就是吃饭while (true) {doAction("进行哲学思考");synchronized (leftChopstick){doAction("拿起左边的筷子");synchronized (rightChopstick){doAction("拿起右边的筷子");doAction("吃饭......");doAction("放下右边的筷子");}doAction("放下左边的筷子");}}}catch (InterruptedException e){e.printStackTrace();}}private void doAction(String action) throws InterruptedException {System.out.println(Thread.currentThread().getName()+":"+action);Thread.sleep((long) (Math.random()*10));}}public static void main(String[] args) {Philosopher[] philosophers = new Philosopher[5];//初始化五个哲学家Object[] chopsticks = new Object[philosophers.length];//初始化5根筷子for(int i =0;i<chopsticks.length;i++){chopsticks[i] = new Object();}//实例化哲学家,并启动线程for(int i =0;i<philosophers.length;i++){Object leftChopstick = chopsticks[i];Object rightChopstick = chopsticks[(i+1)%chopsticks.length];philosophers[i] = new Philosopher(leftChopstick,rightChopstick);new Thread(philosophers[i],"哲学家"+(i+1)).start();}}
}

依旧运行一段时间之后,就出现死锁,哲学家就开始饿肚子了。

发生死锁的必要条件

1、互斥。线程间需要获取的资源是互斥的。

2、请求与保持。线程在获取了一个资源锁之后,并不释放锁,而是保持锁持有的锁。

3、不剥夺。没有第三方强制某个线程释放资源

4、循环等待。线程在不释放一个资源锁的情况下,等待另一个资源锁。多个线程在等待资源锁的时候,出现环形依赖的情况。

上述四个条件是死锁发生的必要条件,也就是说只有都满足上述四个条件的时候,才会发生死锁。

在回到之前的简单实例,4个条件都满足,故而发生死锁。

如何定位修复和避免

关于什么是死锁,发生死锁的4个必要条件,我们都做了介绍,现在该说一下如何避免和修复死锁了。

如何定位

死锁如果成功定位,解决死锁就成功了一半。

1、jstack

JDK提供的命令,通过指定pid(pid可以通过jps查看)即可检测并定位死锁。

用上面多人转账的实例跑一段时间之后,可以通过jstack,指定pid,查询到如下信息

2、ThreadMXBean

这个方式使用代码检测死锁,如果发生死锁,可以记录现场

/*** autor:liman* createtime:2021-10-30* comment:用ThreadMXBean 检测死锁*/
public class ThreadMXBeanDetection implements Runnable {int flag = 1;static Object object01 = new Object();static Object object02 = new Object();public static void main(String[] args) {SimpleDeadLockDemo deadLockDemo01 = new SimpleDeadLockDemo();SimpleDeadLockDemo deadLockDemo02 = new SimpleDeadLockDemo();deadLockDemo01.flag = 1;deadLockDemo02.flag = 0;Thread thread01 = new Thread(deadLockDemo01);Thread thread02 = new Thread(deadLockDemo02);thread01.start();thread02.start();//主线程等待1秒try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}//开始检测死锁ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();//返回发生死锁的线程ID数组long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();if (null != deadlockedThreads && deadlockedThreads.length > 0) {for (int i = 0; i < deadlockedThreads.length; i++) {//TODO:这里可以记录一下我们发生死锁的日志ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);System.out.println(threadInfo.getThreadName()+"发生死锁");String lockName = threadInfo.getLockName();System.out.println("相关的锁信息为"+lockName);}}}@Overridepublic void run() {System.out.println("当前的flag=" + flag);if (1 == flag) {synchronized (object01) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (object02) {System.out.println("escape dead lock,flag:1");}}} else {synchronized (object02) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (object01) {System.out.println("escape dead lock,flag:1");}}}}
}

如何修复

如果程序线上出现死锁是个很麻烦的事情,同时影响面也很广,因此我们尽量在开发过程中就要避免死锁的发生。如果线上真的出现死锁了,也必须要先保护案发现场,然后立刻重启服务器。先保证线上服务的正常与安全,然后利用刚才保存的信息,排查死锁,后续发布版本紧急修复。

如何避免

如果仔细思考一下,其实从业务层面来看,其实谁先获取锁并没有那么重要,我们完全可以通过调整获取锁的顺序来避免死锁。同时我们也可以在代码中引入死锁检测机制,如果发现死锁,就强制剥夺某个线程所拥有的资源。这个其实就是我们如何避免死锁的核心内容。

针对上面的多人转账问题,我们其实可以通过调整其获取锁的顺序来避免死锁

/*** autor:liman* createtime:2021-10-30* comment: 多人同时转账 死锁修复*/
public class ManyPersonTransMoneyFix {//账户个数,随机从500个账户中抽出多个进行转账private static final int NUM_ACCOUNTS = 500;private static final int ACCOUNT_MONEY = 1000;//模拟每秒在线转账的次数private static final int TRANS_MONEY_COUNT = 1000000;private static final int NUM_TRANS_THREAD = 20;static Object extendLock = new Object();public static void main(String[] args) {Random random = new Random();FixAccount[] accounts = new FixAccount[NUM_ACCOUNTS];for (int i = 0; i < accounts.length; i++) {accounts[i] = new FixAccount(ACCOUNT_MONEY);}class TransferThread extends Thread{@Overridepublic void run() {//模拟每次转账的账户个数for(int i=0;i<TRANS_MONEY_COUNT;i++){//随机得到转账账户和金额int fromAcct = random.nextInt(NUM_ACCOUNTS);int toAcct = random.nextInt(NUM_ACCOUNTS);if(fromAcct!=toAcct){int amount = random.nextInt(ACCOUNT_MONEY);//转账transMoney(accounts[fromAcct],accounts[toAcct],amount);}}}}//启动多个线程,进行转账for(int i=0;i<NUM_TRANS_THREAD;i++){new TransferThread().start();}}/*** 模拟转账的函数* @param fromAccount 转出账号* @param toAccount     转入账号* @param amount    金额*/public static void transMoney(FixAccount fromAccount, FixAccount toAccount, int amount) {class Helper{public void transfer(){if (fromAccount.balance - amount < 0) {System.out.println("余额不足,转账失败");}//转账操作fromAccount.balance -= amount;toAccount.balance = toAccount.balance + amount;System.out.println("转账成功" + amount + "元");}}int fromAccountHashCode = System.identityHashCode(fromAccount);int toAccountHashCode = System.identityHashCode(toAccount);//如果fromAccount的hashCode的值,小于toAccount的hashCode的值if(fromAccountHashCode<toAccountHashCode) {synchronized (fromAccount) {synchronized (toAccount) {new Helper().transfer();}}}//如果fromAccount的HashCode的值大于toAccount的hashCode的值if(fromAccountHashCode > toAccountHashCode){synchronized (toAccount) {synchronized (fromAccount) {new Helper().transfer();}}}//如果出现hashCode一样的情况,进入到加时赛if(fromAccountHashCode == toAccountHashCode){synchronized (extendLock){//任意顺序都可以,谁抢到这个锁,谁先执行synchronized (toAccount) {synchronized (fromAccount) {new Helper().transfer();}}}}}//简单的账户对象static class FixAccount {public FixAccount(int balance) {this.balance = balance;}int balance;}
}

之后,上面的代码就能欢快的运行了,并不会出现死锁

对于哲学家就餐问题,也可以通过调整特定的哲学家的获取餐具的顺序,达到避免死锁的目的

/*** autor:liman* createtime:2021-10-31* comment:哲学家就餐问题模拟*/
public class DiningPhilosophers {public static class Philosopher implements Runnable {private Object leftChopstick;private Object rightChopstick;public Philosopher(Object leftChopstick, Object rightChopstick) {this.leftChopstick = leftChopstick;this.rightChopstick = rightChopstick;}@Overridepublic void run() {try {//哲学家无止境的做某些事情,除了思考就是吃饭while (true) {doAction("进行哲学思考");synchronized (leftChopstick) {doAction("拿起左边的筷子");synchronized (rightChopstick) {doAction("拿起右边的筷子");doAction("吃饭......");doAction("放下右边的筷子");}doAction("放下左边的筷子");}}} catch (InterruptedException e) {e.printStackTrace();}}private void doAction(String action) throws InterruptedException {System.out.println(Thread.currentThread().getName() + ":" + action);Thread.sleep((long) (Math.random() * 10));}}public static void main(String[] args) {Philosopher[] philosophers = new Philosopher[5];//初始化五个哲学家Object[] chopsticks = new Object[philosophers.length];//初始化5根筷子for (int i = 0; i < chopsticks.length; i++) {chopsticks[i] = new Object();}//实例化哲学家,并启动线程for (int i = 0; i < philosophers.length; i++) {Object leftChopstick = chopsticks[i];Object rightChopstick = chopsticks[(i + 1) % chopsticks.length];//其中一位哲学家获取筷子的顺序与其他哲学家不同,可以避免死锁if (i == philosophers.length - 1) {//如果是最后一位哲学家,先获取右边的餐具,再获取左边的餐具philosophers[i] = new Philosopher(rightChopstick, leftChopstick);} else {philosophers[i] = new Philosopher(leftChopstick, rightChopstick);}new Thread(philosophers[i], "哲学家" + (i + 1)).start();}}
}

至此,天下太平,程序欢快运行

除此之外,还有其他操作可以避免死锁,引入一个守护线程,不断循环发现是否有死锁,如果有死锁则强制释放相关资源即可。同时也可以引入餐票机制(比如令牌桶)在哲学家想要就餐的时候,先获取餐票。等等,这里不一一举例子了。

在实际工程中一般有以下几种操作可以避免死锁

1、设置获取锁的超时时间,尽量使用tryLock而不是synchronized

2、多使用已经成熟的并发框架或者并发类,不要自己设计锁。

3、尽量降低锁的粒度,不同的逻辑用不同的锁,而不是很复杂的逻辑都用一个锁

4、尽量使用同步代码块

5、避免锁的嵌套

6、尽量专锁专用

7、线程起一个好识别的名称,便于排查问题。

关于tryLock这里还是提供一个简单的实例

/*** autor:liman* createtime:2021-10-31* comment: 多用tryLock,设置超时时间,可以避免死锁*/
public class TryLockFix implements Runnable {static Lock lock1 = new ReentrantLock();static Lock lock2 = new ReentrantLock();int flag = 1;@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(flag == 1){try {if(lock1.tryLock(800,TimeUnit.MILLISECONDS)){System.out.println("线程"+Thread.currentThread().getName()+"已获取lock1锁");if(lock2.tryLock(800,TimeUnit.MILLISECONDS)){System.out.println("线程"+Thread.currentThread().getName()+"已获取lock2锁,可以开始正常处理业务");Thread.sleep(new Random().nextInt(1000));//处理完业务之后,释放两把锁lock2.unlock();lock1.unlock();}else{System.out.println("线程"+Thread.currentThread().getName()+"已获取lock1锁,但未获取lock2锁,为了避免死锁,现在释放已获取的lock1锁");lock1.unlock();//为了避免死锁,释放已经获取的锁Thread.sleep(new Random().nextInt(1000));}}else{System.out.println("线程"+Thread.currentThread().getName()+"未获取lock1锁");}} catch (InterruptedException e) {e.printStackTrace();}}if(flag == 0){try {if(lock2.tryLock(800,TimeUnit.MILLISECONDS)){System.out.println("线程"+Thread.currentThread().getName()+"已获取lock2锁");if(lock1.tryLock(800,TimeUnit.MILLISECONDS)){System.out.println("线程"+Thread.currentThread().getName()+"已获取lock1锁,可以开始正常处理业务");Thread.sleep(new Random().nextInt(1000));//处理完业务之后,释放两把锁lock1.unlock();lock2.unlock();}else{System.out.println("线程"+Thread.currentThread().getName()+"已获取lock2锁,但未获取lock1锁,为了避免死锁,现在释放已获取的lock1锁");lock2.unlock();//为了避免死锁,释放已经获取的锁Thread.sleep(new Random().nextInt(1000));}}else{System.out.println("线程"+Thread.currentThread().getName()+"未获取lock1锁");}} catch (InterruptedException e) {e.printStackTrace();}}}}public static void main(String[] args) {TryLockFix threadOne = new TryLockFix();TryLockFix threadTwo = new TryLockFix();threadOne.flag = 1;threadTwo.flag = 0;new Thread(threadOne).start();new Thread(threadTwo).start();}
}

活锁与饥饿

除了死锁之外,还有其他活跃性问题,通常的称为活锁和饥饿。

活锁

什么是活锁呢?其实单纯点说,就是线程间在相互等待,等待其他线程运行结束,结果谁都没正常结束。

回到哲学家问题,类似于5个哲学家设置了等待时间,当五个哲学家都只拿起左边的叉子的时候,每个人都发现这样会造成死锁。又都同时放下手中的叉子,然后又都同时等待5分钟,5分钟之后都再次同时拿起左边的叉子,又发现会造成死锁,然后又放下叉子…如此反复,依旧没人吃得上饭,但是每个哲学家就在频繁的拿起叉子,放下叉子…这就是活锁。活锁解决方法之一,就是让重试的时间步调不一致即可。

下面用一个简单的实例说明一下活锁

/*** autor:liman* createtime:2021-10-31* comment:活锁实例*/
public class LiveLock {//勺子static class Spoon {private Diner owner;public Spoon(Diner owner) {this.owner = owner;}public Diner getOwner() {return owner;}public void setOwner(Diner owner) {this.owner = owner;}public synchronized void use() {System.out.printf("%s吃完了!", owner.name);}}static class Diner {private String name;private boolean isHungry;public Diner(String name) {this.name = name;isHungry = true;}public void eatWith(Spoon spoon, Diner spouse) {while (isHungry) {//如果自己没有持有勺子if (spoon.owner != this) {//进入等待1秒try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}continue;}//如果自己有勺子,并且饿了,为了避免死锁就先将勺子给对方(谦让)if (spouse.isHungry) {System.out.println(name + ": 亲爱的" + spouse.name + "你先吃吧");spoon.setOwner(spouse);continue;}//自己吃spoon.use();isHungry = false;System.out.println(name + ": 我吃完了");//吃完了将勺子给对方spoon.setOwner(spouse);}}}public static void main(String[] args) {Diner husband = new Diner("牛郎");Diner wife = new Diner("织女");Spoon spoon = new Spoon(husband);new Thread(new Runnable() {@Overridepublic void run() {husband.eatWith(spoon, wife);}}).start();new Thread(new Runnable() {@Overridepublic void run() {wife.eatWith(spoon, husband);}}).start();}
}

上述程序运行会出现活锁,如果想要解决,只需要偶在谦让哪一步引入一个随机条件即可

public void eatWith(Spoon spoon, Diner spouse) {while (isHungry) {//如果自己没有持有勺子if (spoon.owner != this) {//进入等待1秒try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}continue;}//如果自己有勺子,并且饿了,为了避免死锁就先将勺子给对方(谦让)Random random = new Random();//引入随机条件,避免活锁if (spouse.isHungry && random.nextInt(10) < 9) {System.out.println(name + ": 亲爱的" + spouse.name + "你先吃吧");spoon.setOwner(spouse);continue;}//自己吃spoon.use();isHungry = false;System.out.println(name + ": 我吃完了");//吃完了将勺子给对方spoon.setOwner(spouse);}
}

饥饿

饥饿这里不展开总结了,就是某些线程一直谦让其他线程运行,导致一直无法抢到CPU资源,可以通过提高线程优先级来解决线程饥饿的问题。

一些面试题

关于死锁,可能的面试题不多,无非就是什么情况下会发生死锁?发生死锁的必要条件有那几个?排查死锁有哪几种方式?如何避免死锁?

总结

简单总结了一下死锁

Java并发基础学习(八)——好好聊聊死锁相关推荐

  1. Java并发编程学习 + 原理分析(建议收藏)

    总结不易,如果对你有帮助,请点赞关注支持一下 微信搜索程序dunk,关注公众号,获取博客源码 Doug Lea是一个无私的人,他深知分享知识和分享苹果是不一样的,苹果会越分越少,而自己的知识并不会因为 ...

  2. java多线程基础学习[狂神说java-多线程笔记]

    java多线程基础学习 一.线程简介 1.类比 2.程序进程线程 3.线程的核心概念 二.线程的实现(重点) 调用方法与调用多线程的区别 Thread 类 1.thread使用方法 2. 代码实现 3 ...

  3. java并发编程学习一

    java并发编程学习一 什么是进程和线程? 进程是操作系统进行资源分配的最小单位 进程跟进程之间的资源是隔离的,同一个进程之中的线程可以共享进程的资源. 线程是进程的一个实体,是CPU 调度和分派的基 ...

  4. Java零基础学习Java编程语言基础知…

    很多Java编程初学者在刚接触Java语言程序的时候,不知道该学习掌握哪些必要的基础知识.下面就说说Java零基础学习Java编程语言基础知识的几个要点.希望能够对Java编程基础入门学习的新手有帮助 ...

  5. java入门基础学习(三)

    文章目录 (一)有返回值的方法 (二)方法重载 习题 (一)有返回值的方法 格式:public static 返回值数据类型 方法名(参数){方法体return 数据;} 注意:1.返回值数据类型非v ...

  6. [Java并发包学习八]深度剖析ConcurrentHashMap

    转载----http://qifuguang.me/2015/09/10/[Java并发包学习八]深度剖析ConcurrentHashMap/ HashMap是非线程安全的,并发情况下使用,可能会导致 ...

  7. Java零基础学习难吗

    java编程是入行互联网的小伙伴们大多数的选择,那么对于零基础的小伙伴来说Java零基础学习难吗?如果你是初学者,你可以很好的理解java编程语言.并不困难.如果你的学习能力比较高,那么你对Java的 ...

  8. 【J2ME 2D 游戏开发系列】◣HIMI游戏开发启蒙教程◢JAVA零基础学习J2ME游戏开发全过程!...

    本站文章均为 李华明Himi 原创,转载务必在明显处注明:  转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/j2me-2/774.html Himi从写 ...

  9. Java并发基础(六) - 线程池

    Java并发基础(六) - 线程池 1. 概述 这里讲一下Java并发编程的线程池的原理及其实现 2. 线程池的基本用法 2.1 线程池的处理流程图 该图来自<Java并发编程的艺术>: ...

最新文章

  1. 区块链相关论文研读2 - vChain,关于可验证的查询
  2. c#中与vb中CType相同功能的函数(强类型转换)
  3. Java String关于replaceall函数转义字符的一个小贴士
  4. easyExcel 读取日期为数字的解决方案
  5. competitor product could not be downloaded
  6. 计算机组成原理(二)数据的表示和运算
  7. css中的 font 与 font-size
  8. HCIE理论-IPV6
  9. 在不损坏硬盘数据情况下,MBR格式转GPT格式,手动创建EFI和MSR分区,安装win8/win10
  10. 126邮件POP3,SMTP服务器与端口设置
  11. 用计算机数字技术制作的电影是,计算机数字技术为电影带来的空前发展.doc
  12. php随浏览器大小变化,如何在将图像显示到浏览器之前使用php重新调整图像大小?...
  13. 飞塔防火墙添加删除用户配置
  14. 2020年中国云原生用户调研的十二个要点
  15. Window系统改装为linux系统
  16. python exception in thread_这个是什么原因,请问怎么处理Exception in thr
  17. 灵汐科技类脑芯片KA200入选2021年世界互联网领先科技成果“提名项目”
  18. 数字图像处理第二章----数字图像基础
  19. 机器视觉技术的发展动态
  20. 简单有效的科学健脑方法

热门文章

  1. 混淆矩阵及分类评价指标概念辨析
  2. Python学习笔记-Python 条件语句
  3. Python数据分析实战5.2-条件判断:if语句【python】
  4. 小风扇上日本亚马逊需要做什么认证
  5. 扫盲区块链: Corda不是区块链的分布式账本
  6. Hello, multivariate multiplication.
  7. 服务器连接键盘显示器的线,IBM 4269 USB KVM切换器连接线7316-TF3 7042-CR5 7042-CR6
  8. 学计算机文案,对计算机专业的认识及学业规划教学文案.doc
  9. 橱柜效果图-橱柜图片
  10. php制作万年历的步骤_PHP制作万年历_php实例