谁无暴风劲雨时、守得云开见月明
线程的6个状态
创建、运行、阻塞、等待、超时等待、终止
创建:new thread()
运行:.start()
阻塞:synchronized(),lock()
等待:wait()join()
超时等待自动唤醒:sleep(long),wait(long)
终止。
锁
wait和sleep的区别:
区别 | wait | sleep |
---|---|---|
所属类 | object | thread |
锁的释放 | 释放锁 | 不释放 |
使用范围 | 在代码块当中 | 任何地方 |
异常捕获 | 不需要捕获 | 必须捕获 |
synchronized锁和lock锁的区别:
区别 | synchronized | lock |
---|---|---|
类型区别 | java关键字 | 是单独一个类 |
锁的释放 | 自动释放锁 | 必须手动释放锁 |
锁的获取 | 自动获取锁 | 需要trylock |
是否可重入 | 不能 | 可设置释放信号重入锁 |
引用场景 | 适合锁少量的代码同步问题 | 适合锁大量的同步代码。 |
synchronized用法
下面通过一段代码解释synchronize
public class SaleTicketDemo1 {
public static void main(String[] args) {
//Ticket 里拥有2个方法,每个方法都用synchronize标注了。
//表明了当前类只有一个锁,增方法和减方法只能同时进行一个
Ticket ticket = new Ticket();
//启动一个线程,进行20次的增方法
new Thread(()->{
for (int i = 0; i < 20 ; i++) {
try {
ticket.inc();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
//启动一个线程,进行40次的增方法
new Thread(()->{
for (int i = 0; i < 40 ; i++) {
try {
ticket.dec();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Ticket {
//定义票数为0
private int number = 0;
//定义增方法
public synchronized void inc() throws InterruptedException {
//判断是否有票,有票则wait释放进行消费
if(number!=0){
//这里使用的是wait,会自动释放掉锁,线程会进入下面的方法
wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
notifyAll();
}
public synchronized void dec() throws InterruptedException {
//判断是否没票,有票则wait释放进行创建票
if(number!=1){
//这里使用的是wait,会自动释放掉锁,线程会进入上面的方法
wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
notifyAll();//唤醒锁,会将wait的方法进行放行
}
}
上面这段代码,在2个独立的线程下降进行运行,一个线程负责增加,一个线程负责减少。操作的对象用synchronized
关键字进行标记,wait
释放掉锁,notifyAll
将wait掉的锁进行放行。
虚假唤醒
那么如果线程不独立呢?
new Thread(()->{
for (int i = 0; i < 20 ; i++) {
try {
ticket.inc();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
//启动一个线程,进行40次的增方法
new Thread(()->{
for (int i = 0; i < 40 ; i++) {
try {
ticket.dec();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20 ; i++) {
try {
ticket.inc();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
//启动一个线程,进行40次的增方法
new Thread(()->{
for (int i = 0; i < 40 ; i++) {
try {
ticket.dec();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
我们看结果发现处理负一。为什么呢?
因为这里notifyAll()
同时唤醒了其余的线程。相当于2个加1并减1。不要不被这里的顺序看着和描述的不一样。因为这里的和线程的调度顺序有关可能是先加1减1再加1,所以看着正常。这种现象我们称之为虚假唤醒
造成这种原因就是wait之后没有再次判断参数的值,直接放行了。只需将if改为while再次对参数进行判断就不会出现了。
因为if只会执行一次,执行完会接着向下执行if()外边的
而while不会,直到条件满足才会向下执行while()外边的
Lock
ReentrantLock
class Ticket {
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// condition.await() 等待
// condition.signalAll() 唤醒全部
public void inc() throws InterruptedException {
lock.lock();
try {
while (number!=0){
condition.await(); //等于sync中的wait
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition.signalAll(); //等于sync中的notifyAll
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //手动释放锁
}
}
public void dec() throws InterruptedException {
lock.lock();
try {
while (number!=1){
condition.await(); //等于sync中的wait
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition.signalAll(); //等于sync中的notifyAll
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(); //手动释放锁
}
}
}
上面代码使用lock.newCondition()实现了synchronized
中的wait
和notifyall
效果
但是上面输出是无顺序的呢怎么保证顺序线程的顺序输出呢?
class Ticket {
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
Condition condition4 = lock.newCondition();
// condition.await() 等待
// condition.signalAll() 唤醒全部
public void incA() throws InterruptedException {
lock.lock();
try {
while (number!=0){
condition1.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition2.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decB() throws InterruptedException {
lock.lock();
try {
while (number!=1){
condition2.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition3.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void incC() throws InterruptedException {
lock.lock();
try {
while (number!=0){
condition3.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition4.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decD() throws InterruptedException {
lock.lock();
try {
while (number!=1){
condition4.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
condition1.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
使用多个newCondition就可。唤醒对应的线程。
八锁现象
8锁,就是关于锁的八个问题!
public class SaleTicketDemo1 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{ticket.message();},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{ticket.call();},"B").start();
}
}
class Ticket {
public synchronized void message(){
System.out.println("发消息");
}
public synchronized void call(){
System.out.println("打电话");
}
}
问题1:标准情况下,两个线程执行,先打印发短信还是打电话? 1/发短信 2/打电话。
答: 先发消息,1秒后打电话
问题2:在message中添加5秒的延时,是怎样?1/发短信 2/打电话
答: 先发消息,6秒后打电话
问题3:新增一个普通非同步方法hello(),message中延迟5秒,让B线程调用hello,请问执行结果? 1/hello 2/发短信
答: 发短信,1秒后打电话。锁只有A拿到了,线程B是非锁方法。
问题4:两个ticket,分别在A、B中调用message和call,message中延迟5秒,请问执行结果? 1/打电话 2/发短信
答: 先发消息,6秒后打电话,2个锁相互独立的,互不影响
问题5:将call和message都设置成static,message中有延迟5秒,让A和B分别调用message和call,请问执行结果?1/发短信 2/打电话
答: 发消息,打电话。因为是static,锁是class类对象的,只有一把,A先拿B再拿
问题6:在5的基础上,分别用两个ticket分别在A和B中调用static和message,请问执行结果? 1/发短信 2/打电话
答: static类对象只有1个。锁只有一个,依旧是A先拿锁。
问题7:一个延迟4秒的静态同步方法message被A中的ticket调用,一个普通同步方法call被B中的同一个ticket调用,请问执行结果? 1/打电话 2/发短信
答: 2个锁不一样。A调用的是静态锁,B调用的是对象锁
问题18:2个不同的ticket,分别在A和B中调用一个延迟4秒的静态同步方法message和一个普通同步方法call,请问执行结果? 1/打电话 2/发短信 。
答: A调用的是同样的一个静态锁,B是调用的2个不同的对象锁。
带锁安全集合类
一般的list,set,map都是不安全的集合类。在多线程并发的情况下,会报 java.util.ConcurrentModificationException并发修改异常,如何解决这个问题呢?
带锁的安全集合:
List
// 带锁的vector集合
// 不能使用迭代器去遍历修改值,迭代器带锁,会有并发问题
Vector v=new Vector();
// 使用集合锁包装一下
List<String> list = Collections.synchronizedList(new ArrayList<>());
//使用CopyOnWriteArrayList集合
List<String> list = new CopyOnWriteArrayList<>();
CopyOnWrite:
1.写入时复制,COW,是计算机设计领域的一种优化策略
2.当多个线程调用ArrayList的时候,读是固定的,而写不一定是固定的,后者的写操作可能会覆盖前者,为了避免覆盖,才使用写入时复制。
3.CopyOnWrite比Vector好在哪?Vector底层的add方法加了synchronized,方法效率低
Set
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
Map
Map<String,Object> map = Collections.synchronizedMap(new HashMap<>());
Map<String,Object> map = new ConcurrentHashMap<>();
Callable
当我们想在线程中获得返回数据怎么办?
这个时候就要使用到接口Callable了。
Callable实现方式:
public class SaleTicketDemo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//基础了Callable类的接口
MyThread myThread = new MyThread();
//FutureTask包装
FutureTask futureTask = new FutureTask(myThread);
//最后放进线程,A为标识。
new Thread(futureTask,"A").start();
Integer o = (Integer) futureTask.get();
System.out.println(o);
}
}
//函数式接口
//继承Callable
class MyThread implements Callable<Integer>{
//重新callable方法
@Override
public Integer call() throws Exception {
System.out.println("call()"); // 结果只会打印一次call()
return 1024;
}
}