fvdcx's blog


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

leetcode-jump-game

发表于 2016-12-31   |   分类于 算法 , leetcode   |  

题目大意

  https://leetcode.com/problems/jump-game/

  给你一个数组,数组上每一个位置上的值代表从当前位置可以往后跳多远,求从数组起点是否能跳跃到数组终点。

题目分析

  贪心法,从前向后扫描,不断更新最远能跳跃到的位置,如果大于数组末尾那么就返回true.

阅读全文 »

leetcode-remove-k-digits

发表于 2016-12-31   |   分类于 算法 , leetcode   |  

题目大意

  https://leetcode.com/problems/remove-k-digits/

  给了你一个字符串代表非负整数,从其中去掉k位,使得剩下的字符串代表的整数最小并输出

题目分析

  第一种我自己想的思路是:由于让整个数最小,那么应该从最高位开始保证每一位都尽可能的小,很自然地从左向右扫描字符串,得到结果的每一位,复杂度是O(n ^ 2)

  第二种思路看了讨论区利用栈实现的O(n)算法(可以借助均摊分析,最多弹栈k次,压栈n次),思路是用一个辅助栈,然后扫描原串的每一位,在压入栈之前

先利用循环看栈顶的字符串是否比更大,如果大的话,就出栈,每次出栈,都代表remove了一位,因此出栈次数不能超过k,注意最后栈中的长度可能超过答案,只取前len(num) - k个即可

阅读全文 »

Java多线程学习-II

发表于 2016-12-31   |   分类于 Java , 多线程   |  

参考了 《Java核心技术》 卷I 第14章 多线程 14.5.1-14.5.4

5.同步

  并发线程如果访问统一对象或者变量可能造成竞争。书中举了银行账户之间转账的例子,这是代码;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Runnable r = () -> {
try
{
while (true)
{
int toAccount = (int) (bank.size() * Math.random())
double amount = MAX_AMOUNT * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (DELAY * Math.random()));
}
}
catch (InterruptedException e)
{
}
};

  transfer方法代码

1
2
3
4
5
6
7
8
9
public void transfer(int from, int to, double amount)
{
if (accounts[from] < amount) return;
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
}

  这里注意到accounts[to] += amount;不是原子操作,下面这个图也很清晰地显示出发生竞争,导致最后值不正确的过程。

  在Java中,关键字synchronized就是提供了锁的机制,当然java.util.concurrent包中也提供了分离出来的类实现锁的机制

使用ReentrantLock(注意在java.util.concurrent包中,Lock是接口,ReentrantLock是实现类 )的一个基本框架如下:

1
2
3
4
5
6
7
8
9
myLock.lock(); // a ReentrantLock object
try
{
// critical section
}
finally
{
myLock.unlock(); // make sure the lock is unlocked even if an exception is thrown
}

  下面这个例子使用锁来保护Bank类的transfer方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Bank
{
private Lock bankLock = new ReentrantLock(); // ReentrantLock implements the Lock interface
. . .
public void transfer(int from, int to, int amount)
{
bankLock.lock();
try
{
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
}
finally
{
bankLock.unlock();
}
}
}

  可以看到如果一个线程最先调用了Bank对象的transfer方法,将获得锁,在锁不被释放之前,其它线程如果访问这一被锁保护的代码,将会被阻塞。线程锁还有一个hold count的变量,用来记录调用嵌套调用锁方法的次数。也就是说transfer方法中可以嵌套调用另一个方法同样锁住bankLock对象:

  这里transfer方法中调用了getTotalBalance方法,而getTotalBalance方法又要获得锁,在此种情境下,锁并不会冲突,因为是在同一线程下。如果是两个线程访问同一个Bank对象中的transfer或者getTotalBalance方法,锁就要串行化工作了。很显然,如果两个线程访问两个Bank对象的transfer方法,锁并不会冲突,因为是两把锁。

  下面这张图是同步和非同步访问transfer的示例:

  关于条件对象:仔细看刚刚Runnbale中的run方法,会发现在bank.transfer(from, to, amount)之前并没有做任何的条件判断,这会造成余额不够的情况,但是,不能用下面这样的条件判断:

1
2
if (bank.getBalance(from) >= amount)
bank.transfer(from, to, amount);

  因为如果当前线程有可能在下面的注释的地方中断:

1
2
3
if (bank.getBalance(from) >= amount)
// thread might be deactivated at this point
bank.transfer(from, to, amount);

  一旦中断,那么线程再次运行时,有可能判断条件又不满足了,通过使用锁的手段可以避免

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void transfer(int from, int to, int amount)
{
bankLock.lock();
try
{
while (accounts[from] < amount)
{
// wait
}
// transfer funds
}
finally
{
bankLock.unlock();
}
}

  但这就意味着当账户没有足够余额的时候,其他线程根本没有transfer的机会,这就是我们需要条件对象的原因。在此,我们设置了一个条件“余额充足”的条件:

1
2
3
4
5
6
7
8
9
10
class Bank
{
private Condition sufficientFunds; //条件对象
. . .
public Bank()
{
. . .
sufficientFunds = bankLock.newCondition();
}
}

  如果transfer方法发现余额不走,调用sufficientFunds的await 方法,当前线程阻塞,放弃锁,注意调用await方法与等待获得锁的线程有所不同,调用await方法,是进入该条件的等待集,锁可用时,也不能马上接触阻塞,要等待其它线程调用同意条件的signalAll方法才可以。signalAll方法不是激活线程,只是解除等待线程。

