java:多线程

线程涉及的方法总结方式解释
void start()导致此线程开始执行,告诉Java虚拟机调用此线程的run方法(自定义线程里重写的run方法)
void run()封装线程执行的代码,直接调用相当于普通方法的调用
String getName()返回此线程的名称
void setName(String name)将此线程的名称更改为等于参数name
static Thread currentThread()返回对当前正在执行的践程对象的引用
public final int getPriority()返回此线程的优先级
public final void setPriority(int newPriority)更改此线程的优先级
static void sleep(long millis)使当前正在执行的线程停留 (暂停执行)指定的毫秒数
void join()join()方法的实现原理是不停检查join线程是否存活,如果join线程存活则让非join线程永远等待。直到所有的join线程完成后,线程的其他方法会被调用。(所有join线程同时进行)
void setDaemon(boolean on)将此线程标记为守护线程 ,当运行的线程都是守护线程时,Java虚拟机将退出。也就是说主线程中处理守护线程都执行完,主线程会退出而不会等待守护线程执行完毕

进程

进程:是正在运行的程序
1.是系统进行资源分配和调用的独立单位
2.每一个进程都有它自己的内存空间和系统资源

线程

线程:是进程中的单个顺序控制流,是一条执行路径
1.单线程:一个进程如果只有一条执行路径,则称为单线程程序
2.多线程:一个进程如果有多条执行路径,则称为多线程程序
(多线程本质上是对程序的消耗,但是操作起来会减少时间的消耗)

多线程的实现方式:

使用多线程我们需要先了解一个类:Class Thread
public class Thread extends Object implements Runnable

这是一个具体类,Thread就是线程类,继承Object,实现了Runnable接口。
Java的虚拟机运行应用程序同时执行多个线程。

创建一个新的执行线程的两种方法:

方式1:继承Thread类

●定义一个类MyThread继承Thread类
●在MyThread类中重写run方法
(为什么要重写run方法?因为run是用来封装被线程执行的代码,自定义线程里不止由被线程执行的代码)
●创建MyThread类的对象
●启动线程

一个进程由两个线程组成:多线程在执行过程中并不会等第一个调用结束再开始第二个,而是两个方法一起实现。
注意,多线程的调用用到的是statr函数而不是run函数。
start:导致此线程开始执行,告诉Java虚拟机调用此线程的run方法(自定义线程里重写的run方法)。
run:封装线程执行的代码,直接调用相当于普通方法的调用。

