谁有bt365体育在线网址-mobile.365-588-36563688

mobile.365-588

Java基础——深入理解Java中的多线程(超级详细,值得你看)

2025-11-07 20:24:27 作者 admin 阅读 6316
Java基础——深入理解Java中的多线程(超级详细,值得你看)

Java中的多线程

进程(process)是程序的一次执行过程,或是正在运行的有一个程序,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期。

线程(thread),进程可进一步细化线程是一个程序内部的一个执行路径

若一个进程用以时间并行执行多个线程,就是支持多线程线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序技术器(PC),线程切换开销小一个进程中的多个线程共享相同的内存单元/内存地址空间->他们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就睡带来安全隐患。

并行与并发

并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀。多个人做同一件事。

多线程的优点:

提高应用程序的响应。对图形化页面更有意义,可增强用户体验提高计算机系统CPU的利用率改善程序结果,将既长又复杂的的进程分为多线程,独立运行,利于理解和改善

多线程的创建

方式一:继承与Thread类

创建一个继承与Thread类的子类重新Thread了的run()创建Thread类的子类的独享通过此对象调用start():(1)启动当前线程(2)调用当前线程的run()

线程中常用的方法解析

package com.haust.java;

/*测试Thread中的常用方法:

* 1、start():启动当前线程,调用当前线程的run()

* 2、run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中

* 3、currentThread():静态方法,返回执行当前代码的线程

* 4、getName():获取当前线程的名字

* 5、setName():设置当前线程的名字

* 6、yield():释放当前CPU的执行权

* 7、join():在线程A中,调用线程B的join方法,此时线程A就进入阻塞状态,直至线程B完全执行,线程A才继续执行

* 8、stop():已过时,当执行此方法时,强制结束当前线程

* 9、sleep(long millitime):让当前线程休眠一段指定的时间,参数的单位是毫秒;1000毫秒 = 1秒,在指定的时间内,当前线程是阻塞状态

* 10、isAlive():判断当前线程是否还存活*/

