1.线程和进程
1.java默认有两个线程:
1.main线程
2.GC垃圾回收线程
2.java真的可以开启线程么?
答案是否定的,其实底层本地去调用是c++的方法,因为java是运行在虚拟机上的,无法操作硬件!
原理如下:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
//重点:线程内部调用的是start0的本地方法
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
//调用的其实是内部的start0方法
private native void start0();
3.并发、并行
3.1 并发:多个线程操作同一个资源
CPU 一核情况下,模拟出来多条线程,快速交替,营造出共同进行的假象!
3.2 并行:
CPU 多核情况下,多个线程可以同时执行!
程序获取cpu核数:
System.out.println("cpu核数:"+
Runtime.getRuntime().availableProcessors()
);
如何提高执行效率呢:
1.并发:CPU资源的充分利用
2.并行:线程池
多线程在多核cpu下的执行速率快于单线程,因为多核cpu下,多个线程是同时执行的,
但是单核cpu下,多线程的执行速率慢于单线程的速率,因为单核下,多线程是交替进行,会有上下文的切换!
结论:
1.单核CPU下,多线程不能实际的提高程序运行效率,只是为了能够在不同的任务间切换,不同线程轮流使用CPU,
会涉及到下上文切换,会损失一部分的速率
2.多核cpu可以并行跑多个线程,但是是否能够提升效率还得分情况:
2.1 有的任务:经过精心设计,将任务拆分,并行执行,当然可以提高程序得运行效率,但是不是所有得任务都能拆分
3.IO操作不占用CPU,只是我门一半拷贝文件使用的是【阻塞IO】,这时相当于线程虽然不用cpu,但需要一直等待IO结束,没能充分利用线程
后面会有【非阻塞IO】和【异步IO优化】
1.线程的状态
总计6状态:
1. NEW(新建)
2. RUNNABLE(运行)
3. BLOCKED(阻塞)
4. WAITING(等待)
5. TIMED_WAITING(超时等待,超时后就过)
6. TERMINATED(终止)
1.wait和sleep的区别
1.1 来自不同的类
wait-->Object
sleep--->Thread
1.2 关于锁的释放
wait:会释放锁, sleep:不会释放锁
1.3 使用的范围:
wait--->只能在同步代码块中使用
sleep--->可以在任意位置上使用
1.4 是否需要捕获异常
wait:不需要捕获异常
sleep:必须要捕获异常
2.Lock锁(重点)
传统的synchronized
一个简单的场景:售票
重点:
1.并发就是多个线程操作同一个资源,资源类尽量是一个纯粹的类,不和其他做耦合,之前的是在资源类上实现runable接口,这样不太好!
2.lambda表达式的使用:
示例如下:
public class SellTicket {
public static void main(String[] args) {
/*
1.并发:多个线程操作同一个资源类,把资源丢入到线程中
2.lambda表达式:(请求参数)->{代码}
*/
Ticket ticket=new Ticket();
new Thread(()->{
for(int i =0;i<=50;i++){
ticket.sellTicket();
}
},"A").start();
// 重点2:不使用lambda表达式,定义匿名内部类,并实现其run方法
//发现lambda表达式是省略了匿名内部类命和方法名
new Thread(new Runnable() {
@Override
public void run() {
for(int i =0;i<=50;i++){
ticket.sellTicket();
}
}
}, "B").start();
new Thread(()->{
for(int i =0;i<=50;i++){
ticket.sellTicket();
}
},"C").start();
}
}
//重点3:纯粹的资源类,不和任何做耦合,不实现runable接口
class Ticket{
private int num=50;
//重点4:方法上加锁,可以按照预期卖票
public synchronized void sellTicket(){
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖票!剩余票:"+num--);
}
}
}
上述例子使用synchronized实现了锁,那如何根据Lock接口实现锁呢?
由上述截图可知,Lock接口有三个实现类:
1.ReentrantLock(普通锁-->最常用)
2.ReentrantReadWriteLock.ReadLock(读写锁-->读锁)
3.ReentrantReadWriteLock.WriteLock(读写锁-->写锁)
具体的实现代码如下:
public class Juc_Test_Lock {
public static void main(String[] args) {
int num=50;
Ticket ticket=new Ticket(num);
new Thread(()->{for(int i=0;i<num;i++) ticket.sell();},"A").start();
new Thread(()->{for(int i=0;i<num;i++) ticket.sell();},"B").start();
new Thread(()->{for(int i=0;i<num;i++) ticket.sell();},"C").start();
}
}
@AllArgsConstructor
@NoArgsConstructor
class Ticket{
public Ticket(int ticketNum) {
this.ticketNum = ticketNum;
}
private int ticketNum;
//重点1:可重入锁的使用
Lock lock=new ReentrantLock();
public void sell(){
//重点2:步骤1:加锁
lock.lock();
try {
//步骤2:trycatch写业务逻辑
if(ticketNum>0)
System.out.println(Thread.currentThread().getName()+"售票:剩余-->"+ticketNum--);
} catch (Exception e) {
e.printStackTrace();
} finally {
//重点3:步骤3:解锁
lock.unlock();
}
}
}
Lock lock=new ReentrantLock();代码细究:底层代码如下:
{
构造器1:发现创建的是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
构造器2:传入参数,创建对应的锁,默认是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
1.公平锁FairSync:不能插队,排队进行
2.非公平锁NonfairSync: 可以插队,默认都是非公平锁(包括synchronized )
重点:synchronized和lock锁的区别
1.synchronized是内置关键字,Lock是java类
2.synchronized无法判断获取锁的状态,Lock可以判断是否获取锁
3.synchronized会自动会释放锁,Lock必须要手动释放锁,要不会造成死锁
4.synchronized(线程1获取锁,线程2等待,即使线程1阻塞);
Lock锁就不一定会等待下去!
5.synchronized 可重入锁,不可以中断,非公平
Lock,可重入锁,可以判断锁,非公平(可以自己设置)
6.synchronized:适合锁少量的同步代码,
Lock适合锁大量的同步代码
问题:锁是什么?如何判断锁的是谁?
1.对象(可能是多个)
2.Class(只能是一个)
生产者和消费者的synchronized 版本以及虚假唤醒问题
先看一个场景:
一个卖面的面馆,有一个做面的厨师和一个吃面的食客,需要保证,厨师做一碗面,食客吃一碗面,
不能一次性多做几碗面,更不能没有面的时候吃面;
按照上述操作,进行十轮做面吃面的操作。
样例代码:
public class Juc_test {
public static void main(String[] args){
Noodels noodels=new Noodels();
//重点1:创建两个线程:1厨子造面 2.食客吃面,先做10碗
new Thread(()->{
try {
for (int i=0;i<=10;i++){
noodels.makeNoodeles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"厨子").start();
new Thread(()->{
try {
for (int i=0;i<=10;i++){
noodels.eatNoodeles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"食客").start();
}
}
class Noodels{
//面的数量
private int num=0;
/*
做面方法
*/
public synchronized void makeNoo生产者和消费者的synchronized 版本以及虚假唤醒问题deles() throws InterruptedException {
if(num>0){
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"造面了!当前面数:"+num);
//面已做好,唤醒食客
this.notifyAll();
}
public synchronized void eatNoodeles() throws InterruptedException {
if (num==0){
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"造面了!当前面数:"+num);
this.notifyAll();
}
}
运行结果如下:很满意,达到了效果,做一碗吃一碗的水平!
厨子造面了!当前面数:1
食客造面了!当前面数:0
厨子造面了!当前面数:1
食客造面了!当前面数:0
厨子造面了!当前面数:1
食客造面了!当前面数:0
厨子造面了!当前面数:1
问题来了:如果多个线程呢,即两个厨子,两个食客呢??改造代码,只需要在测试类里多加几个线程!
new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"厨子A").start();
new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"厨子B).start();
new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"食客A").start();
new Thread(()->{ for (int i=0;i<=10;i++){noodels.makeNoodeles();}},"食客B").start();
运行结果:打破了吃一碗造一碗的逻辑了,并且明明加了synchronized锁,为什么会这样呢?这里就涉及到了线程的虚假唤醒!
厨子A造面了!当前面数:1
食客A造面了!当前面数:0
厨子B造面了!当前面数:1
厨子A造面了!当前面数:2
厨子B造面了!当前面数:3
...
虚假唤醒
上面的问题就是"虚假唤醒"。
当我们只有一个厨师一个食客时,只能是厨师做面或者食客吃面,并没有其他情况;
但是当有两个厨师,两个食客时,就会出现下面的问题:
1.初始状态
2.厨师A得到操作权,发现面的数量为0,可以做面,面的份数+1,然后唤醒所有线程;
3.厨师B得到操作权,发现面的数量为1,不可以做面,执行wait操作;
4.厨师A得到操作权,发现面的数量为1,不可以做面,执行wait操作;
5.食客甲得到操作权,发现面的数量为1,可以吃面,吃完面后面的数量-1,并唤醒所有线程;
6.此时厨师A得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程;
7.此时厨师B得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程;
if(num != 0){
this.wait();
}
出现虚假唤醒的原因:
是从阻塞态到就绪态再到运行态没有进行判断(num的判断),直接进行下述的+1或-1,
所以我们只需要让其每次得到操作权时都进行判断就可以了;
解决办法:将if判断改为while,每次都会判断,唤醒以后都先判断条件是否成立!
while(num != 0){
this.wait();
}
JUC版的生产者和消费者
使用Lock和Condition实现上述生产者和消费者问题:
样例代码:
class Noodels{
//面的数量
private int num=0;
//重点1:获取lock锁(可重入锁)
Lock lock = new ReentrantLock();
//重点2:获取condition对象
Condition condition = lock.newCondition();
/*
做面方法
*/
public void makeNoodeles() throws InterruptedException {
//重点3:加锁,不用synchronized关键字
lock.lock();
try {
while (num>0){
//重点4:使用condition.await来阻塞队列
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+"造面了!当前面数:"+num);
//重点5:condition.signalAll();来解锁
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//重点5:解锁
lock.unlock();
}
}
public void eatNoodeles() throws InterruptedException {
lock.lock();
try {
while (num==0){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"造面了!当前面数:"+num);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
问题:如何精确唤醒呢?例如A,B,C,D四个线程,如何精确的执行顺序A->B->C->D
实现如下:
/**
* Bruk.liu
* A执行完调用B,B执行完调用C,C执行完调用A
*/
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printC();
}
},"C").start();
}
}
//资源类
class Data3{
//创建Lock锁
private Lock lock = new ReentrantLock();
//同步监视器,创建三个监视器
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int number = 1;//1就是A执行,2就是B执行
public void printA(){
lock.lock();
try {
while(number != 1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"===>AAAAAAAAA");
//唤醒指定线程
number = 2;
//调用指定监视器,唤醒指定线程
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while(number != 2){
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"===>BBBBBBBB");
//唤醒指定线程
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while(number != 3){
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"===>CCCCCCCCC");
//唤醒指定线程
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8锁现象
1.synchronized锁的对象是方法调用者!
2.static synchronized...锁的是class这个类!
示例如下:
1.两种方法都sendMessage/call都加上锁synchronized,锁的是方法的调用者!
public class Juc_Test_Lock {
public static void main(String[] args) throws InterruptedException {
//同一个资源对象Phone,所以所的是同一资源对象phone
Phone phone=new Phone();
new Thread(()->{phone.sendMessage();},"发短信!").start();
//重点1:两个线程间休眠4秒,能明显看出哪个线程先执行
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone.call();},"打电话!").start();
}
}
class Phone{
//重点2:方法加锁,并在打印前加入休眠,能明显看出哪个先执行!
public synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发送短信");
}
public synchronized void call(){
System.out.println("打电话!");
}
}
输出:因为两个方法都加了synchronized,锁的是方法的调用者,方法调用者是同一对象phone,所以锁是同一把锁!
发送短信
打电话!
2.两个对象:
创建两个对象,因为synchronized锁的是方法调用者,此处是 phone1和phone2,不是同一对象,所以线程2不会等待线程1执行完毕
Phone phone1=new Phone();
Phone phone2=new Phone();
new Thread(()->{phone1.sendMessage();},"发短信!").start();
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone2.call();},"打电话!").start();
结论:
打电话!
发送短信
3.static synchronized:锁定的是这个类模板
代码示例如下:
public class Juc_Test_Lock {
public static void main(String[] args) throws InterruptedException {
Phone phone=new Phone();
new Thread(()->{phone.sendMessage();},"发短信!").start();
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone.call();},"打电话!").start();
}
}
class Phone{
重点1:方法上使用static synchronized修饰
public static synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发送短信");
}
public static synchronized void call(){
System.out.println("打电话!");
}
}
输出:static synchronized:锁定的是这个类模板,线程2必须等待线程1执行完毕后才能执行!因为这是一把锁
发送短信
打电话!
4.上述情况下,方法加了static synchronized,但是是两个对象
重点1:创建了两个对象去执行static synchronized修饰的方法
Phone phone1=new Phone();
Phone phone2=new Phone();
new Thread(()->{phone1.sendMessage();},"发短信!").start();
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone2.call();},"打电话!").start();
输出:发现线程2必须等线程1执行完毕后才执行,因为static synchronized锁的是类模板,是一般锁!
发送短信
打电话!
5.staic synchronized和synchronized混用:
public class Juc_Test_Lock {
public static void main(String[] args) throws InterruptedException {
重点1:单个对象
Phone phone=new Phone();
new Thread(()->{phone.sendMessage();},"发短信!").start();
TimeUnit.SECONDS.sleep(1);;
new Thread(()->{phone.call();},"打电话!").start();
}
}
class Phone{
重点2:使用了static synchronized
public static synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发送短信");
}
重点3:使用synchronized
public synchronized void call(){
System.out.println("打电话!");
}
}
输出:因为staic synchronized和synchronized锁的是不同的对象,synchronized锁的是方法调用者phone对象,而staic synchronized锁的是Phnoe类模板
所以是两把不同的锁,所以线程2不会等待1执行结束才执行!
打电话!
发送短信