欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

13Java第十三章线程

时间:2023-06-09
程序、进程、线程

程序:在计算机上安装的程序代码(静态的)。

进程:运行中的程序,从硬盘上被加载到内存中,分配空间,是操作系统分配空间的单位。

线程:线程是进程内部的最小执行单元(单位),是操作系统调度单位。

举例:QQ

安装到电脑上(静态代码) 双击运行:加载到内存中

CPU执行以线程为单位。

线程、进程的关系

线程隶属于进程。

一个进程中可以包含多个线程,一个进程中至少包含一个线程(即主线程)。

可以在主线程中创建其他线程。

main方法

用来启动java主线程的。

线程

线程是操作系统调度执行的单位,线程是具备可以独立执行的。如果是多线程,那么两个线程可以是同时执行的。

线程是一个可以独立被操作系统执行调度的,多核CPU下,同时可以执行多个线程。

创建线程

//此程序非多线程! //这种现象它只是方法调用,程序会按照调用的先后顺序执行。public class Sample { public void method1(String str){ System.out.println(str); } public void method2(String str){ method1(str); } public static void main(String[] args) { Sample s = new Sample(); s.method2("hello!"); } }

继承线程的两种方式 继承Thread类的方式

不可以再继承其他类。

extends Thread:

MyThread myThread = new MyThread();

myThread.start();