public class ThreadMethodTest {

public static void main(String[] args) {

HelloThread h1 = new HelloThread("线程1:");

// h1.setName("线程一:");

h1.start();

//给主线程命名:

Thread.currentThread().setName("主线程:");

for (int i = 0; i < 100; i++) {

if(i % 2 == 0){

System.out.println(Thread.currentThread().getName()+":" +i);

}

if(i==20){

try {

h1.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

}

class HelloThread extends Thread{

@Override

public void run() {

for (int i = 0; i < 100; i++) {

if(i % 2 == 0){

try {

sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+":" +i);

}

if(i%20==0){

yield();

}

}

}

public HelloThread(String name){

super(name);

}

}

线程的调度

时间片抢占式:高优先级的线程抢占CPU

Java的调度方法

同优先级线程组成先进先出队列(先到先服务),使用时间片策略对高优先级,使用优先调度的抢占式策略

线程的优先级

MAX_PRIORITY:10

MIN_PRIORITY:1

NORM_PRIORITY:5

涉及的方法

getPriority():返回线程优先值setPriority(int newPriority):改变线程的优先级

说明:

线程创建时继承父线程的优先级低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

卖票案例(存在线程安全问题)

package com.haust.java;

/*例子:创建三个窗口卖票,总票数为100张

* 存在线程安全问题,待解决*/

class Window extends Thread{

private static int ticket = 100;

@Override

public void run() {

while(true){

if(ticket > 0){

System.out.println(getName() + "卖票,票号为:"+ticket);

ticket--;

}

else{

break;

}

}

}

}

public class WindowTest {

public static void main(String[] args) {

Window t1 = new Window();

Window t2 = new Window();

Window t3 = new Window();

t1.setName("窗口1");

t2.setName("窗口2");

t3.setName("窗口3");

t1.start();

t2.start();

t3.start();

}

}

方式二:实现Runnable接口

package com.haust.java;

/*创建多线程的方式2:实现Runnable接口

* 1、创建实现了Runnable接口的类

* 2、实现类去实现Runnable中的抽象方法:run()

* 3、创建实现类的对象

* 4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

* 5、通过Thread类的对象调用start()*/

public class ThreadTest1 {

public static void main(String[] args) {

// 3、创建实现类的对象

MThread mThread = new MThread();

// 4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

Thread thread = new Thread(mThread);

// 5、通过Thread类的对象调用start()①启动线程②调用当前线程内的run()方法--->调用了Runnable类型的target的run()

thread.setName("线程1:");

thread.start();

// 再启动一个线程,遍历100以内的偶数

Thread thread1 = new Thread(mThread);

thread1.setName("线程2:");

thread1.start();

}

}

//1、创建实现了Runnable接口的类

class MThread implements Runnable{

//2、实现类去实现Runnable中的抽象方法:run()

@Override

public void run() {

for (int i = 0; i < 100; i++) {

if (i % 2 == 0) {

System.out.println(Thread.currentThread().getName()+ ":" + i);

}

}

}

}

比较创建线程的两种方式

开发中优先选择实现Runnable方式

原因:

实现的方式没有类的单继承性的局限性实现的方式更适合来处理多个线程共享数据的情况

相同点:都需要重写run(),将线程要执行的代码声明在run()方法中

线程的生命周期

解决线程安全问题;

使用同步代码块

package com.haust.java;

/*出现线程安全问他的 解决方法:

* 当一个线程a在操作ticket的时候,其他线程不能参与进来,直到线程参与进来,也操作完ticket时,线程才可以开始操作ticket,这种情况即使线程a出现了阻塞,也不能被改变*/

/*在Java中,我们通过同步机制,来解决线程的安全问题

*

* 方式1:同步代码块

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

* 说明:操作共享数据的代码,即为需要被同步的代码

* 共享数据:多个线程共同操作的变量,比如:ticket就是共享数据

* 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁.

* 要求:多个线程必须要共用同一把锁。

*

* 方式2:同步方法

*

* 同步的方式解决了线程安全问题——好处

* 操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低

* */

public class WindowTest1 {

public static void main(String[] args) {

Window1 w = new Window1();

Thread thread1 = new Thread(w);

thread1.setName("窗口1");

Thread thread2= new Thread(w);

thread2.setName("窗口2");

thread1.start();

thread2.start();

}

}

class Window1 implements Runnable{

private int ticket = 100;

Object obj = new Object();

@Override

public void run() {

while(true){

synchronized (obj){

if(ticket > 0){

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + "票号:" + ticket);

ticket--;

}

else

{

break;

}

}

}

}

}

使用同步方法

线程的死锁问题

死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

解决方法

专门的算法、原则进来减少同步资源的定义进来避免嵌套同步

解决线程安全问题方式三:

package com.haust.java1;

import java.util.concurrent.locks.ReentrantLock;

/*解决线程安全问题的方式三:Lock锁——JDK5.0新增

*

* 面试题:synchronized与Lock的异同点

* 相同点:二者都可以解决线程安全问题

* 不同点:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器

* Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())

* 面试题:如何解决线程安全问题 */

public class LockTest {

public static void main(String[] args) {

Window w = new Window();

Thread t1 = new Thread(w);

Thread t2 = new Thread(w);

Thread t3 = new Thread(w);

t1.setName("窗口1");

t2.setName("窗口2");

t3.setName("窗口3");

t1.start();

t2.start();

t3.start();

}

}

class Window implements Runnable{

private int ticket = 100;

//1、实例化ReentrantLock

private ReentrantLock lock = new ReentrantLock(true);

@Override

public void run() {

while(true){

try {

//2、调用Lock方法

lock.lock();

if(ticket > 0){

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+"卖票:"+ticket);

ticket--;

}

else {

break;

}

}finally {

//3、调用解锁方法:unlock()

lock.unlock();

}

}

}

}

存钱案例

package com.haust.exer;

/*银行有一个账户

* 有两个储户分别想同一个账户存3000元,每次存1000,存3次,每次存完打印账户余额

*

* 分析:

* 1、是否是多线程问题?是,两个储户线程

* 2、是否有共享数据?有,账户(或账户余额)

* 3、是否有线程安全问题?有

* 4、是否需要考虑如何解决线程安全问题?同步机制:有三种方式

* */

class Account{

private double balance;

public Account(double balance) {

this.balance = balance;

}

//存储

public synchronized void deposit(double amt){

if (amt > 0){

balance += amt;

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName()+"存钱成功,账户余额为:" + balance);

}

}

}

class Customer extends Thread{

private Account account;

public Customer(Account account){

this.account = account;

}

@Override

public void run() {

for (int i = 0; i < 3; i++) {

account.deposit(1000);

}

}

}

public class AccountTest {

public static void main(String[] args) {

Account account = new Account(0);

Customer c1 = new Customer(account);

Customer c2 = new Customer(account);

c1.setName("甲");

c2.setName("乙");

c1.start();

c2.start();

}

}

线程通信的例子

package com.haust.java2;

/*线程通信的例子:使用两个线程打印1-100,线程A,线程B,交替打印

* 涉及到的三个方法:

* wait()一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器

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

* notifyAll():一旦执行此方法,就会唤醒所有被wait的线程

*

* 说明:

* 1、wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中

* 2、wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常*/

class Number implements Runnable{

private int number = 1;

@Override

public void run() {

while (true){

synchronized (this){

notify();

if(number <= 100){

System.out.println(Thread.currentThread().getName()+":"+number);

number++;

try {

wait();//使得调用如下wait()方法的进程进入阻塞状态

} catch (InterruptedException e) {

e.printStackTrace();

}

}

else

{

break;

}

}

}

}

}

public class CommunicationTest {

public static void main(String[] args) {

Number number = new Number();

Thread t1 = new Thread(number);

Thread t2 = new Thread(number);

t1.setName("A");

t2.setName("B");

t1.start();

t2.start();

}

}

面试题:sleep()方法和wait()方法的异同

相同点:

一旦执行方法,都可以使得当前的线程进入阻塞状态

不同点:

两个方法声明的位置不同

Thread类中声明sleep(),Object类中声明wait()

调用的要求不同:

sleep()可以用在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中

关于是否释放同步监视器

如果两个方法都是用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁

经典消费者与生产者案例

package com.haust.java2;

/*线程通信的应用

*

* 分析:

* 1、是否有多线程问题?是,生产者线程,消费者线程

* 2、是否有共享数据?是店员

* 3、如何解决线程的安全问题?同步机制三种方法

* 4、是否涉及到线程的通信?是

*

* */

class Clerk{

private int productCount = 0;

//生产产品

public synchronized void produceProduct() {

if(productCount < 20){

productCount++;

System.out.println(Thread.currentThread().getName()+"开始生产第"+productCount+"个产品");

notify();

}

else {

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

//消费产品

public synchronized void consumeProduct() {

if (productCount > 0){

System.out.println(Thread.currentThread().getName()+"开始销售第"+productCount+"个产品");

productCount--;

notify();

}

else {

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

class Producer extends Thread{

private Clerk clerk;

public Producer(Clerk clerk){

this.clerk = clerk;

}

@Override

public void run() {

System.out.println(getName()+":开始生产产品……");

while(true){

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

clerk.produceProduct();

}

}

}

class Consumer extends Thread{

private Clerk clerk;

public Consumer(Clerk clerk){

this.clerk = clerk;

}

@Override

public void run() {

System.out.println(getName()+":开始购买产品……");

while(true){

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

clerk.consumeProduct();

}

}

}

public class ProductTest {

public static void main(String[] args) {

Clerk c = new Clerk();

Producer p1 = new Producer(c);

p1.setName("生产者1");

Consumer c1 = new Consumer(c);

c1.setName("消费者1");

p1.start();

c1.start();

}

}

JDK5.0新增线程创建方式

新增方式一:实现Callable接口

与使用Runnable相比,Callable功能更强大一下

相比run()方法,可以有返回值方法可以抛出异常支持范型的返回值需要借助FutureTask类,比如获取返回结果

Future接口

可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等FutureTask是Future接口的唯一实现类FutureTask同时实现了Runnable,Future接口,它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

package com.haust.java2;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;

/*创建线程的方式三:实现Callable接口。JDK5.0新增

* */

//1、创建一个实现Callable的实现类

class NumThread implements Callable{

// 2、实现call()方法,将此线程需要执行的操作声明在call()中

@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 ThreadNew {

public static void main(String[] args) {

// 3、创建callable接口实现类的对象

NumThread numThread = new NumThread();

// 4、将此callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象

FutureTask futureTask = new FutureTask(numThread);

Thread thread = new Thread(futureTask);

thread.start();

try {

//get()返回值即为FutureTask构造参数Callable实现类重写的call()的返回值

Object sum = futureTask.get();

System.out.println("总和为:"+sum);

} catch (InterruptedException e) {

e.printStackTrace();

} catch (ExecutionException e) {

e.printStackTrace();

}

}

}

创建多线程的方式四

package com.haust.java2;

import java.util.concurrent.Executor;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

/*创建多线程的方式四*/

public class ThreadPool {

public static void main(String[] args) {

ExecutorService service = Executors.newFixedThreadPool(10);

service.execute(new NumThread1());//适合适用于Runnable

service.execute(new NumThread2());//适合适用于Runnable

// service.submit(Callable callable);//适合用于Callable

}

}

class NumThread1 implements Runnable{

@Override

public void run() {

for (int i = 0; i <= 100 ; i++) {

if (i %2 == 0) {

System.out.println(Thread.currentThread().getName()+":"+i);

}

}

}

}

class NumThread2 implements Runnable{

@Override

public void run() {

for (int i = 0; i <= 100 ; i++) {

if (i %2 != 0) {

System.out.println(Thread.currentThread().getName()+":"+i);

}

}

}

}

使用线程池的好处

提升响应速度(减少了创建新线程的时间)

降低资源消耗(重复利用线程池中线程,不需要每次都创建)

便于线程管理

/ service.submit(Callable callable);//适合用于Callable

}

}

class NumThread1 implements Runnable{

@Override

public void run() {

for (int i = 0; i <= 100 ; i++) {

if (i %2 == 0) {

System.out.println(Thread.currentThread().getName()+":"+i);

}

}

}

}

class NumThread2 implements Runnable{

@Override

public void run() {

for (int i = 0; i <= 100 ; i++) {

if (i %2 != 0) {

System.out.println(Thread.currentThread().getName()+":"+i);

}

}

}

}

## 使用线程池的好处

- 提升响应速度(减少了创建新线程的时间)

- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

- 便于线程管理

相关文章