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

JavaSE---多线程

时间:2023-06-08
多线程的概念

多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行 执行的线程来完成各自的任务。

何时需要多线程
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、 网络操作、搜索等。
需要一些后台运行的程序时。
多线程的优点
提高程序的响应.
提高CPU的利用率.
改善程序结构,将复杂任务分为多个线程,独立运行.
多线程的缺点
对内存消耗高:线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
对CPU要求高:多线程需要协调和管理,所以需要CPU时间跟踪线程;
线程安全问题:线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
单一的多线程不会出现安全问题,每个线程都在做自己的事情,没有交集。

线程同步

为了解决线程安全问题,同步=排队+锁;

并发与并行

并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:在一个时间段内依次执行操作.例如卖票,抢购,秒杀看似同时进行, 实际是一个一个执行

多线程同步

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

同步就是排队+锁:

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

同步锁

同步锁可以是任何对象,必须唯一,保证多个线程获得是同一个对象(用 来充当锁标记).
同步执行过程
1.第一个线程访问,锁定同步对象,执行其中代码.
2.第二个线程访问,发现同步对象被锁定,无法访问.
3.第一个线程访问完毕,解锁同步对象.
4.第二个线程访问,发现同步对象没有锁,然后锁定并访问.

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

案例:模拟卖票 两个窗口分别售票,票数为10张
分别使用继承Thread和实现Runnable两种方式实现
没加锁时,直接共享资源,出现了线程安全问题

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

继承Thread

使用synchronized(同步锁)关键字同步方法或代码块。

static int num = 10;//假设有10张票 static变量只有一份,多个对象共享 static Object obj = new Object();//多个线程共享的同一个锁对象 //模拟出票 @Override public void run() { while(true){ synchronized(obj){ 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; } } } }

synchronized还可以放在方法声明中,表示整个方法,为同步方法。
synchronized修饰非静态方法时,锁对象默认是this
synchronized修饰的是static方法,那么锁对象是类的Class对象.
每一个类被加载到内存时,就会为类创建一个Class类的对象,无论创建了多少个对象,Class对象只有一个

//模拟出票 @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--; } }

实现Runnable

synchronized (this)此时就可以用this,这里只有一个对象

int num = 10;//这就是共享资源 @Override public void run() { while (true){ 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 synchronized void print()就不用再定义为static

public class TicketThread2 implements Runnable{ int num = 10;//这就是共享资源 @Override public void run() { while(true){ this.print(); if(num==0){ System.out.println("票卖完啦"); break; } } } public synchronized void print(){ if(num>0){ System.out.println(Thread.currentThread().getName()+":"+num); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } num--; } }}

synchronized关键字

synchronized加锁实现,靠底层指令控制实现
synchronized可以修饰代码块和方法,注意锁的对象(锁对象可能会变)
1-修饰代码块
synchronized (同步锁){
// 需要被同步的代码;
}
synchronized(锁对象) 锁对象可以是任何对象,但是必须是唯一的,也就是多个线程对应的是同一个锁对象.
在对象中,有一个区域对叫对象头,象头中有一个锁的标志为,无锁和已被使用
2- synchronized修饰方法
synchronized还可以放在方法声明中,表示整个方法,为同步方法。
synchronized修饰非静态方法时,锁对象默认是this
synchronized修饰的是static方法,那么锁对象是类的Class对象.
每一个类被加载到内存时,就会为类创建一个Class类的对象,无论创建了多少个对象,Class对象只有一个

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

Lock(锁)

从JDK 5.0开始,Java提供了更强大的线程同步机制-通过显式定义同步锁对象 来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的 工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁, 线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存 语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加 锁、释放锁.
Lock是靠java代码来控制的
Lock只能在某一段代码加锁,而且是显式的加锁和释放锁
注意:最好写在

try{
代码块
}finally{
lock.unlock();
}

里面,一旦出现异常,也保证把锁释放掉,以免出现死锁

package day16.Class;import java.util.concurrent.locks.ReentrantLock;public class TicketThread3 implements Runnable{ 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 { lock.unlock();//释放锁 } } }}

死锁

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

package day16.Class;public class DieThread extends Thread{ boolean flag; public DieThread(boolean flag) { this.flag = flag; } static Object objA = new Object(); static Object objB = new Object(); @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 if objB"); synchronized (objA) { System.out.println("else if objA"); } } } }}

线程通信

线程通讯指的是多个线程通过相互牵制,相互调度,即线程间的相互作用。
涉及三个方法:
wait一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait, 就唤醒优先级高的那个。
notifyAll一旦执行此方法,就会唤醒所有被wait的线程。
注意: .wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
wait()和sleep()
相同:让线程进入阻塞状态
不同:sleep()是Thread类的方法,不会释放锁,休眠时间到了后,会自动进入就绪状态
wait()是Object类的方法,会释放锁。wait后的线程,需要使用notify或notifyAll来唤醒
案例:两个线程交替打印1-100之间的数字

package day16.Class;public class PrintThread implements Runnable{ int num = 0; @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 { this.wait();//让线程等待,一定要调用的是锁对象的wait() } catch (InterruptedException e) { e.printStackTrace(); } } } }}

经典例题:生产者/消费者问题

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

package day16.Class.PCquestion;//生产者public class Productor extends Thread{ Counter counter; public Productor(Counter c) { this.counter = c; } @Override public void run() { counter.jia(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }}

package day16.Class.PCquestion;//消费者public class Customer extends Thread{ Counter counter; public Customer(Counter c) { this.counter = c; } @Override public void run() { while (true){ counter.jian(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}

package day16.Class.PCquestion;//柜台public class Counter { int num = 1; //生产者调用 //synchronized修饰的是非静态方法,锁对象默认是this,Counter c = new Counter(); 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(); } } }}

package day16.Class.PCquestion;public class Test { public static void main(String[] args) { Counter c = new Counter(); Productor p = new Productor(c); p.start();//启动生产线程 Customer co = new Customer(c); co.start();//启动消费线程 }}

新增创建线程方式Callable接口

继承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方法的返回值

package day16.Class.NewThread;import java.util.concurrent.Callable;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; }}

package day16.Class.NewThread;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;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答案网 版权所有 备案号:

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