public class SendMailRunnable extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10+(int)(11*Math.random()); i++) {
            System.out.println("I am sending emails");
            try {
                Thread.sleep(1000+(int)(1001*Math.random()));
                //Thread.sleep()休眠,就是暂停一段时间继续往下执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ReceiveMailRunnable extends Thread{
    @Override
    public void run() {
        for(int i=0;i< 10+(int)(11*Math.random());i++){
            System.out.println("I am receiving emails");
            try {
                Thread.sleep(1000+(int)(1000*Math.random()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

测试类:

public class TestThread {
    public static void main(String[] args) {
        Thread receiveThread1=new ReceiveMailRunnable();
        Thread sendThread1=new SendMailRunnable();
        
        receiveThread1.start();
        sendThread1.start();
    }
}

测试结果:

I am receiving emails
I am sending emails
I am sending emails
I am receiving emails
I am receiving emails
I am sending emails
I am receiving emails
I am sending emails
I am receiving emails
I am sending emails
I am receiving emails
I am sending emails
I am sending emails
I am receiving emails
I am sending emails
I am receiving emails
I am receiving emails
I am sending emails
I am receiving emails
I am sending emails
I am receiving emails
I am sending emails
I am receiving emails
I am sending emails
I am receiving emails
I am sending emails
I am receiving emails
I am sending emails

方式2:实现Runnable接口

1:定义一个类MyRunnable实现Runnable接口
2:在MyRunnable类中重写run()方法
3:创建MyRunnable类的对象
4:创建Thread类的对象,把MyRunnable对象作为构造方法的参数
5:启动线程

 Thread receiveThread1=new Thread(new ReceiveMailRunnable());
        receiveThread1.setName("receiveThread1");

跟上一种构造方法的一个差别就是:定义MyRunnable类并没有继承Thread,不能直接调用getName,所以需要Thread.currentThread().getName()。

//第一种
  System.out.println("当前线程"+getName()+"正要退出");
//第二种
  System.out.println("当前线程"+Thread.currentThread().getName()+"正要退出");

构造的时候就传入名称参数:

//        Thread receiveThread1=new Thread(new ReceiveMailRunnable());
//        receiveThread1.setName("receiveThread1");
        Thread receiveThread1=new Thread(new          
        ReceiveMailRunnable(),"receiveThread1");

对比

相比继承Thread类,实现Runnable接口的好处
●避免了 Java单继承的局限性,实现Runnable的类同时还可以继承其他类
●适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想,

设置和获取线程名称:

Thread类中设置和获取线程名称的方法
void setName(String name):将此线程的名称更改为等于参数name
String getName():返回此线程的名称
static Thread currentThread:返回对当前正在执行的践程对象的引用

getName:

public class ReceiveMailRunnable implements Runnable{
    @Override
    public void run() {
        /*...*/
        System.out.println("当前线程"+getName()+"正要退出");
    }
}
当前线程Thread-3正要退出//Thread-num

我们来看一下getName这个函数为什么是这样实现的:
Thread的无参构造方法:

  private volatile String name;
  //name是成员变量
  //1
  public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
        //调用了本类的init方法
    }
   
   //2  
   /* For autonumbering anonymous threads. */
   private static int threadInitNumber;
   //这是一个静态参量,初始为0,每多增加一个线程,threadInitNumber++
   private static synchronized int nextThreadNum() {
       return threadInitNumber++;//0,1,2...
   }


   //3
   private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
    
   private void init(ThreadGroup g, Runnable target, String name,
                    long stackSize, AccessControlContext acc,
     /*...省略...*/
      this.name = name;
     /*...省略...*/
    }


    //4
	public final String getName() {
	        return name;
	}

setName:

 Thread receiveThread1=new ReceiveMailRunnable();
 receiveThread1.setName("receiveThread1");

之前我们使用的是无参构造方法+setName进行name的赋值,接下来我们尝试使用带参构造方法对name进行赋值。
因为ReceiveMailRunnable是自定义函数,我们需要先创建两种构造方法,然后调用继承的父类的带参构造方法。

public class ReceiveMailRunnable extends Thread implements Runnable{
    public ReceiveMailRunnable(){
        super();//这里的super好像也可以不写
    }
    public ReceiveMailRunnable(String name){
        super(name);
    }
    @Override
    public void run() {
       /*...*/
    }
}

Thread.currentThread().getName():

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        //main
    }

线程调度

线程有两种调度模型
●分时调度模型: 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
●抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择-一个, 优先级高的线程获取的CPU时间片相对多一些

Java使用的是抢占式调度模型
假如计算机只有一个CPU, 那么CPU在某一个时刻只能执行条指令, 线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的。

Thread类中设置和获取线程优先级的方法
public final int getPriority():返回此线程的优先级
public final void setPriority(int newPriority):更改此线程的优先级

  receiveThread1.setPriority(5);
  receiveThread1.setPriority(10);
  receiveThread1.setPriority(1);
  receiveThread1.setPriority(1000000);   //报错IllegalArgumentException

在设置优先级的时候要注意优先级的范围,超过优先级范围会报错IllegalArgumentException
如何确定优先级范围:

  System.out.println(receiveThread1.MAX_PRIORITY);//max:10
  System.out.println(receiveThread1.MIN_PRIORITY);//min:1
  System.out.println(receiveThread1.NORM_PRIORITY);//default:5

总结:
线程默认优先级是5;线程优先级的范围是: 1-10
线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果

线程控制

(给个使用场景就行)
static void sleep(long millis)使当前正在执行的线程停留 (暂停执行)指定的毫秒数(记得捕获异常)
void join()等待这个线程死亡。join()方法的实现原理是不停检查join线程是否存活,如果join线程存活则让非join线程永远等待。直到所有的join线程完成后,线程的其他方法会被调用。(所有join线程同时进行)
[给出的样例,康熙,八阿哥,四阿哥争皇位,康熙死后两个阿哥才开始争皇位,所有康熙.join()]
void setDaemon(boolean on)将此线程标记为守护线程 ,当运行的线程都是守护线程时,Java虚拟机将退出。也就是说主线程中除了守护线程都执行完,主线程会退出而不会等待守护线程执行完毕。守护线程不影响主线程的结束。
主线程结束完毕守护线程很快结束,但不一定立刻结束。
[给出的样例,张飞,关羽保刘备,刘备死了张飞关羽不用继续保护了,张飞.setDaemon(true),关羽.setDaemon(true)]

java里面如果一个主线程中启动了多个子线程,那么我们如何保证所有子线程一定在主线程结束之前全部执行完成呢,答案就是使用子线程的join()方法,join方法可以让主线程等待子线程执行完成再结束。
java程序中在主线程中如果有多个子线程,为了保证在主线程结束之前所有子线程都执行完成,那么我们需要调用每一个子线程的join()方法,这样才可以保证在主线程执行完成之前,所有子线程都执行完成。
详细参考连接

public static void main(String[] args) {
       /*...构造...*/
        receiveThread1.start();
        receiveThread2.start();
        receiveThread3.start();
        sendThread1.start();
        sendThread2.start();
        sendThread3.start();
        try {
        // 调用多个子线程的join()方法,让主线程等待它们执行完再让主线程执行
            receiveThread1.join();
            receiveThread2.join();
            receiveThread3.join();
            sendThread1.join();
            sendThread2.join();
            sendThread3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("foxmail任务结束");
    }

线程的生命周期

在这里插入图片描述

线程的同步和互斥

为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)
●是否是多线程环境
●是否有共享数据
●是否有多条语句操作共享数据

如何解决线程安全问题呢?
●基本思想:让程序没有安全问题的环境 ——> 破坏这三个条件的任意一个

怎么看都只能解决最后一个,所以怎么实现呢?
●把多条语句操作共享数据的代码给锁起来, 让任意时刻只能有一个线程执行即可 ——> 同步代码块

案例:使用并发程序处理计算从1到1亿整型数相加。
多线程,有共享数据sum,有两条线程同时对sum进行操作。

对这个案例深入探究冲突发生的原因还是得从计算机组成原理出发:
//感觉和我学的汇编不太一样,以后再改吧,这一小段不适合作为参考
CPU的加法操作:
sum=sum+i

mov  AX, sum    
mov  BX, i
add  BX //BX=AX+BX
mov  CX,BX

为啥不可以BX存放数据,这样不就直接BX+=i,不理解???

按上面这种想法的话,当A线程(sum=5000,i=2333),B线程(sum=5000,i=12000)
两个求出BX同时,一个7888,一个17555,然后放入CX,到底放入的是那个谁也说不准。如果最终CX=17555,那么2333就废掉了,因为他已经参与计算了,不可能再参与一次。
这么看来只能一个算出来再下一步计算???那要多线程干啥,离谱???

测试类:

public static void main(String[] args) {
        long n = 100000000;
        int cnt = 4;
        long div = n / cnt;
        List<Thread> shareList = new ArrayList<>();
        //共享式
        long end;
        for (long beg = 1; beg <= n; beg += div) {
            end = beg + div - 1;
            if (end > n) end = n;
            shareList.add(new Thread(new ShareSumWorker(beg, end)));
        }


        long timeBeg=System.currentTimeMillis();
        for (Thread point : shareList) {
            point.start();
        }
        try {
            for (Thread point : shareList) {
                point.join();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //for (Thread point:list){
        //     point.start();
        //     point.join();这样是单线程
        //}
        long timeEnd=System.currentTimeMillis();
        System.out.println("ShareSum:"+ShareSumWorker.getSum()+" Time:"+(timeEnd-timeBeg)+"ms");
}

(有问题的版本)

public class SumWorker implements Runnable{
    private static long sum=0;
    private long beg,end;

    public SumWorker(long beg,long end){
        this.beg=beg;
        this.end=end;
    }

    @Override
    public void run() {
        for (long i=beg;i<=end;i++) {
            sum += i;
        }
    }

    public static long getSum(){
        return sum;
    }
}

同一份代码,多次运行测试结果不一致,为什么?

1.3745948244024503
2.1269751165445239
3.1255380166048622

为了不冲突,具体作法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。
同步代码块的作用相当于一个锁,就是当一个线程抢到了cpu的时间片就进行run进行计算,进入的同时将这一段代码锁上,在计算过程中,该线程可能会休息一会(Thread.sleep)这时候其他线程很有可能就抢到了cpu的时间片准备进入run进行计算,但是此时的代码还被那个未完成计算的线程占用,代码还被锁上,所以新的线程即使抢到cpu的时间片也不能进入代码执行代码。当把代码块锁上的线程计算结束,出代码块的同时锁也就释放了,这时候新抢到cpu时间片的线程就可以进入了。

同步代码要用到临界区,同一个代码要用到同一个临界区,锁只有一把。
(正确版本)

public class SumWorker implements Runnable{
    private static Object obj=new Object();
    private static long sum=0;
    private long beg,end;
    
    @Override
    public void run() {
        for (long i = beg; i <= end; i++) {
            synchronized (obj) {//用一个静态参量表示是只有一把锁
                sum += i;
            }
        }
    }
}

注意!!synchronized里面的代码要尽可能短。
如果是这样,每一条线程对应的this都不一样,不是同一个,锁没用。

synchronized (this) {
 	sum += i;
}

Wait(),Notify()

wait()

public final void wait​() throws InterruptedException
一直处于等待状态,直到当前线程等到另一个线程调用该对象的notify()方法或notifyAll()方法。

private static Object obj=new Object();
obj.wait();

使当前线程进入等待状态,直到另一个线程为此对象调用notify()方法或notifyAll()方法。
换句话说,这个方法的行为就像它只是执行wait(0)一样(如果没有当前对象notify()唤醒就会一直等待)

  • 1.当前线程必须拥有此对象的monitor监视器(锁)。
  • 2.当前线程调用wait()方法,当前线程就会释放此锁的所有权,并进入等待,这时候其他线程就可以争夺该代码段的使用(时间片)
  • 3.直到另一个线程通过调用notify方法或notifyAll方法通知在该对象的监视器(锁)上等待的线程唤醒。这时候这条线程就可以重新参与争夺代码段的使用(时间片)
    (被唤醒之后还需等待直到抢到锁才能继续执行)
if (条件) {
  synchronized (obj) {
      try {
          obj.wait();
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
    }
 }

notify()

private static Object obj=new Object();
obj.notify();

public final native void wait(long timeout) throws InterruptedException;

例题:哲学家就餐问题

五个哲学家共用一张圆桌,分别坐在周围的五张椅子上,在桌子上有五只筷子,他们的生活方式是交替地进行思考和进餐。平时,一个哲学家进行思考,饥饿时便试图取用其左右最靠近他的筷子,只有在他拿到两只筷子时才能进餐。进餐毕,放下筷子继续思考。

哲学家只有拿到两根筷子才可以进餐,如果左右筷子有一根缺的哲学家就得等待。但是如果五位哲学家,每人都拿起了右手边的筷子就会出现死锁,五位哲学家就要一直等待,会出现饿死现象。

给出的解决办法当一位哲学家左右筷子有缺就把两个筷子都放下,并释放锁让其他哲学家尝试进餐,直至两边筷子都有且该哲学家抢到时间片,哲学家在进餐时其他哲学家进入等待状态,吃完适当锁让其他哲学家可以竞争。

package thinkoreat;

import java.util.Arrays;

public class ThinkOrEat implements Runnable{
    public static boolean[] noChopsticks=new boolean[5];
    private static Object obj=new Object();
    //用来判断是否都有筷子
    private int pos;
    public ThinkOrEat(int pos){
        this.pos=pos;
    }

    //哲学家要进餐时,要么同时拿起两支筷子,要么一支筷子都不拿.
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            //思考
            think(i);
            //恰饭
            takeChopsticks();
            eat(i);
            putDownChopsticks(i);
        }
        System.out.println(Thread.currentThread().getName()+"have ended");
    }

    public void think(int i){
        System.out.println("Activity" + i + ": The philosopher-" + Thread.currentThread().getName() + " is thinking");
        //吃饭
        try {
//                Thread.sleep(1000 + (int) (500 * Math.random()));
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //吃饭
    public void eat(int i){
        System.out.println("Activity"+i+": The philosopher-" + Thread.currentThread().getName() + " is eating");
        try {
//                Thread.sleep(1000 + (int) (500 * Math.random()));
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //拿起筷子
    public void takeChopsticks()
    {
        //拿筷子时只要有一个筷子已经被占用,就要放弃拿筷子,并释放锁进入等待状态
        //得到通知后,再进入就绪状态,重新竞争锁
        //调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁,或者叫管程)
        if (noChopsticks[pos]||noChopsticks[((pos - 1) + 5) % 5]) {
            synchronized (obj) {
                try {
                    obj.wait();
                    //我只有一根筷子,没法吃饭,等着,该条线程先别动
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //
            noChopsticks[pos]=true;
            noChopsticks[((pos - 1) + 5)%5]=true;
        }
    }
    //放下筷子时,也要申请放锁,然后通知所有等待的线程
    //notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
    public void putDownChopsticks(int i) {
        synchronized(obj) {
            noChopsticks[pos]=false;
            noChopsticks[((pos - 1) + 5)%5]=false;
            System.out.println("Activity"+i+": The philosopher-" + Thread.currentThread().getName() + " has finished eaten");
            obj.notifyAll();
            //释放锁后其他线程就可以继续竞争了,其他线程会再次进行if判断
        }
    }
}

上课老师的代码和我写的不太一样,记录一下,但是如果缺少synchronized就错了,会发生冲突。

//用synchronized修饰和给代码段加synchronized效果时一样的,都表示在进行该块代码的时候其他形成不能并行
    public synchronized void takeChopsticks(int i){
        if(!noChopsticks[pos]&&!noChopsticks[((pos - 1) + 5)%5]){
            noChopsticks[pos]=true;
            noChopsticks[((pos-1)+5)%5]=true;
        }
        else {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值