尚硅谷复习笔记中的多线程如何改写为长尾词?

2026-04-19 07:511阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计4635个文字,预计阅读时间需要19分钟。

尚硅谷复习笔记中的多线程如何改写为长尾词?

一、相关概念

1.1 首先了解什么是程序、线程、进程

程序:指完成特定任务的指令集合,通常用某种编程语言编写。线程:程序执行的最小单位,是程序执行流的最基本单元。进程:程序执行时的一个实例,是系统进行资源分配和调度的一个独立单位。

程序:表示完成特定任务的指令集合,用特定语言编写,如C语言、Python等。线程:程序中的执行流,一个程序可以包含多个线程,它们可以并行执行。进程:程序运行的一个实例,是操作系统进行资源分配和调度的基本单位。

一、相关概念

1.1首先了解什么是程序、线程、进程

程序:指为完成特定任务,用某种语言编写的一组指令的集合,通俗易懂来讲程序就是一段静态的代码;

线程:程序组成线程,线是程序内部的一条执行路径,对于程序而言线程的动态的,是CPU调度和执行的最小单位;

进程:线程组成进程,是程序的一次执行的过程,或是在在内存中运行的运用程序,如:打开的QQ/WX,进程至少由一个线程组成、是操作系统调度和分配资源的最小单位,一个进程若执行多个线程,就是支持多线程的。


1.2多线程优缺点

一个进程中多个线程共享相间的内存单元,他们从同一个堆中分配对象,可以访问相间的对象,提升了计算机系统CPU的利用率使得线程间的通信更加简洁、高效,但多个线程共享资源会带来安全隐患。·


1.3线程调度

分时调度:所有线程轮流使用CPU的使用权,并且平均分配每个线程占用CPU的时间。

抢占式调度:让优先级高的线程更大概率能够优先使用到CPU(java使用)


1.4并行与并发

并行:指两个或多个事件在同一时刻发生,指多个指令在多个CPU中同时执行,如:多个人同时在做不同的事情

并发:指两个或多个事件在同一时间段内发生,即一个时间段内有多条指令在单个CPU上快速切换,使得在宏观上具有多个进程在同时执行的效果


二、多线程的创建方式(四种)

2.1继承Thread类

(其实通过源码可以看到,继承的该Thread类也是实现了Runnable接口)

①定义Thread类的子类,并重写run()方法(方法体中代表了线程需要完成的任务)

②创建子类的实例(即创建了线程对象)

③通过子类调用线程对象的star()方法启动线程(启动线程后start方法会调用run方法,如果直接调用run方法线程未启动)

例子:创建分线程1遍历100内的偶数,线程2遍历100内的奇数

