JUC并发编程
线程和进程
进程是程序的一次执行过程,是系统运行的基本单位,系统运行一个程序就是进行创建,使用和销毁的过程。
一个进程往往都包含若干个线程(至少一个),在java中默认包含两个(main,GC)。线程往往看作是一个轻量级进程。
区别与联系:
-
进程是操作系统分配的基本单位,而线程是处理器任务调度和执行的基本单位。
-
每个进程是有独立的代码和数据空间,程序之间切换是有较大的开胶;在进程中的多线程共享进程的堆和方法区,每个线程有自己独立的本地方法栈,虚拟机栈和程序计数器,线程之间的切换开销比较小。
-
一个进程崩溃后,在保护模式下其他进程是不受影响的;而一个线程崩溃会导致所在的进程崩溃。
创建线程的三个方法:
Thread、Runnable、Callable
Runnable与Callable的区别:
- 重写的方法不同:前者重写run()方法,后者重写call方法
- run方法无返回值,call方法可以有返回值。
- call方法可以抛异常,而run方法不可以。
Lock锁
在api文档jucl包下提供三个接口:Condition(条件变量),Lock,ReadWriteLock(读写锁)
锁是用于通过多个线程控制对共享资源的访问的工具,也就是对共享资源的独占访问。
Lock锁实现提供了比使用synchronized方法和语句可以获得的更广泛的锁定操作。常常与try,catch,finally语句一起使用。
synchronized与Lock的区别
1.synchronized 内置的java关键字,Lock是一个Java类
2. Synchronized无法判断获取锁的状态,Lock可以判断是否获得了锁
3. Synchronized会自动释放锁,而Lock一定要手动释放锁,否则会思索。
4. Synchronized 可重入锁,不可以中断,非公平的; Lock锁同样可重入锁,公平/非公平可以自己设置
5. Synchronized 适合锁少量的代码同步; Lock 适合大量代码同步的情况。
synchronized锁的是方法的调用者:
示例:
import java.io.Serializable;
import java.util.HashSet;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
public class testDemo01 {
public static void main(String[] args) {
Phone phone = new Phone();
//线程一拿到了phone对象,那么其余线程调用该phone对象时会等待
new Thread(() -> {
phone.sendMes();
}, "线程一").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "线程二").start();
}
}
class Phone {
public synchronized void sendMes() {
System.out.println(Thread.currentThread().getName() + "发短信");
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName() + "打电话");
}
}
结果:
就算是发短信方法里休眠了两秒答案还是一样的。因为实例对象只有phone一个。线程一已经拿到了该对象,其余线程只能等待。
public synchronized void sendMes() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "发短信");
}
但是我们使用两个对象分别调用这两个方法:
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(() -> {
phone.sendMes();
}, "线程一").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone1.call();
}, "线程二").start();
最后结果将是:
两个线程拿到对象不同就不会出现等待。
但是如果将phone两个方法改成静态的方法其余逻辑还是上一步相同。
public static synchronized void sendMes() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "发短信");
}
public static synchronized void call() {
System.out.println(Thread.currentThread().getName() + "打电话");
}
结果又是相反的,线程一会执行完线程二才能执行。
这是因为静态方法的类对象.class只有一个,而且只会有一个,synchronized方法锁的是这个类对象,那么线程二就只能等待。
再进一步: 如果一个是静态的同步方法,一个普通的同步方法,只创建一个实例对象又会是谁先执行?
import java.io.Serializable;
import java.util.HashSet;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
public class testDemo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendMes();
}, "线程一").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "线程二").start();
}
}
class Phone {
public static synchronized void sendMes() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "发短信");
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName() + "打电话");
}
}
那肯定是线程二先执行了;因为两者synchronized锁的都不是同一个,一个锁的是类模板,一个锁的是实例变量。
Lock接口下有三种实现类:
- ReentrantLock :可重入锁
- ReentrantReadWriteLock.ReadLock写锁 (独占锁)
- ReentrantReadWriteLock.WriteLock 读锁(共享锁)
ReentrantLock :
//默认构造非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 也可以通过传入true设置为公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁:
顾名思义,很公平,不管你们谁执行时间长短,排队去,谁先来谁就可以获取锁并独占。有时效率极低。
底层源码调用过程:
final void lock() {
acquire(1);
}
//调用tryAcquire,失败将向等待队列加入一个等待者
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//只有队首的等待线程才可以获取锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
非公平锁:
非公平锁它只要发现锁是空闲的他不会管前面有没有它都会视图去抢占
与公平锁源码对比发现不会判断等待队列
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//与
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}