记录学习Java JUC
1、JUC介绍 源码+官方文档
java.util.concurrent
普通的线程代码Thread
Runnable没有返回值,效率相比Callable相对较低
2、线程与进程
进程、线程
进程:就比如你桌面上的任何一个软件,QQ、微信这些
一个进程可以包含多个线程,至少也有一个线程
Java默认就是有两个线程:①main线程。②GC垃圾回收线程
但是有一个问题就是?
Java真的可以开启线程吗? —–不可以!
Thread部分源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public synchronized void start () { if (threadStatus != 0 ) throw new IllegalThreadStateException (); group.add(this ); boolean started = false ; try { start0(); started = true ; } finally { try { if (!started) { group.threadStartFailed(this ); } } catch (Throwable ignore) { } } } private native void start0 () ;
并发、并行
并发(多线程操作同一个资源)
CPU单核,模拟出来多条线程,天下武功,唯快不破,快速交替
并行(多个人一次行走)
查询cpu核数
1 2 3 4 5 6 7 public class Test1 { public static void main (String[] args) { System.out.println(Runtime.getRuntime().availableProcessors()); } }
并发编程的本质:充分利用CPU的资源
3、回顾多线程 线程有几个状态 – 6种 直接就是能够看源码
public enum State {
/**
* 新建
*/
NEW,
/**
* 运行
*/
RUNNABLE,
/**
* 阻塞
*/
BLOCKED,
/**
* 等待,死死的等
*/
WAITING,
/**
* 超时等待
*/
TIMED_WAITING,
/**
* 停止
*/
TERMINATED;
}
wait和sleep的区别
来自不同的类
wait—->Object
sleep—->Thread
关于锁的释放
wait会释放锁
sleep不会释放锁,抱着锁睡觉
使用的范围是不同的
wait必须在同步代码块中
sleep可以在任何地方睡
4、传统的Synchronized锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class JUCDemo01 { public static void main (String[] args) throws InterruptedException { Ticket ticket = new Ticket (); new Thread (() -> { for (int i=0 ;i<50 ;i++){ ticket.sale(); } }, "A" ).start(); } } class Ticket { private int number = 50 ; public synchronized void sale () { if (number > 0 ){ System.out.println(Thread.currentThread().getName() + "买了第" + (number--) + "张票" ); } } }
5、Lock锁 加锁,释放锁
实现类(可重入锁,读锁,写锁)
可重入锁构造方法
公平锁:十分公平,可以先来后到
非公平锁:十分不公平,可以插队(默认)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class JUCDemo02 { public static void main (String[] args) { Ticket2 ticket = new Ticket2 (); new Thread (() -> { for (int i = 0 ; i < 50 ; i++) { ticket.sale(); } }, "A" ).start(); new Thread (() -> { for (int i = 0 ; i < 50 ; i++) { ticket.sale(); } }, "B" ).start(); new Thread (() -> { for (int i = 0 ; i < 50 ; i++) { ticket.sale(); } }, "C" ).start(); } } class Ticket2 { private int number = 50 ; Lock lock = new ReentrantLock (); public void sale () { lock.lock(); try { if (number > 0 ) { System.out.println(Thread.currentThread().getName() + "买了第" + (number--) + "张票" ); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
6、Synchronized和Lock区别
Synchronized 内置的java关键字,Lock是一个java类
Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
Synchronized 会自动释放锁,Lock必须手动释放锁,如果不释放锁,会导致死锁!
Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock就不一定会等待下去
Synchronized,可重入锁,不可以中断,非公平;Lock,可重入锁,可以判断锁,非公平(自己可以设置)
Synchronized 使用锁少量的代码同步问题,Lock适合锁大量的同步代码
7、传统的生产者消费者问题,防止虚假唤醒
生产者和消费者问题 Synchronized 版本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public class JUCDemo2 { 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.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B" ).start(); } } class Data { private int number = 0 ; public synchronized void increment () throws InterruptedException { if (number != 0 ) { this .wait(); } number++; System.out.println(Thread.currentThread().getName() + "==>" + number); this .notifyAll(); } public synchronized void decrement () throws InterruptedException { if (number == 0 ) { this .wait(); } number--; System.out.println(Thread.currentThread().getName() + "==>" + number); this .notifyAll(); } }
问题存在,如果是A B C D 四个线程(可能不会出现上面预期的结果,可能是有2,3出现) -》 虚假唤醒
解决:把if 改成while
用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
拿两个加法线程A、C来说,比如A先执行,执行时调用了wait方法,那它会等待,此时会释放锁,那么线程C获得锁并且也会执行wait方法,两个加线程一起等待被唤醒。此时减线程中的某一个线程执行完毕并且唤醒了这俩加线程,那么这俩加线程不会一起执行,其中A获取了锁并且加1,执行完毕之后C再执行。如果是if的话,那么A修改完num后,C不会再去判断num的值,直接会给num+1。如果是while的话,A执行完之后,C还会去判断num的值,因此就不会执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 public class A { 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.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B" ).start(); new Thread (() -> { for (int i = 0 ; i < 10 ; i++) { try { data.increment(); } 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(); } } class Data { 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 == 0 ) { this .wait(); } number--; System.out.println(Thread.currentThread().getName() + "==>" + number); this .notifyAll(); } }