看了java核心技术 卷1的第14章《并发》,在此做点笔记,供以后复习。
线程睡眠函数(java.lang.Thread)
static void sleep(long millis)
实现多线程的两种方式:
第一种是继承Thread类,复写run方法。然后实例化这个类,调用实例的start()方法就能运行了。这样不好的地方在于没有把运行任务和运行机制解耦合。如果有很多任务的话,需要为每个任务都新建一个独立的线程,付出的代价太大。并且Thread类也是实现了Runnable接口。
class aa extends Thread {
public void run() {
System.out.println(Thread.currentThread() );
}
}
public class TestThread {
public static void main(String args[]) {
for(int i=0;i<10;i++) {
Thread ww =new aa();
ww.start();
}
}
}
第二种是实现Runnable接口。aa是用来写任务的,然后新建线程去执行任务。这样实现了运行任务和运行机制的解耦。
class aa implements Runnable {
public void run() {
System.out.println(Thread.currentThread() );
}
}
public class TestThread {
public static void main(String args[]) {
for(int i=0;i<10;i++) {
Thread ww = new Thread(new aa());
ww.start();
}
}
}
任务的声明还可以采用lambda表达式
Runnable r = ()->{
System.out.println(Thread.currentThread());
};
中断线程
当线程的run方法执行方法体最后一条语句后,并经由执行return语句返回时,或者出现了在方法中没有捕获的异常时,线程将终止。没有可以强制终止线程的方法。
一般会用interrupt方法来请求终止线程。interrupt方法本身是中断请求,但是代码里面可以自己写逻辑来根据中断结束线程。但是当线程处于阻塞状态下去请求中断,线程会抛出中断异常。
如下代码:
//将业务代码写在do more work中,一旦接收到了中断就return了
//否则一直检测中断标志位是否被置位。
while(!Thread.currentThread().isInterrupted()){
do more work
}
中断的一些函数(java.lang.Thread)
- void interrupt()
向线程发送中断请求。 - static boolean intrrupted()
测试当前线程是否被中断。调用后当前线程的中断状态会重置为false。 - boolean isInterrupted()
测试线程是否被终止。不改变线程的中断状态 - static Thread currentThread()
返回代表当前执行线程的Thread对象。
线程状态
- New(新创建创建)
- Runnable(可运行)
- Blocked(被阻塞)
- Wating(等待)
- Timed waiting(计时等待)
- Terminated(被终止)
1、新创建线程
当new一个新线程的时候,线程还没有开始运行的状态。
2、可运行线程
一旦调用start方法,线程处于runnable状态。一个可运行线程可能正在运行,也可能没有运行。只是说该线程可以运行。
3、被阻塞线程和等待线程
- 当一个线程视图获取一个内部的对象锁时,而该锁被其他线程持有,则该线程进入阻塞状态。
- 当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。
- 有几个方法有一个超时参数。调用他们会导致线程进入计时等待状态。如Thread.sleep、Object.wait、Thread.join等
4、被终止的线程
- 因为run方法正常退出而自然死亡。
- 因为一个没有捕获的异常终止了run方法而意外死亡。
线程属性
1、线程优先级
每一个线程都有优先级,继承它父线程的优先级。可以使用setPriority方法来提高或者降低任何一个线程的优先级。但是设置了也不一定按设置的来,还是取决于操作系统的安排。
2、守护线程
为其他线程提供服务的线程。没有其他线程,守护线程就没有意义。注 :不要用守护线程访问固定资源。虽然目前不知道含义,但是注意一下。
3、未捕获异常处理器
如果没有对应的异常处理,那所有的异常都会默认传递到一个未捕获异常处理器。
void uncaughtException(Thread t,Throwable e)
同步
当不同的线程访问相同的资源时,会发生竞争的情况,这样会造成数据错误。有两种机制放置代码块受到并发访问的干扰。
lock、unlock和synchronized关键字
lock是上锁,unlock是解锁,unlock一般放在finally中,防止在临界区代码结束之前抛出了异常,而线程还依旧一直处于锁定状态。
条件对象的意思是当线程进行到某一步时,发现没满足条件,此时不能往下进行,所以不需要继续占用锁了。此时把该线程挂起来,并放弃锁。然后等待其他线程来激活这个被挂起来的锁。挂起来的函数为Condition.await(),激活函数为Condition.signalAll()
//没有条件对象的情况
mylock.lock();//上锁
try
{
critical section
}
finally
{
myLock.unlock();//解锁
}
//有条件对象的情况
private Condition condition;
mylock.lock();//上锁
try
{
while(!(ok to proceed))
condition.await();//等待激活
do something
condition.signalAll();//激活
}
finally
{
myLock.unlock();//解锁
}
Synchronized关键字声明类或者方法即可以上锁。然后直接调用wait()和notifyAll()就可以让线程进入等待和解除等待。
以下是我现在用的redis共享锁的代码###############
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 分布式锁的简单实现代码
*/
@Service
public class DistributedLock {
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* Redis SetNX 指令,stringRedisTemplate是封装了redis基础操作,所以不含有setnx指令。这里就通过它本身的接口来实现setnx。
*/
private Boolean setnx(String key, Object value) {
return stringRedisTemplate.execute((RedisConnection conn) -> {
try {
return conn.setNX(stringRedisTemplate.getStringSerializer().serialize(key),
stringRedisTemplate.getStringSerializer().serialize(value.toString()));
} finally {
conn.close();
}
});
}
/**
* 加锁
*
* @param lockName 锁的key
* @param acquireTimeout 获取超时时间
* @param timeout 锁的超时时间
* @return 锁标识
*/
public String lockWithTimeout(String lockName, long acquireTimeout, long timeout) {
String retIdentifier = "0";
try {
// 随机生成一个value
//String identifier = UUID.randomUUID().toString();
// System.out.println("identity:"+identifier);
// 锁名,即key值
String lockKey = "lock:" + lockName;
// 超时时间,上锁后超过此时间则自动释放锁
int lockExpire = (int) (timeout / 1000);
// 获取锁的超时时间,超过这个时间则放弃获取锁
long end = System.currentTimeMillis() + acquireTimeout;
while (System.currentTimeMillis() < end) {
if (this.setnx(lockKey, "1")) {
stringRedisTemplate.opsForValue().set(lockKey, "1", lockExpire, TimeUnit.SECONDS);
// 返回value值,用于释放锁时间确认
retIdentifier = "1";
return retIdentifier;
}
// 返回-1代表key没有设置超时时间,为key设置一个超时时间
if (stringRedisTemplate.getExpire(lockKey) == -1) {
stringRedisTemplate.opsForValue().set(lockKey, "1", lockExpire, TimeUnit.SECONDS);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
return retIdentifier;
}
/**
* 释放锁
*
* @param lockName 锁的key
* @param identifier 释放锁的标识
* @return
*/
public boolean releaseLock(String lockName) {
String lockKey = "lock:" + lockName;
boolean retFlag = false;
try {
// 通过key值判断是不是该锁,若是该锁,则删除,释放锁
stringRedisTemplate.delete(lockKey);
retFlag = true;
} catch (Exception e) {
} finally {
}
return retFlag;
}
}
该共享锁只是实现了基本的分布式锁,能够满足不同服务器争夺同一资源的场景。获取锁超时时间和自动释放锁的时间需要自己衡量。该方法没有实现可重入锁,原理也不难,就是存储的锁key的value是一个随机数就可以代表用户这次获取锁的身份,就可以重入了。具体的细节没做,就没发言权了。因为业务到这一步就够用了。
网友评论