1.什么是JUC
JUC:java原生并发包还有一些常用工具类
2.线程基础知识
并发、并行
并发:多个线程操作同一个资源,交替执行的过程!
并行:多个线程同时执行!只有在多核CPU下才能完成!
线程六种状态
新建(new) ,运行(RUNNABLE),阻塞(BLOCKED),等待(WAITING),延迟等待(TIMED_WAITING),终止(TERMINATED)
wait/Sleep 区别
1.类不同
s.wait(1000);//object 类
TimeUnit.SECONDS.sleep(1);//Thread 类
2.释放资源不同
sleep:抱着锁睡得,不会释放锁!wait 会释放锁!
3.使用范围不同
wait 和 notify 是一组,一般在线程通信的时候使用!
sleep 就是一个单独的方法,在那里都可以用
4.关于异常
sleep 需要捕获异常!
说明
两者都可以暂停线程的执行。
对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。线程不会自动苏醒
3、线程锁
synchronized 传统方式
java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的(后面会在8锁现象里具体说明)
先看传统的synchronized写法:
/*
*线程操作资源类,资源类是单独的
*/
public class Demo1 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//线程操控资源类
new Thread(new Runnable() {
public void run() {
for (int i = 0; i <30 ; i++) {
ticket.saleTicket();
}
}
},"A").start();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i <30 ; i++) {
ticket.saleTicket();
}
}
},"B").start();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i <30 ; i++) {
ticket.saleTicket();
}
}
},"C").start();
}
}
//资源类 假设我们在卖票
class Ticket{
private int num = 100;
public synchronized void saleTicket(){
if (num>0){
System.out.println(Thread.currentThread().getName()
+"卖出第"+(num--)+"票,还剩"+num);
}
}
}
Lock锁
锁是用于通过多个线程控制对共享资源的访问的工具,通常锁提供对共享资源的独占访问,一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如ReadWriteLock的读写锁。在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的。JDK1.5之后并发包中新增了Lock接口以及相关实现类来实现锁功能。synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: 1.获取锁的线程执行完了该代码块,然后线程释放对锁的占有 2.线程执行发生异常,此时JVM会让线程自动释放锁
那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能地等待,这多么影响程序执行效率。因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到
写法:
public class Demo2 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(()->{
for (int i = 0; i < 30; i++)ticket.saleTicket(); },"A").start();
new Thread(()-> {
for (int i = 0; i <30 ; i++) ticket.saleTicket(); },"B").start();
new Thread(()->{
for (int i = 0; i < 30; i++) ticket.saleTicket(); },"C").start();
}
}
//资源类 假设我们在卖票
class Ticket2{
// 使用Lock,它是一个对象
// ReentrantLock 可重入锁:回家:大门 (卧室门,厕所门...)
// ReentrantLock 默认是非公平锁!
// 非公平锁: 不公平 (插队,后面的线程可以插队)
// 公平锁: 公平(只能排队,后面的线程无法插队)
// ReentrantLock(轻量级锁)也可以叫对象锁,可重入锁,互斥锁
private Lock lock = new ReentrantLock();
private int num = 100;
public void saleTicket(){
lock.lock();
try {
if (num>0){
System.out.println(Thread.currentThread().getName()
+"卖出第"+(num--)+"票,还剩"+num);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
注意lock.lock()不能放在try里面
说明:因为在 try-finally 外加锁的话,如果因为发生异常导致加锁失败,try-finally 块中的代码不会执行。相反,如果在 try{ } 代码块中加锁失败,finally 中的代码无论如何都会执行,但是由于当前线程加锁失败并没有持有 lock 对象锁,所以程序会抛出异常。
Synchronized 和 Lock 区别
1、Synchronized 是一个关键字、Lock 是一个对象
2、Synchronized 无法尝试获取锁,Lock 可以尝试获取锁,判断;
3、Synchronized 会自动释放锁(a线程执行完毕,b如果异常了,也会释放锁),lock锁是手动释放锁!如果你不释放就会死锁。
4、Synchronized (线程A(获得锁,如果阻塞),线程B(等待,一直等待);)lock,可以尝试获取锁,失败了之后就放弃
5、Synchronized 一定是非公平的,但是 Lock 锁可以是公平的,通过参数设置;
6、代码量特别大的时候,我们一般使用Lock实现精准控制,Synchronized 适合代码量比较小的同步问题;