public class MyThread extends Thread{ //线程中执行的任务代码必须写在run()中 @Override public void run(){ for (int i = 0; i < 1000; i++) { System.out.println("MyThread:"+i); } }}public class Test { //启动java主线程 public static void main(String[] args) { //创建自己实现的线程对象 MyThread myThread = new MyThread();// myThread.run(); 调用run()方法,并非启动线程,就是普通的方法调用。 //启动线程,并不是启动后就会立即执行。在操作系统处排上队了,什么时候执行由操作系统调度算法决定。 myThread.start(); System.out.println("main结束运行"); //此时已经是多线程了 for (int i = 0; i < 100; i++) { System.out.println("main"+i); } }}

实现Runnable接口的方式

还可以继承其他类。

implements Runnable:

ThreadDemo threadDemo = new ThreadDemo();

Thread thread = new Thread(threadDemo);

thread.start();

public class ThreadDemo implements Runnable{ @Override public void run(){ for (int i = 0; i < 1000; i++) { System.out.println("ThreadDemo:"+i); } }}public class Test { public static void main(String[] args) { //方式2 //创建线程的执行任务,并不是线程。 ThreadDemo threadDemo = new ThreadDemo(); //Thread是线程的管理类,创建线程,并为其分配任务。 Thread thread = new Thread(threadDemo); //启动线程 thread.start(); for (int i = 0; i < 100; i++) { System.out.println("main"+i); } }}

Thread类

Thread类是用来管理线程的。

run()方法:实现线程任务

start()方法:启动线程

currentThread():获得当前执行的线程

线程是有优先级的,优先级范围是1-10,默认是5。

SetPriority(int newPriority):设置线程的优先级

getPriority():返回线程的优先级

以下三个方法会影响线程的状态。

join():等待当前线程结束。

sleep():线程休眠指定的时间。

yield():线程主动让出。

线程优先级

优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通过setPriority和getPriority方法来设置或返回优先级。

较高优先级的线程有更多的机会获得CPU的执行权(不是绝对的)。

public class ThreadDemo implements Runnable{ @Override public void run(){ System.out.println("thread:"+Thread.currentThread().getPriority()); //优先级 }}public class Test { public static void main(String[] args) { //方式2 //创建线程的执行任务,并不是线程。 ThreadDemo threadDemo = new ThreadDemo(); Thread thread = new Thread(threadDemo); thread.setName("mythread111"); //为线程设置名称 thread.start(); thread.setPriority(3); Thread.currentThread().setPriority(10); //main优先级设置为10// System.out.println("thread:"+thread.getPriority()); //优先级 System.out.println("main:"+Thread.currentThread().getPriority()); //优先级 }}

调度策略

时间片

抢占式:高优先级的线程抢占CPU。

Java的调度方法

同优先级线程组成先进先出队列,使用时间片策略。

对高优先级,使用优先调度的抢占式策略。

线程生命周期

生命周期:就是什么时候创建,又什么时候销毁(死亡)。

五种状态

新建

MyThread t = new MyThread( );

就绪

运行

阻塞

死亡

sleep():线程休眠指定的时间。

public class ThreadDemo implements Runnable{ @Override public void run(){ for (int i = 0; i < 100; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+i); } }}public class Test { public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo(); Thread thread = new Thread(threadDemo); thread.start(); }}

yield():可以直接从运行状态回到就绪队列中

public class ThreadDemo implements Runnable{ @Override public void run(){ for (int i = 0; i < 100; i++) { if(i%10==0){ //20%10 Thread.yield(); //主动让线程让步,让步后回到就绪队列,有可能又先于main获得执行权 } System.out.println(Thread.currentThread().getName()+":"+i); } }}public class Test { public static void main(String[] args) { ThreadDemo threadDemo = new ThreadDemo(); Thread thread = new Thread(threadDemo); thread.start(); for (int i = 0; i < 100; i++) { System.out.println("main"+i); } }}

join():等待当前线程结束。

public class Test { public static void main(String[] args) throws InterruptedException { ThreadDemo threadDemo = new ThreadDemo(); Thread thread = new Thread(threadDemo); thread.start(); thread.join(); //等待当前线程结束,阻塞main线程 for (int i = 0; i < 100; i++) { System.out.println("main"+i); } }}public class ThreadDemo implements Runnable{ @Override public void run(){ for (int i = 0; i < 100; i++) { if(i%10==0){ //20%10 Thread.yield(); //主动让线程让步,让步后回到就绪队列,有可能又先于main获得执行权 } System.out.println(Thread.currentThread().getName()+":"+i); } }}

线程分类

Java中的线程分为两类:用户线程和守护线程。

正常创建的线程是用户线程,守护线程与用户线程功能是一样的,去完成某件事情。

用户线程的工作完成后就结束了,守护线程是等待所有的用户线程结束后,守护线程才会自动退出。

例如垃圾回收任务,就是在一个守护线程中进行。

守护线程的作用是为其他线程的运行提供便利服务。

用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。

注意:设置线程为守护线程必须在启动线程之前,否则会跑出一个IllegalThreadStateException异常。

//用户线程public class ThreadDemo1 extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+i); } }}//守护线程public class ThreadDemo2 extends Thread{ @Override public void run() { while (true){ System.out.println(Thread.currentThread().getName()); } }}public class Test { public static void main(String[] args) { ThreadDemo1 t1 = new ThreadDemo1(); t1.setName("用户线程"); t1.start(); ThreadDemo2 t2 = new ThreadDemo2(); t2.setName("守护线程"); //setDaemon()将线程设置为守护线程,必须在启动线程前设置,否则会报错 t2.setDaemon(true); t2.start(); }}

多线程

在一个应用程序中可以有多个线程任务执行。

程序中同时需要执行多个任务时,就需要多个线程。

例如,360安全卫士

多线程的优缺点 优点

提高程序的响应(速度)。

提高CPU的利用率。

改善程序结构,将复杂任务分为对个线程,独立运行。

缺点

线程也是程序,所以线程需要占用内存,线程越多占用内存也越多,多线程对内存消耗增加。

可以通过升级硬件设备来解决

多线程需要协调和管理,所以需要CPU时间跟踪线程,多线程对CPU要求提高。

可以通过升级硬件设备来解决

多个线程访问同一个共享资源,会出现线程安全问题(多线程&&访问同一个共享问题)。

现在都是多核CPU,那么就可以在同一时间点上同时处理多个线程。

单一的多线程不会出现线程安全问题,每一个线程都在做自己的事情,没有交集。

线程安全问题由并发引起。

解决线程安全问题:加锁+排队

为出票方法加锁,一次只能有一个线程进入到出票方法中。

线程同步 多线程同步

多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制,即各线程间要有先来后到。

同步就是排队+锁:

几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作。为了保证数据在方法中被访问时的正确性,在访问时加入锁机制。

一个线程持有锁会导致其他所有需要此锁的线程挂起;在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题。

并发与并行 并行

多个CPU同时执行多个任务。

线程并行执行:多个人同时做不同的事情,互不干扰。

并发

在一个时间段内依次执行操作。

线程并发执行:在一个时间段内,依次执行某件事情,一个一个来做,交替做。

例如卖票,抢购,秒杀看似同时进行, 实际是一个一个执行。

必须程序控制线程并发执行 高并发 双十一 秒杀 抢购

同步锁

同步锁可以是任何对象,必须唯一,保证多个线程获得是同一个对象(用来充当锁标记)。

同步执行过程

1.第一个线程访问,锁定同步对象,执行其中代码。

2.第二个线程访问,发现同步对象被锁定,无法访问。

3.第一个线程访问完毕,解锁同步对象。

4.第二个线程访问,发现同步对象没有锁,然后锁定并访问。

模拟卖票线程

两个线程同时访问一个共享数据,现在没有任何控制,两个线程同时可以进入方法中执行

public class TickThread extends Thread{ //假设有10张票 static变量只有一份,多个对象共享 static int num = 10; //模拟出票 @Override public void run() { while (true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(num > 0){ System.out.println(Thread.currentThread().getName()+":"+num); num--; }else { System.out.println("票卖完了"); break; } } }}

窗口1:10窗口2:10窗口2:9窗口2:8窗口2:7窗口2:6窗口2:5窗口2:3窗口2:2窗口2:1票卖完了窗口1:2票卖完了

出现问题:

两个线程同时访问一个共享数据,现在没有任何控制,两个线程同时可以进入方法中执行。

确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁,哪个线程获取了这把锁,才有权利访问该共享资源。

使用同步锁(正确)

在Java代码中实现同步:使用synchronized(同步锁)关键字同步方法或代码块。

synchronized (同步锁){ // 需要被同步的代码; } //synchronized还可以放在方法声明中,表示整个方法,为同步方法。

使用extends:

使用synchronized关键字,synchronized可以修饰代码块。

synchronized修饰方法,如果是非静态方法,那么锁对象是this。如果是静态方法,那么锁对象是类的Class对象(一个类只有一个Class对象)

方法一:

public class TickThread extends Thread{ //假设有10张票 static变量只有一份,多个对象共享 static int num = 10; //多个线程共享的同一个锁对象 static Object obj = new Object(); //模拟出票 @Override public void run() { while (true){ synchronized (obj){ if(num > 0){ System.out.println(Thread.currentThread().getName()+":"+num); num--; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }else { System.out.println("票卖完了"); break; } } } }}public class Test { public static void main(String[] args) { //创建两个线程对象 num是成员变量 TickThread t1 = new TickThread(); t1.setName("窗口1"); t1.start(); TickThread t2 = new TickThread(); t2.setName("窗口2"); t2.start(); }}

方法二:

public class TickThread extends Thread{ //假设有10张票 static变量只有一份,多个对象共享 static int num = 10; //多个线程共享的同一个锁对象 static Object obj = new Object(); //模拟出票 @Override public void run() { while(true){ this.print(); if(num == 0){ System.out.println("票卖完了"); break; } } } public static synchronized void print(){ if(num > 0){ System.out.println(Thread.currentThread().getName()+":"+num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num--; } }}public class Test { public static void main(String[] args) { //创建两个线程对象 num是成员变量 TickThread t1 = new TickThread(); t1.setName("窗口1"); t1.start(); TickThread t2 = new TickThread(); t2.setName("窗口2"); t2.start(); //t1对象地址==t2对象地址 System.out.println(t1.getClass() == t2.getClass()); //true }}

使用Runnable接口:

synchronized是靠底层指令控制实现。

synchronized加锁实现,底层运行是通过编译后的指令中来进行控制的。

synchronized可以修饰代码块,修饰方法,注意锁的对象(锁对象可能会变)。

synchronized加锁方式是隐式的,进入到同步代码块时,自动获取锁;同步代码块执行完成后,自动释放锁。

方法一:

public class TickThread implements Runnable{ //是否加static并无影响,因为只创建了一个线程任务对象,num只有一份 static int num = 10; //这就是共享资源 @Override public void run() { while (true){ //可以用this,因为this只用了一个 synchronized (this){ if(num > 0){ System.out.println(Thread.currentThread().getName()+":"+num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num--; }else { System.out.println("票卖光了"); break; } } } }}public class Test { public static void main(String[] args) { //创建一个出票的任务 TickThread t = new TickThread(); //只创建了一个线程任务对象 Thread t1 = new Thread(t,"窗口1"); t1.start(); Thread t2 = new Thread(t,"窗口2"); t2.start(); }}

方法二:

public class TickThread implements Runnable{ //是否加static并无影响,因为只创建了一个线程任务对象,num只有一份 static int num = 10; //这就是共享资源 @Override public void run() { while(true){ this.print(); if(num == 0){ System.out.println("票卖完了"); break; } } } public static synchronized void print(){ if(num > 0){ System.out.println(Thread.currentThread().getName()+":"+num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num--; } }}public class Test { public static void main(String[] args) { //创建一个出票的任务 TickThread t = new TickThread(); //只创建了一个线程任务对象 Thread t1 = new Thread(t,"窗口1"); t1.start(); Thread t2 = new Thread(t,"窗口2"); t2.start(); }}

Lock接口

Lock锁是靠java代码来控制的。

ReentrantLock类实现了Lock接口,可以来控制与synchronized相同的功能,拥有与synchronized相同的并发性和内存语义,但是两者的实现细节完全不同。

在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

public class TickThread implements Runnable{ static int num = 10; //这就是共享资源 ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true){ try { //加锁 lock.lock(); if(num > 0){ System.out.println(Thread.currentThread().getName()+":"+num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num--; }else { System.out.println("票卖光了"); break; } }finally { //synchronized如果出现异常会自动释放锁 //释放锁,尽量放在finally代码块中释放锁,一旦try中出现异常,也保证把锁释放掉 lock.unlock(); } } }}public class Test { public static void main(String[] args) { //创建一个出票的任务 TickThread t = new TickThread(); //只创建了一个线程任务对象 Thread t1 = new Thread(t,"窗口1"); t1.start(); Thread t2 = new Thread(t,"窗口2"); t2.start(); }}

线程死锁

不同的线程分别占用对方需要的同步资源不放弃,出现死锁,发生死锁后,程序不会报错,只会等待,所有的线程都处于阻塞状态,无法继续。

锁嵌套时,容易发生死锁现象。加锁时,考虑清楚锁的顺序,尽量减少嵌套使用。

public class DieThread extends Thread{ boolean flag ; static Object objA = new Object(); static Object objB = new Object(); public DieThread(boolean flag) { this.flag = flag; } @Override public void run() { //双方分别持有对方需要的同步资源不放弃,造成死锁 if(flag){ synchronized (objA){ System.out.println("if objA"); synchronized (objB){ System.out.println("if objB"); } } }else { synchronized (objB){ System.out.println("else objB"); synchronized (objA){ System.out.println("else objA"); } } } }}public class Test { public static void main(String[] args) { DieThread d1 = new DieThread(true); d1.start(); DieThread d2 = new DieThread(false); d2.start(); }}

线程通信

线程通讯指的是多个线程通过相互牵制,相互调度,制约运行,即线程间的相互作用。

涉及三个方法:

wait():让线程等待,wait()方法后线程就进入到阻塞状态,,并释放同步监视器,必须通过另一个线程唤醒。

notify():唤醒等待中的线程。一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。

notifyAll():唤醒等待中的所有被wait的线程。

注意:wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。

//两个线程交替打印1-100之间的数字public class PrintThread implements Runnable{ int num = 0; //共享变量// Object obj = new Object(); //也可将锁换为obj @Override public void run() { while (true){ synchronized (this){ //唤醒等待的线程 this.notify(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if(num <= 100){ System.out.println(Thread.currentThread().getName()+":"+num); num++; }else { break; } try { //让线程等待。wait方法一定要调用的时锁对象中的wait方法,可以释放掉锁。 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }}public class Test { public static void main(String[] args) { PrintThread p = new PrintThread(); Thread t1 = new Thread(p); t1.start(); Thread t2 = new Thread(p); t2.start(); }}

sleep()和wait()

相同点:

都可以让线程进入阻塞状态。

不同点:

sleep()

是Thread类中的方法

不会释放锁

休眠时间到了后,会自动进入到就绪状态

wait()

是Object类中的方法

wait方法是可以释放锁

wait后的线程需要使用notify/notifyAll唤醒

生产者和消费者问题

两个线程之间相互牵制使用。

问题描述:生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台 处取走产品,生产者一次只能生产固定数量的产品(比如:1),这时柜台中不能再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待。

生产者线程 +1

​ 柜台中的商品

消费者线程 -1

public class Productor extends Thread{ Counter counter; public Productor(Counter counter) { this.counter = counter; } @Override public void run() { while (true){ counter.jia(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}public class Customer extends Thread{ Counter counter; public Customer(Counter counter) { this.counter = counter; } @Override public void run() { while (true){ counter.jian(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}public class Counter { int num = 1; //商品数量为1 public synchronized void jia(){ if(num == 0){ num++; System.out.println("生产者生产了一个商品"); this.notify(); }else { try { this.wait(); //生产者等待 释放锁 } catch (InterruptedException e) { e.printStackTrace(); } } } //消费者调用 public synchronized void jian(){ if(num == 1){ num--; System.out.println("消费者取走了一个商品"); this.notify(); }else { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } }}public class Test { public static void main(String[] args) { Counter counter = new Counter(); //共享同一个柜台,共享数据 Productor productor = new Productor(counter); productor.start(); //启动生产者线程 Customer customer = new Customer(counter); customer.start(); //启动消费者线程 }}

第三种创建线程的方式

继承Thread还是实现Runnable,最终都是重写Run()。run方法没有返回值,也不能抛出异常,就存在局限性。

Java中推出了新的接口 Callable接口,定义了一个call()方法,可以有返回值(使用泛型自定义),可以抛出异常。

实现Callable接口与使用Runnable相比,Callable功能更强大些。

相比run()方法,可以有返回值

方法可以抛出异常

支持泛型的返回值

需要借助FutureTask类,获取返回结果

接收任务:

FutureTask futureTask = new FutureTask(任务);

创建线程:

Thread t = new Thread(futureTask); t.start(); Integer val = futureTask.get(); //获得线程call方法的返回值

示例:

public class SumThread implements Callable { @Override public Integer call() throws Exception { int sum = 0; Thread.sleep(100); for (int i = 0; i <= 10; i++) { sum += i; } return sum; }}public class Test { public static void main(String[] args) { SumThread sumThread = new SumThread(); FutureTask futureTask = new FutureTask(sumThread); Thread t = new Thread(futureTask); t.start(); try { Integer sum = futureTask.get(); //获得线程所返回的结果 System.out.println(sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }}

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。