17.多线程

多线程

程序、进程、线程的概念

程序:是指令和数据的有序集合,是一个静态的概念。比如,在电脑中,打开某个软件,就是启动程序。

进程:是执行程序的一次执行过程,是一个动态的概念,是系统资源的分配单位。

当我们运行一个程序后,这个程序的进程就会启动。将来可以在任务管理器中查看进程。

线程:一个进程中,可以包含若干个线程,至少包含一个线程。线程是CPU调度和执行的单位。

在同一个进程中,可以执行多个任务,每个任务就可以看成是一个线程。

比如:放音乐的时候,可以一边放音乐,一边下载其他音乐;

看电影的时候,播放器中的画面、声音、弹幕,都是独立的线程。

总结:

进程可以指运行中的程序,特点:动态性、独立性、并发性。

线程是进程内部的执行单位,它是程序中一个顺序控制流程。

多线程就是一个进程中,同时有多个线程,用于完成不同的工作。

Java中的线程

主线程、子线程

main()方法就是主线程的入口,其中执行的内容就是主线程内容,其他的子线程需要通过特殊的类,去创建在main()方法中执行,main()方法必须最后执行完毕,因为它要执行各种关闭操作。

package com.day17.thread1;public class ThreadDemo {public static void main(String[] args) {//测试main()方法中的线程//返回当前main()方法中的线程对象Thread thread = Thread.currentThread();//返回当前线程名称System.out.println(thread.getName());//设置main线程的名字thread.setName("主线程");System.out.println(thread.getName());}
}

Java中线程的创建和启动

1、继承Thread类

构造方法

Thread()

分配一个新的 Thread对象。

Thread(Runnable target)

分配一个新的 Thread对象。

Thread(Runnable target, String name)

分配一个新的 Thread对象。

Thread(String name)

分配一个新的 Thread对象。

常用普通方法

static Thread currentThread()

返回对当前正在执行的线程对象的引用。

String getName()

返回此线程的名称。

void join()

等待这个线程死亡。

void run()

如果这个线程使用单独的Runnable运行对象构造,则调用该Runnable对象的run()方法;

否则,此方法不执行任何操作并返回。

void setName(String name)

将此线程的名称更改为等于参数 name 。

void setPriority(int newPriority)

更改此线程的优先级。

static void sleep(long millis)

使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。

void start()

导致此线程开始执行; Java虚拟机调用此线程的run方法。

static void yield()

对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。

继承Thread类创建多线程的步骤

1、继承Thread类

2、重写run方法,将来要其他线程要执行的逻辑内容,都放在run方法中

3、实例化子类对象后,调用start方法来启动线程,

调用start方法的时候,是在告诉JVM分配线程去调用run()方法

调用run()方法和start()方法的区别

run方法是继承了Thread类重写的一个方法,

当我们调用start()方法的时候,JVM会启动一个线程,并执行run()方法中的内容,

直接调用run()方法,会把run()当作main()线程下的一个普通方法执行,

不会在某个线程中执行,所以并不是多线程工作。

package com.day17.thread1;public class ThreadDemo01 extends Thread{//继承Thread类创建多线程的步骤//1、继承Thread类//2、重写run方法,将来要其他线程要执行的逻辑内容,都放在run方法中//3、实例化子类对象后,调用start方法来启动线程//调用start方法的时候,是在告诉JVM分配线程去调用run()方法@Overridepublic void run() {for (int i = 0; i < 10; i++) {//获取当前线程名称System.out.println(Thread.currentThread().getName() + ":" + i);}}public static void main(String[] args) {//main方法既是程序的入口,也是一个主线程,也是子线程启动的入口ThreadDemo01 thread01 = new ThreadDemo01();thread01.setName("子线程1");thread01.start();//启动线程//直接调用run方法,JVM默认会将run方法作为普通方法交给main线程来执行// thread01.run();//        ThreadDemo01 thread02 = new ThreadDemo01();
//        thread02.setName("子线程2");
//        thread02.start();}
}

2、实现Runnable接口

1、实现Runnable接口,重写run()方法

2、创建对象,把对象作为参数,传递到Thread类中中,创建Thread对象,创建出来的Thread对象才是真正的线程对象

3、通过线程对象,调用start()方法

package com.day17.thread2;//1、实现Runnable接口,重写run()方法
//2、创建对象,把对象作为参数,传递到Thread类中中,创建
//Thread对象,创建出来的Thread对象才是真正的线程对象
//3、通过线程对象,调用start()方法
public class ThreadDemo implements Runnable{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+": "+i);}}public static void main(String[] args) {//先创建Runnable接口实现类对象ThreadDemo thread01 = new ThreadDemo();//        //再通过实现类对象,创建Thread对象//        Thread t1 = new Thread(thread01);//        //通过Thread对象调用start()方法//        t1.start();//        //将创建Runnable接口对象,作为参数直接传入Thread类的构造方法//        Thread t2 = new Thread(new ThreadDemo(), "子线程2");//        t2.start();Thread t1 = new Thread(thread01, "小红");Thread t2 = new Thread(thread01, "小明");t1.start();t2.start();}
}