Java多线程学习-I

发表于 2016-12-31   |   分类于 Java , 多线程   |  

参考了 《Java核心技术》 卷I 第14章 多线程 14.1-14.4

1.线程概念

  在面试中经常问到的一个问题,线程和进程的区别是什么? 本质在于每个进程的一整套变量,而线程是共享数据的,基于共享变量,线程之间的通信比进程之间的通信更加高效(这里又有面试题,进程之间的通信方式有哪些),此外,与进程相比,线程更加轻量,创建销毁一个线程比进程的代价小的多。

  下面通过代码来看下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.Date;
public class MyRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
System.out.println(new Date());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

  MyRunnable实现了Runnable接口,并实现了run方法

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
Runnable r = new MyRunnable();
Thread t = new Thread(r); //创建Thread对象
t.start(); //启动一个线程,并执行run方法
}
}

  注意,不要直接调用Thread类或者Runnable的对象的run方法,直接调用run方法,只会执行同一个线程中的任务,而不会执行新线程,应该调用Thread类的start方法,这样才会创建一个新线程并执行run方法。

2.中断线程

  当线程run方法执行完最后一条语句,return返回,或者出现了没有捕获的异常时,线程将终止,在Java早期版本中有一个stop方法,可以调用强制终止线程,但现在已经弃用了。因此除stop之外没有一种可以强制终止线程的方法。

  然而interrupt方法可以请求终止线程,当调用interrupt方法,线程的中断状态将被置位,这是每一个线程都具有的标志位,每个线程都应该不时地检查这个标志,以判断线程是否被中断。还是刚刚例子:

1
2
3
4
5
6
7
8
9
10
public class Main {
public static void main(String[] args) {
Runnable r = new MyRunnable();
for (int i = 0; i < 1; i++) {
Thread t = new Thread(r);
t.start();
t.interrupt();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
public class MyRunnable implements Runnable {
private int a;
@Override
public void run() {
System.out.println(Thread.currentThread().isInterrupted());
System.out.println(a);
}
}

  console会输出true和0,因此可以证明调用interrupt方法只是置了标志位而已,并没有终止程序。

  当一个被阻塞的线程(比如调用sleep或者wait方法)上调用interrupt方法,阻塞线程将抛出InterruptedException(抛出异常以后会将interrupt状态进行清除)。但是如果你先调用interrupt方法,而后在线程中调用sleep方法,它不会sleep,相反,会将interrupt状态清除(置为false),并抛出InterruptedException,可以看下下面程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
Runnable r = new MyRunnable();
for (int i = 0; i < 1; i++) {
Thread t = new Thread(r);
t.start();
t.interrupt();
System.out.println("[0] t.isInterrupted : " + t.isInterrupted());
for (int j = 0; j < Integer.MAX_VALUE; j++);
for (int j = 0; j < Integer.MAX_VALUE; j++);
for (int j = 0; j < Integer.MAX_VALUE; j++);
System.out.println("[1] t.isInterrupted : " + t.isInterrupted());
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyRunnable implements Runnable {
private int a;
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(a++);
}
}

  输出

1
2
3
4
5
6
7
[0] t.isInterrupted : true
java.lang.InterruptedException: sleep interrupted
0
at java.lang.Thread.sleep(Native Method)
at com.multitask.MyRunnable.run(MyRunnable.java:11)
at java.lang.Thread.run(Thread.java:722)
[1] t.isInterrupted : false

  可以看到在sleep之前,就对线程进行了interrupt调用,线程不会sleep。

  还需要注意对InterruptedException异常处理要足够重视,不要在catch子句中什么都不做,可以有两种做法:

1
2
3
4
5
6
7
void mySubTask()
{
. . .
try { sleep(delay); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
. . .
}

  这样调用者可以对其检测,还可以抛出InterruptedException,由上级调用者捕获这一异常

1
2
3
4
5
6
void mySubTask() throws InterruptedException
{
. . .
sleep(delay);
. . .
}

  但是注意实现Runnable接口的类的run方法是不允许抛出异常的。具体可以看下这里:http://stackoverflow.com/questions/11410042/why-cannot-run-of-runnable-throw-checked-exceptions

3.线程状态

  现成6种状态:New,Runnable,Blocked,Waiting,Timed wating,Terminated,可以调用getState方法。

具体状态之间的转换关系:

https://ooo.0o0.ooo/2016/12/29/5864cc246033c.png

4.线程属性

  在Java中,每个线程都有一个优先级,默认情况下,一个线程继承他父亲线程的优先级,同时可以用setPriority方法提高或降低任何一个线程优先级。

  通过setDaemon(true)可以设置一个线程为守护线程,注意此方法必须在启动线程之前调用。

  之前说过run方法是不能抛出任何checked exception,但是他可以被unchecked exception所终止,在线程死亡之前,异常可以设置传递到一个用于捕获异常的处理器,具体可以看下core java 14.4.3中的详细介绍。

1…161718…38
fvdcx

fvdcx

149 日志
14 分类
20 标签
GitHub
© 2015 - 2017 fvdcx
由 Hexo 强力驱动
主题 - NexT.Pisces