package Mon07.day0722; public class ThreadTest{ public static void main(String[]args){ Thread1 t1 = new Thread1(); Thread2 t2 = new Thread2(); t1.start(); t2.start(); //注意:若还想建立新的R2线程,可以直接实例化并启动新线程 Thread t3 = new Thread(); t3.start(); } } class Thread1 extends Thread{ @Override public void run(){ for(int i = 0 ; i <= 100 ; i++){ if(i%2 == 0){ Thread.currentThread().setName("线程1"); System.out.println(Thread.currentThread().getName()+":"+i); } } } } class Thread2 extends Thread { @Override public void run() { for (int i = 0; i <= 100; i++) { if (i % 2 != 0) { Thread.currentThread().setName("线程2"); System.out.println(Thread.currentThread().getName()+":"+i); } } } }

2.2实现Runnable接口

①定义实现Runnable接口的子类,并重写run()方法(方法体中代表了线程需要完成的任务)

②创建子类的实例(即创建了线程对象)

尚硅谷复习笔记中的多线程如何改写为长尾词?

③通过子类传入Thread类后再调用线程对象的star()方法启动线程(此子类和Thread类都实现了Runable接口,但是还是传入Thread类,是因为采用了代理)

案例

package Mon07.day0722; public class RunnableTest { public static void main(String[] args) { Runnable1 R1 = new Runnable1(); Thread t1 = new Thread(R1); t1.start(); Runnable2 R2 = new Runnable2(); Thread t2 = new Thread(R2); t2.start(); //注意:若还想建立新的R2线程,可以直接实例化并启动新线程 Thread t2 = new Thread(R2); t2.start(); } } class Runnable1 implements Runnable{ @Override public void run() { for(int i = 0 ; i <= 100 ; i++){ if(i%2 == 0){ Thread.currentThread().setName("线程1");//线程命名 System.out.println(Thread.currentThread().getName()+":"+i); } } } }class Runnable2 implements Runnable{ @Override public void run() { for(int i = 0 ; i <= 100 ; i++){ if(i%2 != 0){ Thread.currentThread().setName("线程2");//线程命名 System.out.println(Thread.currentThread().getName()+":"+i); } } } }


拓展:继承Thread类和继承Runnable接口的区别

共同点:

①都要调用Thread类中定义的start()方法

②创建的线程对象都是Thread类或其子类的实例

不同点:一个是类的继承,一个是接口的实现

建议:常用Runnable接口实现

使用Runnable接口的好处在于:①该实现方法可以避免继承的局限性②更适合处理有共享数据的问题(继承需要通过静态共享数据,但接口数据天然 就会共享)


2.4实现Callable接口

(jdk5新增)

与之前的方法runnable()进行对比,有哪些好处?

1.call()可以有返回值,却因为callable使用了泛型参数,所以call()的返回值类型会更加灵活;

2.call()可以使用throws方式处理异常,更加灵活。

package Mon09.day0903; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; class NumThread implements Callable{ @Override public Object call() throws Exception { int sum = 0; for (int i = 0; i < 100; i++) { if (i%2 ==0){ System.out.println(i); sum += i; } } return sum; } } public class CallableTest { public static void main(String[] args) { NumThread numThread = new NumThread();//创建callable接口实现类对象 FutureTask futureTask = new FutureTask(numThread);//将对象传递到FutureTask构造器中,创建futureTask对象 Thread thread = new Thread(futureTask);//将futureTask对象传递到Thread中,创建Thread对象并调用start() thread.start(); try { Object sum = futureTask.get(); //主线程带出,但如果此时主线程下、需要获取分线程call()的返回值,则此时主线程自带阻塞 System.out.println("总和为:"+sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }

2.5 使用线程池

好处:

1.提高了程序执行的效率(因为线程已经提前创建好了)

2.提高了资源的复用率(因为执行完的线程并未销毁,而是可以继续执行其它的任务)

3.可以设置相关的参数,对线程池中的线程进行管理。



三、Thread的常用方法

3.1多线程的常用结构

3.1.1线程中的构造器

public Thread():分配一个新的线程对象;

public Thread(String name):分配一个指定名称的新线程对象;

public Thread(Runnale target):指定创建线程的目标对象,它实现了Runnable接口的run方法;

public Thread(Runnale target,String name):分配一个带有指定目标且指定名称的新线程对象;


3.1.2线程的常用方法

start():①启动线程 ②调用线程的run()

run():将线程要执行的操作,声明在run()中。

currentThread():获取当前执行代码对应的线程

getName():获取线程名

setName():设置线程名

sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数

yield():静态方法,一旦执行即可能主动释放本次CPU的执行器

join():在线程a中通过线程b调用join(),以为着线程a进入阻塞状态,直到线程b执行结束,线程a才会结束阻塞状态继续执行

isAlive():判断当前线程的存活情况

getPriority():获取线程的优先级,未设置的默认为5级(1-10级)

三个优先级常量:MAX_PRIORITY(10),MIN_PRIORITY(1),NORM_PRIORITY(5)

setPriority(int newPriority):改变线程的优先级,范围[1,10]

过时方法(了解即可):

public final void stop(): 强行结束一个线程的执行,直接进入死亡状态。run()即刻停止,可能会导致一些清理性的工作得不到完成,如文件,数据库等的关闭。同时,会立即释放该线程所持有的所有的锁,导致数据得不到同步的处理,出现数据不一致的问题。

void suspend()/void resume():这两个操作就好比播放器的暂停和恢复。二者必须成对出现,否则非常容易发生死锁。suspend()调用会导致线程暂停,但不会释放任何锁资源,导致其它线程都无法访问被它占用的锁,直到调用resume()。已过时 ,不建议使用。


四、多线程的生命周期

了解即可。

五、如何使用同步机制解决线程安全问题(synchronized)

关于什么是线程安全问题:当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题。但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。

5.1同步代码块

synchronize(同步监视器){ //需要被同步的代码 }

说明:

1.需要被同步的代码,即为操作共享数据的代码。

2.共享数据:即多个线程多需要操作的数据。比如:ticket

3.需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其它线程必须等待,同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码。

4.同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。

重点:在实现Runnable接口的方式中,同步监听器/锁 可以考虑使用this

在继承Thread类的方式中,同步监听器慎用this(很可能多个实现类this不唯一)

package day0801; import sun.plugin2.os.windows.Windows; class wimdow extends Thread{ static int ticket = 100; static Object obj = new Object(); public void run(){ while(true){ //synchronized(this){//实例化多个对象是,this代表t1 t2 t3,不能代表唯一性 // synchronized(obj){//obj若静态,则可以保证唯一性 synchronized(Windows.class){ if(ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+"售票,票号:"+ticket); ticket--; }else{break;} } } } } public class Window{ public static void main(String[] args) { wimdow w = new wimdow(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.start(); t2.start(); t3.start(); } }


5.2同步方法

如果操作共享数据的代码完整声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。

非静态的同步方法,默认同步监听器为this;

静态的同步方法,默认同步监听器为当前类本身。

package day0803; import sun.plugin2.os.windows.Windows; class wimdow1 extends Thread{ static int ticket = 100; static Object obj = new Object(); boolean isFlag = true; public synchronized void run(){ while(isFlag){ show(); } } public void show(){ if(ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+"售票,票号:"+ticket); ticket--; }else {isFlag = false;} } } public class WindowTest1 { public static void main(String[] args) { wimdow1 w = new wimdow1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.start(); t2.start(); t3.start(); } }

5.3synchronize好处与弊端

好处:解决了线程的安全问题

弊端:在操作共享数据时,多线程是串行执行的,意味着性能变低。

六、死锁

是什么

不同线程分别占用对方需要的同步资源不放弃,都在等对方放弃自己需要的同步资源,导致形成死锁。

死锁出现后,程序不会发生异常,不会给出提示,只是所有线程处于阻塞状态无法继续。


诱发死锁的原因

-条件互斥

-占用且等待

-不可抢夺

循环等待

以上四个条件,同时出现则会触发死锁。


如何避免死锁

针对条件1:互斥条件基本无法被破坏,因为线程需要通过互斥解决安全问题

针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待问题

针对条件3:占用部分资源的线程进一步申请其它资源时,如果申请不到则主动释放已经占用的资源

针对条件4:可以将资源改为线性顺序。申请资源时先申请序号较小的,避免循环等待问题。


七、Lock锁的使用

使用步骤:

1.创建lock的实例,确保多个线程公用一个lock实例(static final)

2.执行lock方法,锁定共享数据的方法

3.unlock()的调用,释放对共享数据的锁定

案例:

package day0828; import java.util.concurrent.locks.ReentrantLock; class Window extends Thread{ static int ticket = 100; //1.创建lock的实例,确保多个线程公用一个lock实例(static final) private static final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true){ try { //2.执行lock方法,锁定共享数据的方法 lock.lock(); if (ticket > 0){ try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+"售票,售票号为:"+ticket); ticket--; }else {break;} }finally { //3.unlock()的调用,释放对共享数据的锁定 lock.unlock(); } } } }

package day0828; public class LockTest { public static void main(String[] args) { Window window = new Window(); window.start(); } }

面试题:

一、synchronized方法与lock的对比?

synchronized不管是同步代码块还是同步方法,都需要在结束一对{}后释放对同步监视器的调用。

lock是通过两个方法控制需要被同步的代码,更加灵活一些。

Lock作为接口,提供了多种实现类,适合更多复杂的场景,效率更高

二、wait和sleep方法的区别?

相同点:一旦执行,当前线程就会进入阻塞状态

不同点:

声明的位置:wait声明在Object中,sleep在Thread类中且为静态

使用的场景不同:wait只能在同步代码块或同步方法中,sleep在任何需要使用的场景中

使用在同步代码块中时:wait一旦执行则会释放同步监视器,sleep不会释放。

结束阻塞的方式:wait:到达指定时间自动结束或者notify、notifyAll唤醒,sleep通过指定时间自动结束


八、线程通信

当我们需要多个线程有规律的共同执行一个任务时,多线程间就需要一些通信机制来协调它们的工作。

比如:线程A用来生产包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,此时B线程必须等到A线程完成后才能执行,那么线程A与线程B之间就需要线程通信,即——等待唤醒机制。

等待唤醒机制关键字:

wait:线程不再活动、参与调度,进入到wait set中,因此不会浪费CPU资源,不会竞争锁,此时线程的状态会转变为WAITING或者TMED_WAITING。只有当别的线程执行了一个特别动作:通知(notify)或者等待时间到,这个对象上等待的现场才会从wait set中释放出来,重新进入到调度队列(ready queue)中。

notify:选取所通知对象的wait set中一个线程释放;

notifyAll:释放所通知对象的wait set中所有线程;

注意:

1.此三个方法必须是在同步代码块或同步方法中。(需要配合condition实现线程间的通信)

2.此三个方法的调用者必须是同步监视器

例子:

package day0828; /* 使用两个线程交替打印1-100 */ import javafx.scene.control.TableRow; class PrintNumber implements Runnable{ private int number = 1; @Override public void run() { synchronized (this) { while (true){ notify(); if (number <= 100){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+number); number++; try { wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } }else {break;} } } } } public class PrintNumberTest { public static void main(String[] args) { PrintNumber p = new PrintNumber(); Thread t1 = new Thread(p, "线程1"); Thread t2 = new Thread(p, "线程2"); t1.start(); t2.start(); } }

九、案例

生产者Productor将产品交给店员Clerk,消费者从店员取走,店员最多持有20件产品,超过20件的话就会通知生产者停止生产,少于20件后才会恢复生产,若无产品店员则会让消费者稍等,等有产品后再通知消费者消费。

分析:多线程为消费者、生产者,共享数据为店员的产品(存在共享数据就会有线程安全问题)。


package Mon08.day0830; /** * * 生产者Productor将产品交给店员Clerk,消费者从店员取走,店员最多持有20件产品,超过20件的话 * 就会通知生产者停止生产,少于20件后才会恢复生产, * 若无产品店员则会让消费者稍等,等有产品后再通知消费者消费。 */ //店员 class Clerk{ private int productNum = 0;//产品初始数量 public synchronized void addProductNum() { if (productNum >= 20){ try { wait();//等待 } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName()+"生产了第"+productNum+"个产品"); productNum++; notify(); } public synchronized void minusProductNum() { if (productNum <= 0){ try { wait();//等待 } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName()+"消费了第"+productNum+"个产品"); productNum--; notify(); } } //生产者 class Productor extends Thread{ private Clerk clerk; public Productor(Clerk clerk){ this.clerk = clerk; } @Override public void run() { while (true){ System.out.println("生产者生成产品……"); try { Thread.sleep(50); } catch (InterruptedException e) { throw new RuntimeException(e); } clerk.addProductNum(); } } } //消费者 class Comsumer implements Runnable{ private Clerk clerk; public Comsumer(Clerk clerk){ this.clerk = clerk; } @Override public void run() { while (true){ System.out.println("消费者开始消费产品"); try { Thread.sleep(50); } catch (InterruptedException e) { throw new RuntimeException(e); } clerk.minusProductNum(); } } } public class ProducerComsumerTest { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor p1 = new Productor(clerk); Comsumer c1 = new Comsumer(clerk); p1.start(); Thread thread = new Thread(c1); thread.start(); p1.setName("生产者1"); thread.setName("消费者1"); } }



#在职重学JAVA基础

本文共计4635个文字,预计阅读时间需要19分钟。

尚硅谷复习笔记中的多线程如何改写为长尾词?

一、相关概念

1.1 首先了解什么是程序、线程、进程

程序:指完成特定任务的指令集合,通常用某种编程语言编写。线程:程序执行的最小单位,是程序执行流的最基本单元。进程:程序执行时的一个实例,是系统进行资源分配和调度的一个独立单位。

程序:表示完成特定任务的指令集合,用特定语言编写,如C语言、Python等。线程:程序中的执行流,一个程序可以包含多个线程,它们可以并行执行。进程:程序运行的一个实例,是操作系统进行资源分配和调度的基本单位。

一、相关概念

1.1首先了解什么是程序、线程、进程

程序:指为完成特定任务,用某种语言编写的一组指令的集合,通俗易懂来讲程序就是一段静态的代码;

线程:程序组成线程,线是程序内部的一条执行路径,对于程序而言线程的动态的,是CPU调度和执行的最小单位;

进程:线程组成进程,是程序的一次执行的过程,或是在在内存中运行的运用程序,如:打开的QQ/WX,进程至少由一个线程组成、是操作系统调度和分配资源的最小单位,一个进程若执行多个线程,就是支持多线程的。


1.2多线程优缺点

一个进程中多个线程共享相间的内存单元,他们从同一个堆中分配对象,可以访问相间的对象,提升了计算机系统CPU的利用率使得线程间的通信更加简洁、高效,但多个线程共享资源会带来安全隐患。·


1.3线程调度

分时调度:所有线程轮流使用CPU的使用权,并且平均分配每个线程占用CPU的时间。

抢占式调度:让优先级高的线程更大概率能够优先使用到CPU(java使用)


1.4并行与并发

并行:指两个或多个事件在同一时刻发生,指多个指令在多个CPU中同时执行,如:多个人同时在做不同的事情

并发:指两个或多个事件在同一时间段内发生,即一个时间段内有多条指令在单个CPU上快速切换,使得在宏观上具有多个进程在同时执行的效果


二、多线程的创建方式(四种)

2.1继承Thread类

(其实通过源码可以看到,继承的该Thread类也是实现了Runnable接口)

①定义Thread类的子类,并重写run()方法(方法体中代表了线程需要完成的任务)

②创建子类的实例(即创建了线程对象)

③通过子类调用线程对象的star()方法启动线程(启动线程后start方法会调用run方法,如果直接调用run方法线程未启动)

例子:创建分线程1遍历100内的偶数,线程2遍历100内的奇数

package Mon07.day0722; public class ThreadTest{ public static void main(String[]args){ Thread1 t1 = new Thread1(); Thread2 t2 = new Thread2(); t1.start(); t2.start(); //注意:若还想建立新的R2线程,可以直接实例化并启动新线程 Thread t3 = new Thread(); t3.start(); } } class Thread1 extends Thread{ @Override public void run(){ for(int i = 0 ; i <= 100 ; i++){ if(i%2 == 0){ Thread.currentThread().setName("线程1"); System.out.println(Thread.currentThread().getName()+":"+i); } } } } class Thread2 extends Thread { @Override public void run() { for (int i = 0; i <= 100; i++) { if (i % 2 != 0) { Thread.currentThread().setName("线程2"); System.out.println(Thread.currentThread().getName()+":"+i); } } } }

2.2实现Runnable接口

①定义实现Runnable接口的子类,并重写run()方法(方法体中代表了线程需要完成的任务)

②创建子类的实例(即创建了线程对象)

尚硅谷复习笔记中的多线程如何改写为长尾词?

③通过子类传入Thread类后再调用线程对象的star()方法启动线程(此子类和Thread类都实现了Runable接口,但是还是传入Thread类,是因为采用了代理)

案例

package Mon07.day0722; public class RunnableTest { public static void main(String[] args) { Runnable1 R1 = new Runnable1(); Thread t1 = new Thread(R1); t1.start(); Runnable2 R2 = new Runnable2(); Thread t2 = new Thread(R2); t2.start(); //注意:若还想建立新的R2线程,可以直接实例化并启动新线程 Thread t2 = new Thread(R2); t2.start(); } } class Runnable1 implements Runnable{ @Override public void run() { for(int i = 0 ; i <= 100 ; i++){ if(i%2 == 0){ Thread.currentThread().setName("线程1");//线程命名 System.out.println(Thread.currentThread().getName()+":"+i); } } } }class Runnable2 implements Runnable{ @Override public void run() { for(int i = 0 ; i <= 100 ; i++){ if(i%2 != 0){ Thread.currentThread().setName("线程2");//线程命名 System.out.println(Thread.currentThread().getName()+":"+i); } } } }


拓展:继承Thread类和继承Runnable接口的区别

共同点:

①都要调用Thread类中定义的start()方法

②创建的线程对象都是Thread类或其子类的实例

不同点:一个是类的继承,一个是接口的实现

建议:常用Runnable接口实现

使用Runnable接口的好处在于:①该实现方法可以避免继承的局限性②更适合处理有共享数据的问题(继承需要通过静态共享数据,但接口数据天然 就会共享)


2.4实现Callable接口

(jdk5新增)

与之前的方法runnable()进行对比,有哪些好处?

1.call()可以有返回值,却因为callable使用了泛型参数,所以call()的返回值类型会更加灵活;

2.call()可以使用throws方式处理异常,更加灵活。

package Mon09.day0903; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; class NumThread implements Callable{ @Override public Object call() throws Exception { int sum = 0; for (int i = 0; i < 100; i++) { if (i%2 ==0){ System.out.println(i); sum += i; } } return sum; } } public class CallableTest { public static void main(String[] args) { NumThread numThread = new NumThread();//创建callable接口实现类对象 FutureTask futureTask = new FutureTask(numThread);//将对象传递到FutureTask构造器中,创建futureTask对象 Thread thread = new Thread(futureTask);//将futureTask对象传递到Thread中,创建Thread对象并调用start() thread.start(); try { Object sum = futureTask.get(); //主线程带出,但如果此时主线程下、需要获取分线程call()的返回值,则此时主线程自带阻塞 System.out.println("总和为:"+sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }

2.5 使用线程池

好处:

1.提高了程序执行的效率(因为线程已经提前创建好了)

2.提高了资源的复用率(因为执行完的线程并未销毁,而是可以继续执行其它的任务)

3.可以设置相关的参数,对线程池中的线程进行管理。



三、Thread的常用方法

3.1多线程的常用结构

3.1.1线程中的构造器

public Thread():分配一个新的线程对象;

public Thread(String name):分配一个指定名称的新线程对象;

public Thread(Runnale target):指定创建线程的目标对象,它实现了Runnable接口的run方法;

public Thread(Runnale target,String name):分配一个带有指定目标且指定名称的新线程对象;


3.1.2线程的常用方法

start():①启动线程 ②调用线程的run()

run():将线程要执行的操作,声明在run()中。

currentThread():获取当前执行代码对应的线程

getName():获取线程名

setName():设置线程名

sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数

yield():静态方法,一旦执行即可能主动释放本次CPU的执行器

join():在线程a中通过线程b调用join(),以为着线程a进入阻塞状态,直到线程b执行结束,线程a才会结束阻塞状态继续执行

isAlive():判断当前线程的存活情况

getPriority():获取线程的优先级,未设置的默认为5级(1-10级)

三个优先级常量:MAX_PRIORITY(10),MIN_PRIORITY(1),NORM_PRIORITY(5)

setPriority(int newPriority):改变线程的优先级,范围[1,10]

过时方法(了解即可):

public final void stop(): 强行结束一个线程的执行,直接进入死亡状态。run()即刻停止,可能会导致一些清理性的工作得不到完成,如文件,数据库等的关闭。同时,会立即释放该线程所持有的所有的锁,导致数据得不到同步的处理,出现数据不一致的问题。

void suspend()/void resume():这两个操作就好比播放器的暂停和恢复。二者必须成对出现,否则非常容易发生死锁。suspend()调用会导致线程暂停,但不会释放任何锁资源,导致其它线程都无法访问被它占用的锁,直到调用resume()。已过时 ,不建议使用。


四、多线程的生命周期

了解即可。

五、如何使用同步机制解决线程安全问题(synchronized)

关于什么是线程安全问题:当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题。但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。

5.1同步代码块

synchronize(同步监视器){ //需要被同步的代码 }

说明:

1.需要被同步的代码,即为操作共享数据的代码。

2.共享数据:即多个线程多需要操作的数据。比如:ticket

3.需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其它线程必须等待,同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码。

4.同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。

重点:在实现Runnable接口的方式中,同步监听器/锁 可以考虑使用this

在继承Thread类的方式中,同步监听器慎用this(很可能多个实现类this不唯一)

package day0801; import sun.plugin2.os.windows.Windows; class wimdow extends Thread{ static int ticket = 100; static Object obj = new Object(); public void run(){ while(true){ //synchronized(this){//实例化多个对象是,this代表t1 t2 t3,不能代表唯一性 // synchronized(obj){//obj若静态,则可以保证唯一性 synchronized(Windows.class){ if(ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+"售票,票号:"+ticket); ticket--; }else{break;} } } } } public class Window{ public static void main(String[] args) { wimdow w = new wimdow(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.start(); t2.start(); t3.start(); } }


5.2同步方法

如果操作共享数据的代码完整声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。

非静态的同步方法,默认同步监听器为this;

静态的同步方法,默认同步监听器为当前类本身。

package day0803; import sun.plugin2.os.windows.Windows; class wimdow1 extends Thread{ static int ticket = 100; static Object obj = new Object(); boolean isFlag = true; public synchronized void run(){ while(isFlag){ show(); } } public void show(){ if(ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+"售票,票号:"+ticket); ticket--; }else {isFlag = false;} } } public class WindowTest1 { public static void main(String[] args) { wimdow1 w = new wimdow1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.start(); t2.start(); t3.start(); } }

5.3synchronize好处与弊端

好处:解决了线程的安全问题

弊端:在操作共享数据时,多线程是串行执行的,意味着性能变低。

六、死锁

是什么

不同线程分别占用对方需要的同步资源不放弃,都在等对方放弃自己需要的同步资源,导致形成死锁。

死锁出现后,程序不会发生异常,不会给出提示,只是所有线程处于阻塞状态无法继续。


诱发死锁的原因

-条件互斥

-占用且等待

-不可抢夺

循环等待

以上四个条件,同时出现则会触发死锁。


如何避免死锁

针对条件1:互斥条件基本无法被破坏,因为线程需要通过互斥解决安全问题

针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待问题

针对条件3:占用部分资源的线程进一步申请其它资源时,如果申请不到则主动释放已经占用的资源

针对条件4:可以将资源改为线性顺序。申请资源时先申请序号较小的,避免循环等待问题。


七、Lock锁的使用

使用步骤:

1.创建lock的实例,确保多个线程公用一个lock实例(static final)

2.执行lock方法,锁定共享数据的方法

3.unlock()的调用,释放对共享数据的锁定

案例:

package day0828; import java.util.concurrent.locks.ReentrantLock; class Window extends Thread{ static int ticket = 100; //1.创建lock的实例,确保多个线程公用一个lock实例(static final) private static final ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true){ try { //2.执行lock方法,锁定共享数据的方法 lock.lock(); if (ticket > 0){ try { Thread.sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(Thread.currentThread().getName()+"售票,售票号为:"+ticket); ticket--; }else {break;} }finally { //3.unlock()的调用,释放对共享数据的锁定 lock.unlock(); } } } }

package day0828; public class LockTest { public static void main(String[] args) { Window window = new Window(); window.start(); } }

面试题:

一、synchronized方法与lock的对比?

synchronized不管是同步代码块还是同步方法,都需要在结束一对{}后释放对同步监视器的调用。

lock是通过两个方法控制需要被同步的代码,更加灵活一些。

Lock作为接口,提供了多种实现类,适合更多复杂的场景,效率更高

二、wait和sleep方法的区别?

相同点:一旦执行,当前线程就会进入阻塞状态

不同点:

声明的位置:wait声明在Object中,sleep在Thread类中且为静态

使用的场景不同:wait只能在同步代码块或同步方法中,sleep在任何需要使用的场景中

使用在同步代码块中时:wait一旦执行则会释放同步监视器,sleep不会释放。

结束阻塞的方式:wait:到达指定时间自动结束或者notify、notifyAll唤醒,sleep通过指定时间自动结束


八、线程通信

当我们需要多个线程有规律的共同执行一个任务时,多线程间就需要一些通信机制来协调它们的工作。

比如:线程A用来生产包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,此时B线程必须等到A线程完成后才能执行,那么线程A与线程B之间就需要线程通信,即——等待唤醒机制。

等待唤醒机制关键字:

wait:线程不再活动、参与调度,进入到wait set中,因此不会浪费CPU资源,不会竞争锁,此时线程的状态会转变为WAITING或者TMED_WAITING。只有当别的线程执行了一个特别动作:通知(notify)或者等待时间到,这个对象上等待的现场才会从wait set中释放出来,重新进入到调度队列(ready queue)中。

notify:选取所通知对象的wait set中一个线程释放;

notifyAll:释放所通知对象的wait set中所有线程;

注意:

1.此三个方法必须是在同步代码块或同步方法中。(需要配合condition实现线程间的通信)

2.此三个方法的调用者必须是同步监视器

例子:

package day0828; /* 使用两个线程交替打印1-100 */ import javafx.scene.control.TableRow; class PrintNumber implements Runnable{ private int number = 1; @Override public void run() { synchronized (this) { while (true){ notify(); if (number <= 100){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+number); number++; try { wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } }else {break;} } } } } public class PrintNumberTest { public static void main(String[] args) { PrintNumber p = new PrintNumber(); Thread t1 = new Thread(p, "线程1"); Thread t2 = new Thread(p, "线程2"); t1.start(); t2.start(); } }

九、案例

生产者Productor将产品交给店员Clerk,消费者从店员取走,店员最多持有20件产品,超过20件的话就会通知生产者停止生产,少于20件后才会恢复生产,若无产品店员则会让消费者稍等,等有产品后再通知消费者消费。

分析:多线程为消费者、生产者,共享数据为店员的产品(存在共享数据就会有线程安全问题)。


package Mon08.day0830; /** * * 生产者Productor将产品交给店员Clerk,消费者从店员取走,店员最多持有20件产品,超过20件的话 * 就会通知生产者停止生产,少于20件后才会恢复生产, * 若无产品店员则会让消费者稍等,等有产品后再通知消费者消费。 */ //店员 class Clerk{ private int productNum = 0;//产品初始数量 public synchronized void addProductNum() { if (productNum >= 20){ try { wait();//等待 } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName()+"生产了第"+productNum+"个产品"); productNum++; notify(); } public synchronized void minusProductNum() { if (productNum <= 0){ try { wait();//等待 } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName()+"消费了第"+productNum+"个产品"); productNum--; notify(); } } //生产者 class Productor extends Thread{ private Clerk clerk; public Productor(Clerk clerk){ this.clerk = clerk; } @Override public void run() { while (true){ System.out.println("生产者生成产品……"); try { Thread.sleep(50); } catch (InterruptedException e) { throw new RuntimeException(e); } clerk.addProductNum(); } } } //消费者 class Comsumer implements Runnable{ private Clerk clerk; public Comsumer(Clerk clerk){ this.clerk = clerk; } @Override public void run() { while (true){ System.out.println("消费者开始消费产品"); try { Thread.sleep(50); } catch (InterruptedException e) { throw new RuntimeException(e); } clerk.minusProductNum(); } } } public class ProducerComsumerTest { public static void main(String[] args) { Clerk clerk = new Clerk(); Productor p1 = new Productor(clerk); Comsumer c1 = new Comsumer(clerk); p1.start(); Thread thread = new Thread(c1); thread.start(); p1.setName("生产者1"); thread.setName("消费者1"); } }



#在职重学JAVA基础