使用匿名内部类实现Runnable接口,实现多线程效果

package com.day17.thread2;
//使用匿名内部类实现Runnable接口,实现多线程效果
public class ThreadDemo02 {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+": "+i);}}},"小明").start();new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+": "+i);}}},"小红").start();}
}

lambda表达式的写法实现Runnable接口,完成多线程实现

是一种函数式编程

语法:()->{} ()中将来传递参数,{}写重写接口的方法

package com.day17.thread2;
//lambda表达式的写法实现Runnable接口,完成多线程实现
//是一种函数式编程
//语法:()->{}  ()中将来传递参数,{}写重写接口的方法
public class ThreadDemo03 {public static void main(String[] args) {new Thread(()->{for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+": "+i);}},"小红").start();new Thread(()->{for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + ": " + i);}},"小明").start();}
}

3、实现Callable接口

实现步骤:

1.实现Callable接口

2.以实现类为参数,创建FutureTask对象

3.将FutureTask对象作为参数,创建Thread对象

4.调用start方法

package com.day17.thread7;import java.util.concurrent.Callable;public class CallableDemo implements Callable {@Overridepublic Integer call() throws Exception {System.out.println(Thread.currentThread().getName()+"->call方法被执行了!");return 10;}
}
package com.day17.thread7;import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class TestCallable {public static void main(String[] args) throws ExecutionException, InterruptedException {//创建FutureTask对象FutureTask<Integer> futureTask = new FutureTask<Integer>(new CallableDemo());new Thread(futureTask).start();//获取返回call方法的返回值,要通过FutureTask对象获取System.out.println(futureTask.get());}
}

4、使用Executors工具类创建线程池

package com.day17.thread8;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ExecutorsDemo extends Thread{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"正在执行...");}public static void main(String[] args) {//通过Executors工具类创建线程池//可变大小的线程池ExecutorService pool = Executors.newCachedThreadPool();//固定大小的线程池
//        ExecutorService pool = Executors.newFixedThreadPool(2);//单个的
//        ExecutorService pool = Executors.newSingleThreadExecutor();//创建线程对象ExecutorsDemo t1 = new ExecutorsDemo();ExecutorsDemo t2 = new ExecutorsDemo();ExecutorsDemo t3 = new ExecutorsDemo();ExecutorsDemo t4 = new ExecutorsDemo();//把线程对象放入池子中pool.execute(t1);pool.execute(t2);pool.execute(t3);pool.execute(t4);//关闭线程池pool.shutdown();}
}
package com.day17.thread8;import java.util.Date;public class ExecutorsDemo02 implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"开始执行:"+new Date());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"结束执行:"+new Date());}
}
package com.day17.thread8;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class ThreadPoolExecutorDemo {public final static int CORE_POOL_SIZE = 5;public static final int MAX_POOL_SIZE = 10;public static final Long KEEP_ALIVE_TIME = 1L;//使用阿里巴巴推荐的ThreadPoolExecutor创建线程池public static void main(String[] args) {ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,new ArrayBlockingQueue<>(100));for (int i = 0; i < 10; i++) {ExecutorsDemo02 demo02 = new ExecutorsDemo02();poolExecutor.execute(demo02);}//关闭线程池poolExecutor.shutdown();}
}

继承Thread类和实现Runnable接口的使用区别?

继承Thread类,使用起来比较方便,可以直接使用Thread类中的一些方法,但是不能实现资源共享。

实现Runnable接口,会让编写更具有统一性,解决了继承的局限性,可以实现线程之间的资源共享。

Runnable和Callable的区别

相同点:都是接口,都可以编写多线程程序,都需要通过Thread.start()方法启动

不同点:

1.Runnable接口的run方法没有返回值;

Callable接口的call方法有返回值,是一个泛型,可以结合FutureTask对象获取对象的返回值。

2.Runnable接口的run方法只能抛出运行时异常,只能用try/catch

线程执行的生命周期

新生状态:new一个线程对象,该线程对象就处于新生状态,这个时候,该线程对象具有自己的内存空间。

就绪状态:新生状态的线程对象,调用了start方法之后,进入就绪状态,这时候,线程具有运行条件,再等待CPU分配时间片。

如果系统选定了一个就绪状态的线程,就会从就绪状态,进入执行状态,这个动作称为CPU调度。

运行状态:获取到执行的时间片,这时候会执行线程对象中的run方法,直到任务完成后死亡,或者等待资源阻塞。

阻塞状态:运行状态下的线程,执行力sleep方法或者io资源阻塞、wait方法、synchronized同步作用下会进入阻塞状态。

