1.环境
2.JUC简介与线程基础
2.1.juc是什么?
主要指的就是java.util下面的包:java.util.concurrent
2.2.java默认两个线程
main和GC
2.3.java能否直接调用线程
java没办法调用线程,底层是通过调用c++来调用的
2.4.并发,并行
并发是交替的(多个线程操作同一资源,快速交替,可以充分利用cpu资源)
并行是同时的(多核情况下,真正的执行多个任务)
2.5.线程状态
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
2.6.slep和wait区别
-
wait来自Object基础类,sleep来自java.util.concurrent
-
两者最主要的区别在于:sleep ⽅法没有释放锁,⽽ wait ⽅法释放了锁 。
-
两者都可以暂停线程的执⾏。
-
Wait 通常被⽤于线程间交互/通信,sleep 通常被⽤于暂停执⾏。
-
wait() ⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify() 或者
notifyAll() ⽅法。sleep() ⽅法执⾏完成后,线程会⾃动苏醒。或者可以使⽤ wait(long
timeout)超时后线程会⾃动苏醒。 -
wait必须在同步代码块中,sleep可以作用任何地方
-
wait不需要捕获异常,sleep必须要捕获异常
3.Lock锁
3.1synchronized锁
主要两种写法:synchronized同步方法(锁住的是方法的调用对象)和synchronized代码块(代码块写法可以把类传进去)
package com.juc.demo01;
/**
* Created by yj on 2020/8/18 21:07
*/
public class TestTicket {
public static void main(String[] args){
Ticket ticket = new Ticket();
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();
new Thread(()->{
for(int i=0;i<50;i++){
ticket.sale();
}
},"D").start();
}
}
class Ticket{
//属性,方法
private int number =50;
//买票方式
public synchronized void sale(){
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余"+number);
}
}
}
如果没加锁synchronized,就会出现如下情况,造成票被多个线程去抢购,这样就会线程不安全,而加锁后就类似于一个门锁,每次都只有一个线程去拿到锁然后进门里面,然后拿完票后就出去了,然后下一个线程拿到门锁进去,依次类推
r
3.2lock锁
java.util.concurrent.locks.Lock包下的Lock锁主要的实现类如下。
注意ReentrantLock是他的实现类
上面写法改为lock锁来写
package com.juc.demo01;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created by yj on 2020/8/18 21:07
*/
public class TestTicket02 {
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();
new Thread(()->{
for(int i=0;i<50;i++) ticket.sale();
},"D").start();
}
}
class Ticket2{
//属性,方法
private int number =50;
Lock lock = new ReentrantLock();
//买票方式
public void sale(){
try {
//1.加锁
lock.lock();
//2.业务代码
if(number>0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余"+number);
}
}catch (Exception e){
e.printStackTrace();
}finally {
/3.解锁
lock.unlock();
}
}
}
3.3synchronized和Lock(这儿笔记都是和ReentrantLock比较的)区别
- synchronized是java内置关键字,Lock则是java.util.concurrent.locks.Lock的一个类(作为一个类灵活度比较高)
- Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
- Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,会死锁
- Synchronized 线程 1(获得锁后阻塞)、线程2(就会一直等下去);Lock锁就不一定会等待下
去,因为可以用tryLock()去尝试去获取锁; - Synchronized 是可重入的,不可以中断,是非公平锁;Lock也是可重入锁(ReentrantLock是他的实现类),但是他可以中断锁,默认非公平,可以自行设置
- Synchronized适合锁少量代码同步问题,Lock则适合大量的同步代码
3.4公平锁和非公平锁
在ReentrantLock中有两个属性公平锁和非公平锁,这样可以通过方法进行设置是否是公平锁,默认非公平的。
非公平锁:可以插队
公平锁:分先来后到
为什么默认是非公平的,如果用公平的,一个线程比如时间过长,会造成后面等待很久。而非公平的会先让时间短的先插队执行。
tips:阻塞和死锁区别
阻塞:资源不足,出现排队现象
死锁:多个线程抢同一个资源,都不释放,然后都拿不到
4.生产者和消费者问题
下面这些都是通过一个标志位控制一条线程生产,一条线程消费,核心的资源方法都写在Data里面,而线程的话在外面调用方法就可以了。
资源里面核心方法很简单就是三步:判断是否等待,等待的话就会将当前线程挂起,释放锁==》执行业务方法==》唤醒其他的线程,看看其他线程现在可以执行(其他线程会根据标志位来判断自己是否执行)。
4.1synchronized版的生产与消费
注意:下面不用if是为了防止虚假唤醒:没有通知,中断或者超时时候一个线程被唤醒。等待应该总是出现在循环中。
package com.pro_com;
import lombok.Synchronized;
import java.util.Date;
/**
* Created by yj on 2020/8/18 22:53
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
//这样的话开启两个线程,一个线程执行add,一个del
//第一个线程add后+1,这时候第一个线程他就只能等待了,因为数字为1
//然后第二个线程del后-1,这时候第二个线程等待,数字为0了,这时候第一个线程又可以执行了,不等待了
//这样就实现了进程之间通信
new Thread(()->{
for(int i=0;i<50;i++){
try {
data.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for(int i=0;i<50;i++){
try {
data.del();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for(int i=0;i<50;i++){
try {
data.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for(int i=0;i<50;i++){
try {
data.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
}
class Data{//这儿都是资源
//设置了一个flag用以判断
private int number = 0;
public synchronized void add() throws InterruptedException {
//1.判断是否该等待,这儿不用if是为了防止虚假唤醒:没有通知,中断或者超时时候一个线程被唤醒。所以等待应该总是出现在循环中
//不等于0要一直等待,不能只等待一次
while(number!=0){
this.wait();
}
//2业务方法
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//3.唤醒
this.notifyAll();
}
public synchronized void del() throws InterruptedException {
//1.判断是否该等待
while(number==0){
this.wait();
}
//2业务方法
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
//3.唤醒
this.notifyAll();
}
}
4.2JUC版的生产与消费
在java.util.concurrent下面找到这个Condition接口,他具有一些方法可以替代wait和notify操作,这里面lock取代了synchronized方法和语句,Condition则取代了对象监视器的使用方法。
也就是他和synchronized其实很多方法是一样的。下面方法是等价的。
-
写法一
写法一其实就是将上面的wait等方法直接换成了JUC的方法。
package com.pro_com; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Created by yj on 2020/8/19 15:14 */ public class B { public static void main(String[] args) { Data2 data = new Data2(); 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 Data2{ // 数字 资源类 private int number = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); //condition.await(); // 等待 //condition.signalAll(); // 唤醒全部 //+1 public void increment() throws InterruptedException { lock.lock(); try { // 业务代码 while (number!=0){ //0 // 等待 condition.await(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); // 通知其他线程,我+1完毕了 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } //-1 public void decrement() throws InterruptedException { lock.lock(); try { while (number==0){ // 1 // 等待 condition.await(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); // 通知其他线程,我-1完毕了 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
-
写法二
JUC的Condition监视器可以精准的去唤醒线程,可以让线程去按照顺序去执行,具体写法如下所示。
上面两种写法则不能让其按照顺序来执行,每次执行完当前线程,下一次都是随机的。这儿的话就体现出了JUC版本的优势,它可以在当前线程执行完,可以精准的去唤醒下一个线程。
package com.pro_com; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Created by yj on 2020/8/19 15:40 */ /** * 下面完成了这样一件事,就是需要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(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); private int number = 1; // 1A 2B 3C public void printA(){ lock.lock(); try { // 业务,判断-> 执行-> 通知 while (number!=1){ // 等待 condition1.await(); } System.out.println(Thread.currentThread().getName()+"=>AAAAAAA"); // 唤醒,唤醒指定的人,B 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()+"=>BBBBBBBBB"); // 唤醒,唤醒指定的人,c 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()+"=>BBBBBBBBB"); // 唤醒,唤醒指定的人,c number = 1; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
5.到底什么是锁
5.1锁住的到底是什么
一定要注意锁的不是方法,对普通方法加锁,之后调用的时候,锁的是调用的对象。
如果对静态方法加锁,那么就是锁的是静态方法所在的类。
5.2只有一个对象的时候测试
package com.lock;
/**
* Created by yj on 2020/8/19 16:14
*/
import java.util.concurrent.TimeUnit;
/**
* 实际上下面不管是不是在方法里面加上延迟都是按顺序执行
* 发短信和打电话,主要原因如果加了synchronized在方法上,他锁住的对象是这个方法调用者
* 比如这儿就是锁的phone这个对象,在第一个线程执行phone.sendSms(),这个方法的调用对象phone就被锁住了
* 这样的话其他线程都只能等待,而他用完后会自动释放锁,然后第二个线程执行phone.call()方法,拿到这个锁
* 一定要注意锁的不是方法,对方法加锁,之后调用的时候,锁的是调用的对象,这儿对象都是同一个。
* */
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
//锁的存在
new Thread(()->{
phone.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
// synchronized 锁的对象是方法的调用者!、
// 两个方法用的是同一个锁,谁先拿到谁执行!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
5.3两个对象的时候测试
package com.lock;
/**
* Created by yj on 2020/8/19 16:14
*/
import java.util.concurrent.TimeUnit;
/**
* 1.下面不加锁的hello,是不受锁的影响的,该怎么执行怎么执行
* 2.下面有两个对象的时候,线程执行相互不影响,因为sendSms有延迟,所以这儿先执行打电话,再执行发短信
* 如果这儿只有一个对象,两个线程还是这么调用的话,因为锁的关系,就像前面一样,即使发短信有延迟,另一个线程也得等待
*/
public class Test2 {
public static void main(String[] args) {
// 两个对象,两个调用者,两把锁!
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone2{
// synchronized 锁的对象是方法的调用者!
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
// 这里没有锁!不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
5.4对两个静态加锁方法进行测试
如果在静态方法上测试的话,因为静态方法是类加载的时候加载的,所以锁的就是这个类。和上面是不一样的,上面锁的是实例对象。
package com.lock;
import java.util.concurrent.TimeUnit;
/**
* Created by yj on 2020/8/19 16:14
*/
/**
*下面是先打印发短信,再打印打电话
*/
public class Test3 {
public static void main(String[] args) {
// 两个对象的Class类模板只有一个,static,锁的是Class
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
// Phone3唯一的一个 Class 对象
class Phone3{
// synchronized 锁的对象是方法的调用者!
// static 静态方法
// 类一加载就有了!锁的是Class
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
5.5对一个个静态加锁方法和一个普通方法进行测试
package com.lock;
/**
* Created by yj on 2020/8/19 16:15
*/
import java.util.concurrent.TimeUnit;
/**
* 1、1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印打电话,后打印发短信,因为后面那个线程他不是静态的,他锁的是对象所以前面静态方法把类给锁了,对他没有影响,所以先执行打电话(发短信有延迟)。
* 2、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印打电话,后打印发短信,两个对象的情况下,本来对象就没有被锁,而第二个线程拿到对象锁就够了,所以还是先执行打电话
*/
public class Test4 {
public static void main(String[] args) {
// 两个对象的Class类模板只有一个,static,锁的是Class
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
//锁的存在
new Thread(()->{
phone1.sendSms();
},"A").start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
// Phone3唯一的一个 Class 对象
class Phone4{
// 静态的同步方法 锁的是 Class 类模板
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 普通的同步方法 锁的调用者
public synchronized void call(){
System.out.println("打电话");
}
}