进程
说多线程之前,先了解一下什么是进程,进程指的就是正在运行的程序,如下图
任务管理去中的这些就是正在运行的程序,也叫进程
线程
再说一下线程,线程是一个进程的执行单元,负责当前进程中的执行,一个进程中最少包含一个线程,当然可可以包含多个线程。
两种线程模型
用户线程(ULT)
用户程序实现,不依赖操作系统核心,应用提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/核心态切换,速度快。内核对ULT无感知,线程阻塞则进程(包括它所有线程)阻塞。
内核线程(KLT)
系统内核管理线程(KLT),内核保存线程的状态和上下文信息,线程阻塞不会引起进程阻塞。在多处理系统上,多线程在多处理器上并行运行。现成的创建调度和管理由内核完成,效率比ULT要慢,比进程操作快。
单线程
单线程,一次执行,当上一个程序执行完毕之后,下一个才可以开始执行,(就像超市排队结账一样,收银员算完前一个人了钱,才可以算下一个的,不可能一块算两个人的)
多线程
多线程,多个任务一起执行。(如上,收银一个一个结账,这个时候 ,就可以在旁边在来几个收银台,这样就可以一起结几个人的账了。)
创建方式
1.继承Thread
步骤:
创建一个类继承Thread =》重写run()方法 =》 调用start()开启线程
实现:
public class ThreadA{
public static void main(String[] args) {
//创建爱你自定义线程对象
MyThread mt=new MyThread("新的线程!");
//开启新的线程
mt.start();
//在主方法中执行for循环
for (int i=0;i<10;i++){
System.out.println("mian线程!"+i);
}
}
}
MyThread实现类
public class MyThread extends Thread {
//定义指定线程名称的构造方法
public MyThread(String name){
//调用父类的String参数的构造方法,指定线程的名字
super(name);
}
//重写run方法 ,完成线程执行的逻辑
@Override
public void run() {
for (int i=0;i<10; i++){
System.out.println(getName()+"继承Thread正在执行!"+i);
}
}
}
调用Thread中的run方法不是开启线程,只是对象的调用。
线程对象调用start方法才是开启线程,并让jvm调用run方法在开启的线程中执行。
输出结果
这个每次输出都是不一样的这个主要看的是cup的调用。
2.实现Runnable接口
步骤:
常见实现类 =》继承Runnable接口 =》重写run()方法 =》创建Thread实例把声明的接口类放进去 =》调用start()方法。
实现:
class RunnableA{
public static void main(String[]args){
MyRnnable myRnnable=new MyRnnable();
myRnnable.setName("新的Runnable线程!");
Thread th=new Thread(myRnnable);
th.start();
//在主方法中执行for循环
for (int i=0;i<10;i++){
System.out.println("mian线程!"+i);
}
}
}
MyRunable实现类
public class MyRnnable implements Runnable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void run() {
for (int i=0;i<10; i++){
System.out.println(getName()+"实现Runnable接口"+i);
}
}
}
运行结果
3.通过Callable和FutureTask创建线程
步骤:
- 创建Callable接口实现类,并实现call方法。
- 创建Callable实现类的实现类,使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的call方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动线程
- 调用FutureTask对象的get()来获取子线程执行结束的返回值。
实现:
Callable接口实现类
import java.util.concurrent.Callable;
public class MyTickets implements Callable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object call() throws Exception {
for (int i=0;i<10; i++){
System.out.println(getName()+"实现Callable接口"+i);
}
return null;
}
}
Callable实现类的实现
class TicketsA{
public static void main(String[] args) {
Callable ca=new MyTickets();
FutureTask<Object> task = new FutureTask<Object>(ca);
Thread thread = new Thread(task);
thread.start();
//在主方法中执行for循环
for (int i=0;i<10;i++){
System.out.println("mian线程!"+i);
}
}
}
Lamda表达式
λ希腊字母表中排序第十一位字母,英文名称Lambda
它避免了匿名函数类定义过多
其实质属于函数式编程的概念
(params)->expression[表达式]
(params)->statement[语句]
(params)->{statements}
//例子
a->System.out.println("lambda-->"+a)
为什么要用lambda表达式
避免匿名内部类定义过多
可以让代码看起来比较简洁
去掉了一堆没有意义的代码,只留下核心代码。
什么情况下使用?
任何接口,如果质保函唯一一个抽象方法,那么他就是一个函数式接口
对于函数式接口,我们通过lambda表达式可以来创建接口的对象。
实现方法
//1.定义一个函数式接口
interface ILIke{
void lambda();
}
//2.实现类
class Likes implements ILIke{
@Override
public void lambda() {
System.out.println("我是lambda11111111!");
}
}
class TestLambda{
// (1).调用外部实现类
ILIke like=new Likes();
//使用lambda简化
like = ()->{
System.out.println("我是lambda表达式!");
};
like.lambda();
}
生命周期
线程的声明周期分为五个步骤
1. 新建状态
当new一个线程出来的时候,该线程就处于新建创建,然后通过调用线程中的start() 方法,来让线程进入就绪状态。
2. 就绪状态
处于就绪状态线程具备了运行条件,但还没分配到CPU,处于线程就绪队列,等待系统为其分配CPU。当系统选定一个等待执行的线程后,它就会从就绪状态进入运行状态,该动作称为“CPU调度”。
3. 运行状态
在处于运行状态的线程,执行自己run方法中的代码,知道等待某资源阻塞或者完成的任何死亡,如果给定的事件片内没有执行结束,就会被系统给换下来回到就绪状态。
4. 阻塞状态
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或者等到I/O设备等资源,将让出cpu暂时的停止自己运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列,只有当引起阻塞的原因消除时,如睡眠时间已到,或者等待的I/O设备空闲下来,线程边转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续执行。
5. 死亡状态
死亡状态就是线程生命周期的最后一个阶段。线程死亡的原因有三个,一个是正常运行的线程完成了它的 全部工作;另一个就是线程被强制性的终止,如通过stop方法来终止一个线程【不推荐使用】;三是线程抛出为未捕捉异常。
状态之间的转换图
线程中的常用方法
方法名称 | 方法描述 |
---|---|
void stop() | 是当前这个线程立即停止,有的正在运行的线程立刻停止导致数据不完整,所以不推荐使用。 |
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒书内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他的线程 |
void interrupt() | 中断线程,不推荐使用这个方式 |
boolean isAlive() | 测试线程是否处于活动状态 |
void start() | 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
void run() | 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
void setName(String name) | 改变线程名称,使之与参数 name 相同 |
代码测试:
stop()方法:
是当前正在执行的线程立即停止。
class ThreadStop implements Runnable{
private boolean flag=true;
@Override
public void run() {
int i =0;
while (flag){
System.out.println("run 方法 跑起来了。。。"+i++);
}
}
//stop()方法停止线程立即停止现在不推荐使用了
public static void main(String[] args) {
ThreadStop stops = new ThreadStop();
Thread t=new Thread(stops);
t.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main的方法"+i);
if (i==900){
t.stop();
System.out.println("线程停止了");
}
}
}
}
sleep()方法
模拟网络延迟:放大问题的发生性
用抢票来实现
class NetworkDelay implements Runnable{
//票数
private int num=10;
public void run(){
while(true){
//加锁的作用就是让线程同步,不然有可能两个人同时抢到一张票,或者票数有可能为负数
synchronized(this){
if(num<=0){
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+num--+"张票");
}
}
}
public static void main(String[] args) {
NetworkDelay networkDelay = new NetworkDelay();
//模拟出他同时几个人在抢票
new Thread(networkDelay,"小明").start();
new Thread(networkDelay,"小李").start();
new Thread(networkDelay,"小红").start();
new Thread(networkDelay,"小小").start();
}
}
用sleep来模拟倒计时
class CountDown{
public static void main(String[] args) {
try {
tenDown();
}catch (InterruptedException e){
e.printStackTrace();
}
}
public static void tenDown() throws InterruptedException {
int num=10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num==0)break;
}
}
}
yieid()方法
线程礼让先让另一个线程执行。
class yieldA implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();//线程礼让
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
public static void main(String[] args) {
yieldA yi=new yieldA();
new Thread(yi,"a").start();
new Thread(yi,"b").start();
}
}
yieid()方法
线程插队,合并线程,等待此线程执行完毕之后,其他的线程才能执行,在join线程运行时,其他线程只能阻塞。
class JoinA implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("join线程"+i);
}
}
public static void main(String[] args) throws InterruptedException {
JoinA jo=new JoinA();
Thread thread = new Thread(jo);
for (int i = 0; i < 100; i++) {
if (i==20){
thread.start();
thread.join();//插队
}
System.out.println("mian线程"+i);
}
}
}
线程的优先级
java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,现成的调用器按照优先级决定应该调用哪个线程来执行。
线程优先级用数字表示 范围1~10
优先级低只意味着获得调度的概率低,并不是优先级低就不会被调用了,折哦度是看cpu的调度的。
测试线程的优先级
class PriorityA{
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
Thread t5 = new Thread(myPriority);
Thread t6 = new Thread(myPriority);
//设置优先级在启动
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(2);
t3.start();
t4.setPriority(3);
t4.start();
t5.setPriority(4);
t5.start();
t6.setPriority(5);
t6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-——->"+Thread.currentThread().getPriority());
}
}
观察线程的状态
- NEW :尚未启动的线程处于此状态
- RUNNABLE : 在Java虚拟机中执行的线程处于次状态
- BLOCKED : 被阻塞等待监视器锁定的线程处于此状态
- WAITING:正在等待另一个线程执行特定动作的线程处于此状态。
- TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
- TERMINATED:已经退出线程处于此状态
测试:
class StateA{
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("//");
});
//观察状态
Thread.State state=thread.getState();
System.out.println(state);//NEW
//观察启动后的状态
thread.start();//启动线程
state=thread.getState();
System.out.println(state);//RUN
//只要线程不终止,就一直循环
while (state != Thread.State.TERMINATED){
Thread.sleep(100);
state=thread.getState();
System.out.println(state);//
}
}
}
输出查看结果。
守护线程(daemon)
线程分为用户线程和守护线程。
虚拟机必须确保用户线程执行完毕。
虚拟机不用等待守护线程执行完毕。
守护线程就是能之一跑着,直到守护线程守护的那个线程结束之后,守护线程就结束了。
如,后台操作日志,监控内存,垃圾回收等。
class TetsGod{
public static void main(String[] args) {
Guard guard = new Guard();
BeGuarded beGuarded = new BeGuarded();
Thread thread=new Thread(guard);
thread.setDaemon(true);//默认是false表示 被守护线程,正常的线程都是被守护线程。
//启动守护线程
thread.start();
//启动被守护线程
new Thread(beGuarded).start();
}
}
//守护线程
class Guard implements Runnable{
@Override
public void run() {
while (true){
System.out.println("我是守护线程!!");
}
}
}
//被守护线程
class BeGuarded implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是被守护的线程!!!!");
}
System.out.println("被守护线程结束了!");
}
}
线程同步
多个线程操作同一个资源。
就如上边sleep方法中的 抢票。
概念
处理多线程的时候,多个线程访问同一个对象,并且某些线程还想修改这个对象。这个时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要访问此对象的线程进入这个对象的等待池
形成队列,等待前面线程使用完毕,下一个线程在使用。
线程同步的形成条件:队列+锁
由于统一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问是的正确性,在访问时加速锁机制 synchronized
,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可。存在的问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起。
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延迟,引起性能问题。
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
先看一下不安全的案例
比如说买票,买车票如果线程不安全,可能导致两个用户同时买到一张票。或者导致票数为负数。
例子
class UnsafeBuy{
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"用户1").start();
new Thread(buyTicket,"用户2").start();
new Thread(buyTicket,"用户3").start();
new Thread(buyTicket,"用户4").start();
}
}
class BuyTicket implements Runnable{
//票数
private int num =10;
//外部停止的方法
boolean flag=true;
@Override
public void run() {
//买票
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
//判断是否有票
if (num<=0){
flag=false;
return ;
}
//模拟延迟
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+num--);
}
}
输出结果:
可以看到最后那两个用户买到了票数为负数的
再来看一个线程不安全的例子
比如说 两个人同时取一张卡里边的钱 ,那么有可能导致卡里边的钱为负数。
例子
class UnsafeBank {
public static void main(String[] args) {
//账户
Account account=new Account(100,"存款");
Drawing you=new Drawing(account,50,"你");
Drawing wo=new Drawing(account,100,"我");
you.start();
wo.start();
}
}
//账户
class Account{
int money;//账户余额
String name;//卡名
public Account(int money,String name){
this.money=money;
this.name=name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account;//账户
int drawingMoney;//取了多少钱
int nowMoney;//现在手里有多少钱
public Drawing(Account account, int drawingMoney, String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
//取钱
@Override
public void run() {
//判断有没有钱
if(account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够了!");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额=余额-你取的钱
account.money=account.money-drawingMoney;
//你手里的钱
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
//Thread.currentThread().getName =this.getName()
System.out.println(this.getName()+"手里的钱"+nowMoney);
}
}
输出结果
可以看到卡里边的钱为负数
解决线程不安全的方法就是同步方法
synchronized
同步方法
由于我们通过private关键字来保证了数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized方法
和synchronized块
。
synchronized方法控制对“对象”的访问,每一个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则贤臣会阻塞,方法一旦执行,就独占该锁,知道方法执行完成返回才释放锁,后边被阻塞的线程才能够拿到这个锁,继续执行。
缺陷
若一个大方法中拿到了synchronized将会影响效率、浪费资源。
实现方法
上边买票的线程不安全的例子中在进行修改的代码上加上synchronized 就行了。 就是这么简单
class UnsafeBuy{
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"用户1").start();
new Thread(buyTicket,"用户2").start();
new Thread(buyTicket,"用户3").start();
new Thread(buyTicket,"用户4").start();
}
}
class BuyTicket implements Runnable{
//票数
private int num =10;
//外部停止的方法
boolean flag=true;
@Override
public void run() {
//买票
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//synchronized 同步方法
private synchronized void buy() throws InterruptedException {
//判断是否有票
if (num<=0){
flag=false;
return ;
}
//模拟延迟
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"拿到"+num--);
}
}
如上是synchronized的一种用法 在进行操作修改的代码方法上加上 synchronized关键字即可
第二种synchronized的用法(锁块)
拿上边的线程不安全的取钱例子来进行修改
class UnsafeBank {
public static void main(String[] args) {
//账户
Account account=new Account(100,"存款");
Drawing you=new Drawing(account,50,"你");
Drawing wo=new Drawing(account,100,"我");
you.start();
wo.start();
}
}
//账户
class Account{
int money;//账户余额
String name;//卡名
public Account(int money,String name){
this.money=money;
this.name=name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account;//账户
int drawingMoney;//取了多少钱
int nowMoney;//现在手里有多少钱
public Drawing(Account account, int drawingMoney, String name){
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
//取钱
@Override
public void run() {
synchronized (account){
//判断有没有钱
if(account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够了!");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额=余额-你取的钱
account.money=account.money-drawingMoney;
//你手里的钱
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
//Thread.currentThread().getName =this.getName()
System.out.println(this.getName()+"手里的钱"+nowMoney);
}
}
}
只需在进行操作 修改的代码体上加上一个synchronized块即可。
书写格式
synchronized(Object){ 代码 }
多线程中的锁
死锁
概念
多线程中各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而到时两个或多个线程都在等待对方释放资源,都停止线执行的情形,某一个同步块同时拥有两个以上对象的锁
时,就可能会发生“死锁”的问题。
产生死锁的必要条件
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
代码
class MainSiSuo{
public static void main(String[] args) {
Deadlock u1=new Deadlock(0,"用户1");
Deadlock u2=new Deadlock(1,"用户2");
u1.start();
u2.start();
}
}
class User1{
}
class User2{
}
class Deadlock extends Thread{
static User1 user1=new User1();
static User2 user2=new User2();
int choice;//选择
String name;//用户
Deadlock(int choice,String name){
this.choice=choice;
this.name=name;
}
@Override
public void run() {
try {
eachOther();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//两个用户互相拿有对方的资源
private void eachOther() throws InterruptedException {
if (choice==0){
synchronized (user1){
System.out.println(this.name+"获得用户1的锁");
Thread.sleep(1000);
synchronized (user2){//一秒钟后去拿用户2的锁
System.out.println(this.name+"获得用户2的锁");
}
}
}else{
synchronized (user2){
System.out.println(this.name+"获得用户2的锁");
Thread.sleep(2000);
synchronized (user1){//2秒钟后去拿用户2的锁
System.out.println(this.name+"获得用户1的锁");
}
}
}
}
}
输出结果
可以发现他们俩互相拿着对方的资源,程序一直在运行。这就是死锁。
解决方法
修改刚才代码里边的eachOther()方法
把里边的锁拿出来,不要互相拿着对方的锁就行了
private void eachOther() throws InterruptedException {
if (choice==0){
synchronized (user1){
System.out.println(this.name+"获得用户1的锁");
Thread.sleep(1000);
}
synchronized (user2){//一秒钟后去拿用户2的锁
System.out.println(this.name+"获得用户2的锁");
}
}else{
synchronized (user2){
System.out.println(this.name+"获得用户2的锁");
Thread.sleep(2000);
}
synchronized (user1){//一秒钟后去拿用户2的锁
System.out.println(this.name+"获得用户1的锁");
}
}
}
Lock锁
概念
- 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当。
- java.utile.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。suo提供了对共享资源的独占空间,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,他拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。
代码
//Lock锁
class MainLock{
public static void main(String[] args) {
Lock lock1 = new Lock();
new Thread(lock1).start();
new Thread(lock1).start();
new Thread(lock1).start();
}
}
//定义一个Lock锁
class Lock implements Runnable{
int num =10;
private final ReentrantLock reentrantLock=new ReentrantLock();
@Override
public void run() {
while (true) {
try {
reentrantLock.lock();//加锁
if (num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
} else {
break;
}
}finally {
reentrantLock.unlock();//解锁
}
}
}
}
synchronized 和 Lock对比
- Lock是显示锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放。
- Lock只有代码块锁,synchronzied有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序:
- Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体外)
生产者和消费者(线程通信)
应用场景:生产者和消费者的问题
- 假设仓库中只能存放一条产品,生产者将生产出的产品放入仓库,消费者将仓库中产品取走消费。
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,知道仓库中的产品被消费者取走为止。
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费等待,知道仓库中再次放入产品为止。
Java提供了几个方法解决线程之间的通信问题
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 指定一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度 |
解决方式1 (管程法)
并发写作模型“生产者/消费者模式”——>管程法
- 生产者:负责生产数据
- 消费者:负责处理数据
- 缓冲区:消费者不能直接使用生产者的数据,他们之间有一个缓冲区。
生产者将生产好的数据放入缓存区,消费者从缓冲区中拿出数据。
例子
/
//管城法
class Test{
public static void main(String[] args) {
SynContainers container =new SynContainers();
new Productors(container).start();
new Consumers(container).start();
}
}
//生产者
class Productors extends Thread{
SynContainers containers;
public Productors(SynContainers containers){
this.containers=containers;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
containers.push(new Chicken(i));
System.out.println("生产了"+i+"只鸡");
}
}
}
//消费者
class Consumers extends Thread{
SynContainers containers;
public Consumers(SynContainers containers){
this.containers=containers;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了"+containers.pop().id+"只鸡");
}
}
}
//产品
class Chicken{
int id;
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SynContainers{
//容器大小
Chicken[] chickens=new Chicken[10];
//容器计算器
int count = 0;
//生产者放入产品
public synchronized void push(Chicken chicken){
//如果容器满了 就需要等待消费
if (count ==chickens.length){
try {
this.wait(); //线程阻塞 生产者通知消费解除
} catch (InterruptedException e) {
}
}
//如果没有瞒 继续生产
chickens[count]=chicken;
count++;
//通知消费者消费
this.notifyAll();
}
//消费
public synchronized Chicken pop(){
//判断是否能消费
if (count==0){
// 等待生产 消费者等待
try {
this.wait(); //线程阻塞 生产者通知消费解除
} catch (InterruptedException e) {
}
}
//如果可以消费
count--;
Chicken chicken= chickens[count];
//吃完了 通知生产者生产
this.notifyAll(); //存在空间了,可以唤醒对方生产了
return chicken;
}
}
解决方式2 (信号灯法)
并发写作模型“生产者/消费者模型” ——>信号灯法。
信号灯法就是有一个boolean 标识
为true的时候进行生产
为false的时候消费
例子
生产者消费者的实现方法:信号灯法 有一个表示 为真生产,为假消费
//测试类
class TestXinHaoDeng{
public static void main(String[] args) {
Product p=new Product();
new Watcher(p).start();
new Player(p).start();
}
}
//生产者
class Player extends Thread{
Product p;
public Player(Product p){
this.p=p;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0){
try {
this.p.push("生产者开始了1111111111111111111");
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
try {
this.p.push("生产者开始了2222222222222222222");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//消费者
class Watcher extends Thread{
Product p;
public Watcher(Product p){
this.p=p;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
p.pop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//产品
class Product{
String program;//产品名称
boolean flag=true;//标识
//生产
public synchronized void push(String program) throws InterruptedException {
if (!flag){
this.wait();
}
System.out.println("生产者 开始生产"+program);
// 唤醒
this.notifyAll();
this.program=program;
this.flag= !this.flag;
}
public synchronized void pop() throws InterruptedException {
if (flag){
this.wait();
}
System.out.println("消费者 开始消费了"+program);
// 唤醒
this.notifyAll();
this.program=program;
this.flag= !this.flag;
}
}
线程池
什么是线程池
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
///线程池
class Pool{
public static void main(String[] args) {
ExecutorService service=Executors.newFixedThreadPool(10);
service.execute(new MyPool());
service.execute(new MyPool());
service.execute(new MyPool());
service.execute(new MyPool());
service.execute(new MyPool());
}
}
class MyPool implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"");
}
}