阻塞状态下,线程不能进入就绪队列,只有阻塞原因清除,才会重新进入就绪状态中排队等待,被系统选中后,从原来停止的位置,继续执行。

死亡状态:线程正常执行完了它的工作、线程被强制终止了、线程抛出未被捕获的异常,线程都会进入死亡状态。

package com.day17.thread3;public class ThreadDemo05 implements Runnable{@Overridepublic void run() {System.out.println("我正在运行");try {Thread.sleep(2000);//休眠2秒System.out.println("线程休眠后继续运行");} catch (InterruptedException e) {e.printStackTrace();System.out.println("线程被中断");}}public static void main(String[] args) {Thread t = new Thread(new ThreadDemo05());System.out.println("线程t进入新生状态1");t.start();System.out.println("线程进入就绪状态2");}
}

线程的调度

void join()

等待这个线程死亡。

void setPriority(int newPriority)

更改此线程的优先级。

static void yield()

对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。

package com.day17.thread3;public class ThreadDemo06 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}public static void main(String[] args) {//void setPriority(int newPriority)//更改此线程的优先级,并不会一定将线程优先执行完毕Thread t1 = new Thread(new ThreadDemo06(), "A");Thread t2 = new Thread(new ThreadDemo06(), "B");//        t1.setPriority(Thread.MAX_PRIORITY);
//        t2.setPriority(Thread.MIN_PRIORITY);
//
//        t1.start();
//        t2.start();//void join()//等待这个线程死亡。t1.start();for (int i = 1; i < 10; i++) {if (i==5){try {
//                    t1.join();//插队System.out.println("线程礼让");//线程礼让方法,执行之后,不代表后面马上会执行其他内容//只是礼让下次执行机会,继续再公平竞争Thread.yield();} catch (Exception e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ":" + i);}//static void yield()//对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。}
}

void setDaemon(boolean on)

将此线程标记为 daemon线程或用户线程。

当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。

守护线程:运行在后台的一种特殊线程,独立于控制终端并且周期性执行某种任务

setDaemon()这个方法必须在启动线程前调用

比如JVM中垃圾回收、内存管理等线程都是守护线程

//1.设为守护线程后,主线程结束,守护线程也会结束

//2.普通线程,主线程结束后,普通线程还会继续运行

package com.day17.thread3;public class ThreadDemo07 extends Thread{@Overridepublic void run() {while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("守护线程");}}public static void main(String[] args) {System.out.println("程序开始");ThreadDemo07 t = new ThreadDemo07();//1.设为守护线程后,主线程结束,守护线程也会结束//2.普通线程,主线程结束后,普通线程还会继续运行t.setDaemon(true);t.start();//主线程休眠5秒try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("主线程运行结束");}
}

线程安全问题

在多线程的运行下,程序运行的结果,和我们预期的结果不一致(排除代码错误的情况),

这种情况就可以看作是线程安全问题,每次运行的结果可能都会不同,问题不易复现,

解决比较困难。

package com.day17.thread4;public class T1A {//出现线程安全,这个类中必须得有共享资源int num;public int getNum() {return num;}public int updateNum(){return num++;}
}
package com.day17.thread4;public class T1 extends Thread{T1A t1a = new T1A();@Overridepublic void run() {while (true){System.out.println("现在运行的是:" +Thread.currentThread().getName() +"num == " + t1a.updateNum());try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {T1 t1 = new T1();new Thread(t1).start();new Thread(t1).start();new Thread(t1).start();new Thread(t1).start();}
}

通过反编译查看线程安全文件的原因:

其实,出现线程安全问题的原因就是,代码执行的过程并不是原子性的操作,一个线程在执行的时候,会有其它线程读取到中间步骤或者读取到未修改的值,所以导致线程安全问题的出现。

通过synchronized关键字解决线程安全问题。

package com.day17.thread4;import java.util.ArrayList;/*
线程同步示例*/
public class ThreadDemo08 {public static void main(String[] args) {//线程不安全的ListArrayList<String> list = new ArrayList<>();for (int i = 0; i < 20000; i++) {new Thread(()->{synchronized (list){list.add(Thread.currentThread().getName());}}).start();}for (int i = 5; i > 0; i--) {try {Thread.sleep(1000);System.out.println("倒计时:"+i);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(list.size());}
}

什么样的情况,会发生线程安全问题?

1.多线程环境

2.存在共享资源,多个线程会去访问共享资源

3.存在并发修改共享资源的情况

线程同步

1、为什么要使用多线程完成并发编程?

多线程可以充分利用CPU计算能力,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,提升性能。

方便进行业务拆分,提升系统的并发能力和性能。

2、并发编程的缺点

并发编程虽然能提高效率,但是也会产生各种问题,比如:线程安全、死锁、上下文切换、内存泄露...

3、并发编程三要素【面试】

1.原子性:保证程序执行的步骤不可再分,要么全部成功要么全部失败。

2.可见性:一个线程对共享资源的修改,另一个线程可以马上看到。

(synchronized、volatile)

3. 有序性:程序执行的顺序按照代码的先后顺序执行(JVM中可能会对指令进行重排序) 

线程切换,可能会带来原子性问题。

缓存导致可见性问题。

编译优化带来有序性问题。

以上三个问题的出现,就是线程安全问题的原因。

Java中解决线程安全问题

原子性:synchronized、Lock、JDK、Atomic开头的原子类 可以解决原子性问题

可见性:synchronized、volatile、Lock 可以解决可见性问题

有序性:Happens-Before规则 解决有序性问题

并发相关的概念

并发:多个任务在同一个CPU核上,按照时间片轮流执行,从逻辑上看,多个任务是同时执行的。

并行:单位时间内,多个处理器同时处理多个任务,是真正的同时执行。

串行:有多个任务,由同一个线程按照顺序执行,由于任务、方法都在一个线程下执行,不存在线程不安全的情况。

高并发:同一个对象,被多个对象同时操作,存在线程安全问题,可以通过线程同步来解决并发问题。

线程同步:其实就是一种等待机制,多个需要同时访问某个对象的线程,进入对象的等待池中,形成队列,前面的线程用完了,下个线程再使用。

Java中怎么实现线程同步?

  Java为了保证数据访问的安全性,在多线程中可以加入锁机制synchronized,

  如果一个线程获取到锁,把资源独占,其他线程等待前面线程用完之后释放锁。

synchronized关键字的用法

synchronized可以用来控制线程同步,在多线程情况下,加了这个关键字的代码,不会被多个线程执行。

synchronized可以用来修饰类、方法、变量。

早期版本的synchronized比较笨重,jdk1.6之后引入了大量的优化。

常用的写法:
1、直接写在方法上,称为同步方法

public synchronized void method(){}

每个synchronized方法,必须获取到调用该方法的对象的锁才能执行,否则该线程处于阻塞状态,

如果线程拿到锁了,就能独占该锁,直到方法运行结束,释放锁,其他的被阻塞的线程才能获取锁。

package com.day17.thread4;/*
初识并发问题,模拟抢票案例*/
public class TicketDemo implements Runnable{//设置票数,票数是公共的资源,多个线程共享private int ticketNumber = 10;boolean flag=true;@Overridepublic void run() {while (flag){buyTicket();try {//模拟网络延迟Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}//写一个买票方法public synchronized void buyTicket(){if (ticketNumber <= 0){flag=false;return;}//        try {//                //模拟网络延迟//                Thread.sleep(100);//        } catch (InterruptedException e) {//                e.printStackTrace();//        }System.out.println(Thread.currentThread().getName()+"->抢到了第" + ticketNumber-- +"张票");}public static void main(String[] args) {TicketDemo t = new TicketDemo();//把实现类对象传入到Thread对象中,创建Thread对象new Thread(t,"小明").start();new Thread(t,"小红").start();new Thread(t,"黄牛").start();}
}
2、修饰静态方法

当synchronized静态方法上的时候,就是相当于给类加锁,锁的是每个类的Class类对象, 会作用于所有对象的实例。

3、使用synchronized修饰代码块,成为同步代码块

synchronized(obj){}

同步代码块一般需要传入一个参数,这个参数(obj)可以是任意对象,将来一般将具有共享资源的对象放在这里,作为同步监视器。

package com.day17.thread4;public class TicketDemo02 implements Runnable{//设置票数,票数是公共的资源,多个线程共享private int ticketNumber = 10;boolean flag=true;Object object = new Object();@Overridepublic void run() {while (flag){buyTicket();try {//模拟网络延迟Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}//写一个买票方法public void buyTicket(){synchronized (object){if (ticketNumber <= 0){flag=false;return;}System.out.println(Thread.currentThread().getName()+"->抢到了第" + ticketNumber-- +"张票");}}public static void main(String[] args) {TicketDemo02 t = new TicketDemo02();//把实现类对象传入到Thread对象中,创建Thread对象new Thread(t,"小明").start();new Thread(t,"小红").start();new Thread(t,"黄牛").start();}
}
synchronized优缺点

优点:解决了线程安全问题

缺点:性能下降,影响效率,修饰方法的话,每次调用方法就会产生锁,浪费资源,可能产生死锁

Lock锁

JDK1.5开始,提供的一个更强大的线程同步锁

通过显式的定义同步锁对象来实现同步,也就是Lock对象。

锁是用于通过多个线程控制对共享资源的访问的工具。

通常,锁提供对共享资源的独占访问:

一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。

使用方式:

使用Lock接口的常用实现类ReentrantLock

ReentrantLock是一个可重入互斥Lock,具有与使用synchronized方法

和语句访问的隐式监视锁相同的基本行为和语义,但具有扩展功能

语法:

class X {

private final ReentrantLock lock = new ReentrantLock();

// ... public void m() {

lock.lock();//手动调用lock方法加锁

try {

// ... method body

}

finally {

lock.unlock() ; //释放锁

}

}

}

package com.day17.thread4;
/*
Lock*/
import java.util.concurrent.locks.ReentrantLock;public class TicketDemo03 implements Runnable{//设置票数,票数是公共的资源,多个线程共享private int ticketNumber = 20;boolean flag=true;ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (flag){buyTicket();}}//写一个买票方法public  void buyTicket(){try {lock.lock();if (ticketNumber <= 0){flag=false;return;}System.out.println(Thread.currentThread().getName()+"->抢到了第" + ticketNumber-- +"张票");//模拟网络延迟Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock();}}public static void main(String[] args) {TicketDemo03 t = new TicketDemo03();//把实现类对象传入到Thread对象中,创建Thread对象new Thread(t,"小明").start();new Thread(t,"小红").start();new Thread(t,"黄牛").start();}}

Lock和synchronized的区别

1.synchronized 是一个关键字,Lock是一个接口,ReentrantLock是一个类,类中提供了比synchronized更多灵活性的操作,比如,可以被继承、可以有方法、可以有各种类变量

2.synchronized早期效率比较低,jdk1.6之后做了优化后,性能较好

3.ReentrantLock使用起来比较灵活,但是必须要有开启锁和释放锁的动作,synchronized不需要手动开启、释放锁

4.ReentrantLock一般只适用于代码块锁,而synchronized可以用于修饰类、变量、方法

Lock和synchronized选择用哪个?

因为JDK1.6之后,synchronized做了很多优化,所以synchronized效率不比Lock,而且使用更加简单,一般情况下,就使用synchronized

死锁

概念

指的是两个或者两个以上的线程中,在执行过程中,由于竞争资源或者由于彼此通信造成的一种阻塞现象,如果没有外力作用,他们无法推进下去,此时这种状态称为系统的死锁,这些永远在互相等待的线程称为死锁线程。

出现死锁后,程序不会有异常,也不会出现提示,只是线程处于阻塞状态无法继续,将来编写程序,要避免死锁的发生。

出现死锁的条件

1.互斥条件:线程对于分配到的资源具有排他性,也就是一个资源只能被一个线程占用,直到被该线程释放。

2.请求与保持条件:一个线程因为请求被占用资源而发生阻塞时,对已经获取的资源,保持不放。

3.不剥夺条件:线程在获取资源后,未使用完之前不能被其他线程强行剥夺。

4.循环等待条件:当发生死锁的时候,所有等待的线程必定会形成一个环路(类似于死循环),造成永久阻塞。

怎么避免死锁?

破坏上面四个条件中的任意一个,就不会产生死锁。

package com.day17.deadLock5;//茶杯对象
public class TeaCup {
}package com.day17.deadLock5;//牙膏对象
public class ToothPaste {
}
package com.day17.deadLock5;public class BrushTooth implements Runnable{String name;//谁拿了对象int choice;//0:茶杯;1:牙膏static TeaCup teaCup = new TeaCup();static ToothPaste toothPaste = new ToothPaste();public BrushTooth(String name, int choice) {this.name = name;this.choice = choice;}@Overridepublic void run() {try {brushTooth();} catch (InterruptedException e) {e.printStackTrace();}}//写一个刷牙方法public void brushTooth() throws InterruptedException {//先拿茶杯,再拿牙膏if (choice == 0){synchronized (teaCup){System.out.println(name+"拿到了茶杯,等待拿牙膏刷牙!");Thread.sleep(1000);synchronized (toothPaste){System.out.println(name+"拿到牙膏,开始刷牙!");}}}else {//先拿牙膏,再拿茶杯synchronized (toothPaste){System.out.println(name+"拿到了牙膏,等待拿茶杯刷牙!");Thread.sleep(1000);synchronized (teaCup){System.out.println(name+"拿到茶杯,开始刷牙!");}}}}
}
package com.day17.deadLock5;public class Test {public static void main(String[] args) {BrushTooth t1 = new BrushTooth("A", 0);BrushTooth t2 = new BrushTooth("B", 1);new Thread(t1).start();new Thread(t2).start();}
}

volatile关键字

Java提供了volatile关键字用来解决多线程中的部分问题。

volatile可以用来保证可见性和有序性,不能保证原子性,还会出现线程安全问题。

package com.day17.thread6;public class ThreadDemo09 {public int num=0;public  boolean flag = false;//不加volatile,程序一直在运行//volatile保证了类属性在多个线程中的可见性//public volatile boolean flag = false;//synchronized也可以保证可见性//通过给方法修饰后,间接控制属性可见性public synchronized boolean isFlag() {return flag;}public synchronized void setFlag(boolean flag) {this.flag = flag;}public synchronized void addNum(){num++;}public int getNum() {return num;}public static void main(String[] args) {ThreadDemo09 t1 = new ThreadDemo09();new Thread(()->{for (int i = 0; i <5; i++) {t1.addNum();System.out.println("addNum方法被调用" + i);try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}//将flag设为true//                    t1.flag=true;t1.setFlag(true);System.out.println("已经将flag设为true了。");}).start();new Thread(()->{System.out.println("线程2正在执行");//                  while (!t1.flag){//                  }while (!t1.isFlag()){}System.out.println("num的值是"+t1.getNum());}).start();}
}
package com.day17.thread6;public class ThreadDemo10 {//volatile不能保证原子性public volatile int num=0;
//    public int num=0;//    public synchronized void addNum(){public void addNum(){num++;}public int getNum() {return num;}public static void main(String[] args) {ThreadDemo10 t1 = new ThreadDemo10();//循环10次,每次创建一个子线程,每个子线程做了100次 num++,//如果有原子性的,线程安全的,最终的值应该是1000//在属性前加上volatile不能保证原子性,所以最终的值不是1000//使用synchronized关键字,加在方法前面,可以保证原子性for (int i = 0; i < 10; i++) {new Thread(()->{for (int j = 0; j < 100; j++) {t1.addNum();try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}}).start();}//获取值for (int i = 0; i < 10; i++) {System.out.println("num的值是:"+t1.getNum());try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}
}

synchronized和volatile的区别

synchronized表示只有一个线程可以获取作用对象的值,执行代码会阻塞其他线程。

volatile表示变量在CPU的寄存器中是不确定的,必须从内存中读取,保证多线程下变量的可见性,

禁止指令重排(有序性)。

区别:

1.volatile是用来修饰变量的,synchronized可以修饰类、方法、变量。

2.volatile只能实现变量的可见性,不能保证原子性;synchronized可以保证原子性和可见性。

3. volatile不会造成线程阻塞,synchronized会造成线程阻塞。

4.volatile标记的变量不会被编译器优化,synchronized标记的变量可以被编译器优化。

5.volatile性能比synchronized要好。

线程交互

Object类中的方法 wait()、notify()、notifyAll()

wait()方法:释放占有的对象锁,线程进入等待池,释放CPU,

其他正在等待的线程可以拿到锁,获取到锁的线程可以执行程序。

notify()方法:该方法会唤醒因为调用wait()方法而等待的线程,其实就是对 对象锁 的唤醒,

被唤醒的线程可以有机会继续获得对象锁,从而执行程序。

另一个线程调用notify()后,不会马上释放锁,继续把代码执行完毕,才会释放锁。

notifyAll()方法:所有线程

void notify()

唤醒正在等待对象监视器的单个线程。

void notifyAll()

唤醒正在等待对象监视器的所有线程。

void wait()

导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。

注意:wait()和notify()都需要synchronized中调用。

因为wait()就是用来释放锁的,线程必须拥有该对象的锁,才能去释放锁。

package com.day17.thread9;public class WaitDemo01 implements Runnable{int count=0;@Overridepublic void run() {while (count<10){synchronized (ThreadWait.obj){//线程第一次进来,没有线程等待,不需要执行唤醒if (count!=0){javaThreadWait.obj.notify();}System.out.println("A"+count);try {ThreadWait.obj.wait();} catch (InterruptedException e) {e.printStackTrace();}count++;}}}
}
package com.day17.thread9;public class WaitDemo02 implements Runnable{int count=0;@Overridepublic void run() {while (count<10){synchronized (ThreadWait.obj){//唤醒线程1,唤醒之后会继续执行当前代码//代码执行完,才会释放锁ThreadWait.obj.notify();System.out.println("B"+count);//当程序运行到9,表示循环结束了,不需要再等待了if (count != 9){try {ThreadWait.obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}count++;}}}
}
package com.day17.thread9;public class ThreadWait {//创建一个对象public static final Object obj = new Object();public static void main(String[] args) {new Thread(new WaitDemo01()).start();new Thread(new WaitDemo02()).start();}
}

【面试题】wait()和sleep()的区别?

相同点:都可以让线程暂停,进入阻塞状态。

不同点:

1.所属类不同,wait()方法属于Object类的一个方法,sleep()是属于Thread类的一个方法。

2.锁释放不同:wait()会释放锁,sleep()不会。

3.用途不同:wait()常被用于线程间的交互(通信),sleep()一般用来暂停线程的执行。

4.用法不同:wait()方法被调用,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()方法或者notifyAll()方法进行唤醒操作,如果调用wait(long timeout) ,超时后会自动唤醒。

sleep()执行完之后,线程会自动苏醒。

生产消费者模型(基于wait方法的一个应用模型)

不是面向对象的设计模式

也可以称为(生产者 - 消费者 - 仓库)模型

1.生产者,在仓库未存满的时候,开始生产商品,仓库满了,停止生产

2.消费者,在仓库有产品的时候,才能消费,仓库空了,则等待

3.当消费者发现仓库没有产品,会通知生产者生产

4.当生产者生产可以消费的产品,则通知消费者消费。

这个模型有哪些类?

生产者,就有生产方法

消费者:具有消费方法

仓库:添加产品,减少产品

产品

package com.day17.thread10;
//炸鸡产品
public class Chicken {int number;public Chicken(int number) {this.number = number;}}
package com.day17.thread10;
//仓库
public class Warehouse {//表示仓库中最多可以被存放多少个产品Chicken[] chickens = new Chicken[10];//计数变量,用来记录产品数量int num=0;//取出产品public synchronized Chicken pop() {//如果仓库中没有产品,消费者等待while (num<=0){try {this.wait();//消费者等待} catch (InterruptedException e) {e.printStackTrace();}}num--;try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}Chicken chicken = chickens[num];this.notifyAll();return chicken;}//添加产品public synchronized void put(Chicken chicken) {//如果产品满了,不生产,等待消费while (num >= chickens.length-1){try {this.wait();//停止生产,等待消费} catch (InterruptedException e) {e.printStackTrace();}}//产品没满,就通知生产者生产chickens[num] = chicken;num++;this.notifyAll();}}
package com.day17.thread10;
//生产者
public class Producer implements Runnable{//声明一个仓库对象,通过仓库对象调用方法Warehouse warehouse = new Warehouse();public Producer(Warehouse warehouse) {this.warehouse = warehouse;}//生产方法@Overridepublic void run() {for (int i = 1; i <= 50; i++) {//生产产品其实是在调用仓库对象,往仓库中添加产品warehouse.put(new Chicken(i));System.out.println("生产者生产了第"+ i +"号炸鸡!");}}}
package com.day17.thread10;
//消费者
public class Consumer implements Runnable{//声明一个仓库对象,通过仓库对象调用方法Warehouse warehouse = new Warehouse();public Consumer(Warehouse warehouse) {this.warehouse = warehouse;}//取出方法@Overridepublic void run() {for (int i = 1; i <= 50; i++) {//消费产品其实是在调用仓库对象,从仓库中取出产品Chicken chicken = warehouse.pop();System.out.println("消费者消费了了第"+ chicken.number +"号炸鸡!");}}
}

package com.day17.thread10;public class Test {public static void main(String[] args) {Warehouse warehouse = new Warehouse();new Thread(new Producer(warehouse)).start();new Thread(new Consumer(warehouse)).start();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/837934.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于SSM的“口腔护理网站”的设计与实现(源码+数据库+文档)

基于SSM的“口腔护理网站”的设计与实现&#xff08;源码数据库文档) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 首页 用户注册页面 医生信息查看模块 口腔护理预约模块 后台首页面…

分享如何通过定时任务调用lighthouse前端测试脚本+在持续集成测试中调用lighthouse前端测试脚本

最近写了个小工具来优化lighthouse在实际工作中的使用&#xff0c;具体实现了&#xff1a;通过定时任务调用前端测试脚本在持续集成测试中调用前端测试脚本。由于在公司中已经应用&#xff0c;所以就不能提供源码了&#xff0c;这里简单说一下实现思路&#xff0c;希望可以帮助…

Java 循环结构 - for, while 及 do...while

Java 循环结构 - for, while 及 do…while 顺序结构的程序语句只能被执行一次。 如果您想要同样的操作执行多次&#xff0c;就需要使用循环结构。 Java中有三种主要的循环结构&#xff1a; while 循环 do…while 循环 for 循环 在 Java5 中引入了一种主要用于数组的增强型 f…

OUC图书馆电脑开启无线网络,连接手机热点,解决联网但无法访问网络的问题

OUC图书馆电脑连手机热点 前言手动脚本&#xff08;暂未测试&#xff09;注意 前言 【中国海洋大学】OUC图书馆电脑默认只能有线连校园网&#xff0c;这让没有校园网的人很是头疼&#xff08;手机流量太多了&#xff0c;根本用不完&#xff0c;需要大流量卡的可以私信我&#…

在Android设备丢失数据后恢复数据的4个方法

了解 Android 媒体存储 媒体存储是下载、查看、播放和流式传输视频文件、音频文件、图像和其他媒体文件时所需的过程。此服务无法从手机桌面访问&#xff0c;因此您需要按照以下步骤通过安卓手机访问此系统服务。 步骤1&#xff1a;导航到手机设置&#xff0c;然后转到应用程…

初识鸿蒙之ArkTS基础

前言 学习一种应用程序开发&#xff0c;需要从这种程序的开发语言开始&#xff0c;比如说Android开发从入门到放弃&#xff0c;肯定是从Java基础或者是Kotlin语言基础开始学习的&#xff0c;IOS程序开发也肯定是从object-c开始学习的。鸿蒙软件开发也不例外&#xff0c;如果做…

Vue3+TS实现将html或富文本编辑器转为Word并下载

说明&#xff1a;我用的富文本编辑器是wangEditor&#xff1a; wangEditor官网 安装 yarn add wangeditor/editor # 或者 npm install wangeditor/editor --save yarn add wangeditor/editor-for-vuenext # 或者 npm install wangeditor/editor-for-vuenext --save yarn add …

金万维动态域名小助手怎么用?

金万维动态域名小助手是一个域名检测工具&#xff0c;使用此工具可以进行检测域名解析是否正确、清除DNS缓存、修改DNS服务器地址及寻找在线客服&#xff08;仅支持付费用户&#xff09;等操作。对不懂网络的用户是一个很好的检测域名的工具&#xff0c;下面我就讲解一下金万维…

Java聚合项目打包运行笔记

聚合项目创建 略 聚合项目打包配置 父工程 pom文件添加 <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>…

[初学者来练]用html+css+javascript个人博客作业需求

文章目录 项目概述项目需求页面设计主页文章列表页文章详情页用户交互额外功能&#xff08;可选&#xff09; 技术要求提交要求评分标准文件代码格式提示HTML 页面结构CSS 样式设计JavaScript 交互功能 项目概述 这个项目旨在通过使用HTML、CSS和JavaScript创建一个简单而功能…

【微信小程序开发(从零到一)【婚礼邀请函】制作】——任务分析和效果实现的前期准备(1)

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

深入浅出Java中的数据结构:LinkedHashMap详解

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

通过windows远程桌面,远程连接CentOS系统

1.配置阿里云的YUM仓库 1.1 备份当前的YUM仓库配置文件 sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup1.2 下载阿里云的CentOS仓库配置文件 对于CentOS 7&#xff1a; sudo wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirr…

[第五空间 2021]WebFTP

目录扫描git泄露phpinfo.php 一开始想到是sql注入&#xff0c;但是不行。目录扫描&#xff0c;发现 .git 和 phpinfo.php 访问phpinfo.php&#xff0c;ctrlf 搜索 flag&#xff0c;找到 flag。

Vue 封装axios

【一】准备工作 &#xff08;1&#xff09;安装必要插件 安装Axios&#xff0c;这是必要的。默认最新版 npm install axios -S 或 cnpm install axios -S安装elementui-plus&#xff0c;用于提示信息 npm install element-plus --save # 或 cnpm install element-plus --s…

风电功率预测 | 基于RF随机森林的风电功率预测(附matlab完整源码)

风电功率预测 风电功率预测完整代码风电功率预测 基于随机森林(Random Forest, RF)的风电功率预测是一种常用且有效的方法。以下是基于RF的风电功率预测的一般步骤: 数据准备:收集与风电场发电功率相关的数据,包括风速、风向、温度、湿度等气象数据以及风电场的历史功率数…

MKS 电源 :EDGE 30R40A 400kh 现货 功能正常

MKS 电源 :EDGE 30R40A 400kh 现货 功能正常

【微记录】dmidecode是干什么的?常用来做什么?如何查看系统支持的PCIe版本号(本质:标准,Desktop Management Interface)

是什么 dmidecode 是一个在 Linux 系统提取硬件信息的命令行工具。DMI 代表桌面管理接口&#xff08;Desktop Management Interface&#xff09;&#xff0c;是一种标准&#xff0c;收集桌面计算机的硬件信息&#xff0c;包括系统制造商、序列号、BIOS 信息、系统资产标签等。…

10分钟快速掌握正则表达式

一、背景 因为工作的时候要做一些表单校验和精准搜索。所以写下这篇文章。 当涉及到正则表达式的理解和使用时&#xff0c;尽管它们提供了强大的文本处理能力&#xff0c;但其语法的复杂性常常让人倍感挑战。即使是对经常使用正则表达式的专业开发者来说&#xff0c;也常常会因…

计算机毕业设计Python+Spark知识图谱酒店推荐系统 酒店评论情感分析 酒店价格预测系统 酒店可视化 酒店爬虫 neo4j知识图谱 深度学习

广东科技学院毕业设计(论文)开题报告 设计(论文)名称 民宿数据可视化分析系统的设计与实现 设计(论文)类型 C 指导教师 朱富裕 学 院 计算机学院 专 业 数据科学与大数据技术 姓 名 庄贵远 学 号 2020135232 班 级 20大数据本科2班 选题依据(包括项目研究的…