JUC总结
线程与进程
进程:是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。
线程:是进程的一个实体,是 cpu 调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。
java如何开启线程?
通过底层调用C语言,所以java本身无法创建线程!
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
// 本地方法,底层的C++ ,Java 无法直接操作硬件
private native void start0();
并发与并行
并发(多线程操作同一个资源)
- CPU一核 ,模拟出来多条线程,天下武功,唯快不破,快速交替
并行(多个人一起行走)
- CPU多核 ,多个线程可以同时执行; 线程池
并行是在不同实体上的多个事件,并发是在同一实体上的多个事件
线程的几种状态
public enum State {
// 新生
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,死死地等
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}
wait/sleep 区别
-
sleep() 方法正在执行的线程主动让出 cpu(然后 cpu 就可以去执行其他任务),在 sleep 指定时间后 cpu 再回到该线程继续往下执行(注意:sleep 方法只让出了 cpu,而并不会释放同步资源锁);
而 wait() 方法则是指当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了 notify() 方法,之前调用 wait() 的线程才会解除 wait 状态,可以去参与竞争同步资源锁,进而得到执行。(注意:notify 的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说 notify 只是让之前调用 wait 的线程有权利重新参与线程的调度);
sleep() 方法可以在任何地方使用,而 wait() 方法则只能在同步方法或同步块中使用;
-
sleep() 是线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait() 是 Object 的方法,调用会放弃对象锁,进入等待队列,待调用 notify()/notifyAll() 唤醒指定的线程或者所有线程,才会进入锁池,不再次获得对象锁才会进入运行状态。
wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放!
Lock锁
传统Synchronized
修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁;
-
修饰静态方法:作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员(static 表明这是该类的一个静态资源,不管 new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。
所以如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁;
修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。和 synchronized 方法一样,synchronized(this) 代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class 类上锁。这里再提一下:synchronized 关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为 JVM 中,字符串常量池具有缓冲功能。
Lock接口
实现类:
- ReentrantLock(可重入锁 常用)
- ReentrantReadWriteLock.ReadLock(读锁)
- ReentrantReadWriteLock.WriteLock(写锁)
public ReentrantLock() {
sync = new NonfairSync(); //非公平锁
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();//公平锁
}
公平锁:十分公平:可以先来后到
非公平锁:十分不公平:可以插队 (默认)
Synchronized 和 Lock 区别
Synchronized是内置的Java关键字, Lock 是一个接口
Synchronized无法判断获取锁的状态,Lock 可以判断是否获取到了锁
Synchronized会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去;
Synchronized 可重入锁,不可以中断的,非公平;Lock 可重入锁,可以判断锁,非公平(可以自己设置);
生产者和消费者问题
Synchronized实现
class Data1 {
private int number = 0;
public synchronized void increment() throws InterruptedException {
while (number != 0) {
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + ":" + number);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while (number != 1) {
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + ":" + number);
this.notifyAll();
}
}
lock实现
Sychronized---->lock(Lock)
wait---->await(Condition)
notifiy---->signal(Condition)
class Data2 {
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
lock.lock();
try {
while (number != 0) {
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + ":" + number);
condition.signalAll();
} finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number != 1) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + ":" + number);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
测试类
public class Test {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
注意(虚假唤醒)
当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功。比如说买货,如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了 ,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁。
如何解决?必须将if改成while
Condition实现精准通知和唤醒线程
目的:使唤醒顺序为ABC有序
class Data3{
int number =1; //1A 2B 3C
Lock lock=new ReentrantLock();
Condition condition1=lock.newCondition();
Condition condition2=lock.newCondition();
Condition condition3=lock.newCondition();
public void printA(){
lock.lock();
try {
while (number !=1){//2,3
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAAAAAA");
number=2;
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (number !=2){//1,3
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"=>BBBBBBB");
number=3;
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (number !=3){ //1,2
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"=>CCCCCCC");
number=1;
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
测试
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{for (int i = 0; i < 10; i++) {
data3.printB();
}},"B").start();
new Thread(()->{for (int i = 0; i < 10; i++) {
data3.printC();
}},"C").start();
}
结果:
A=>AAAAAAA
B=>BBBBBBB
C=>CCCCCCC
A=>AAAAAAA
B=>BBBBBBB
C=>CCCCCCC
...