java 多线程基础

十、多线程

1、基本概念

一个exe在内存中有一个单独的地址,程序一般对应一个进程
一个进程中如需同时运行多个子程序时,需要开启多个线程
当一个Java程序启动时,JVM会自动创建主线程,并在该线程中调用程序的main()方法
JVM还创建了其他线程,例如,与垃圾收集、对象终止和其它JVM内务处理任务相关的线程。
合理的线程使用可以帮助我们显著的提供程序运行效率

2、线程的实现

Java语言中实现线程的方式有两种,但都是通过start()方法启动线程实例

  • 第一种,继承线程类:Thread
继承Thread类:
 class Test extends Thread{
    public void run(){
         while(true){
	   System.out.println("o");
	   try {
	         sleep(1000);
	   } catch (InterruptedException e) {
	         e.printStackTrace();
	   }		
         }
    }
}
实例化:
Test td = new Test();
启动线程
td.start();

  • 第二种:实现Runnable接口
实现runable接口:
 class Test implements Runnable{
         public void run(){
             while(true){
	       System.out.println("o");
	       try {
	             Thread.sleep(1000);
	       } catch (InterruptedException e) {
	             e.printStackTrace();
	       }		
             }
        }
    }
    实例化并建立线程对象:
    Thread td = new Thread(new Test());
    运行线程:
    td.start();
Daemon线程

先来看个多线程的例子


    public class ParentTest  
    {  
      
        public static void main(String[] args)  
        {  
            System.out.println("parent thread begin ");  
              
            ChildThread t1 = new ChildThread("thread1");  
            ChildThread t2 = new ChildThread("thread2");  
            //t1.setDaemon(true);  
            //t2.setDaemon(true);  
              
            t1.start();  
            t2.start();  
      
            System.out.println("parent thread over ");  
        }  
    }  
    class ChildThread extends Thread  
    {  
        private String name = null;  
        public ChildThread(String name)  
        {  
            this.name = name;  
        }  
        @Override  
        public void run()  
        {  
            System.out.println(this.name + "--child thead begin");  
            try  
            {  
                Thread.sleep(500);  
            }  
            catch (InterruptedException e)  
            {  
                System.out.println(e);  
            }  
            System.out.println(this.name + "--child thead over");  
        }  
    }  
       
     执行结果如下:  
    parent thread begin  
    parent thread over  
    thread1--child thead begin  
    thread2--child thead begin  


程序在主程序运行结束后,子程序依然进行;
若在主线程中创建了子线程,如果希望在主线程结束时,子线程也结束,这时候就需要Daemon线程

一个Daemon线程是一个在后台执行服务的子线程。 如果所有的非Daemon的线程都结束了,则Daemon 线程就会自动终止。
例如:main方法是一个非Daemon线程,如果希望main方法结束后子线程跟着终止,要将它设为Daemon线程。
Daemon线程设置方法: Thread.setDaemon(true);

  • 第三种:实现Callable接口
    比较推荐实现Runnable接口的方式,原因如下:
(1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数
据有效的分离,较好地体现了面向对象的设计思想。 (可联想到模拟火车站卖票的例子) 

(2)可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经
继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread
类的方式,那么,这个类就只能采用实现Runnable接口的方式了。 (单继承多实现) 

(3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代
码来自同一个类的实例时,即称它们共享相同的代码。多个线程操作相同的数据,与它们的代码
无关。当共享访问相同的对象时,即它们共享相同的数据。当线程被构造时,需要的代码和数据
通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。


4、线程的生命周期
  • 生命的几个基本状态 在这里插入图片描述

  • 运行中的方式

 1、执行了start()之后,线程进入了Runnable状态,此时线程尚未真正开始执行
 2、必须等待排班器(Scheduler)的排班,决定运行的先后顺序

在这里插入图片描述
有几种状况会让线程进入Blocked状态

等待输入输出;
调用sleep()方法;
尝试取得对象锁定;
调用wait()方法;

Blocked线程如何恢复到Runnable状态?

输入输出完成;
调用interrupt();
取得对象锁定;
调用notify()或notifyAll();
5、线程的休眠与唤醒
Thread thread = new Thread(new Runnable() {
        public void run() {
                try {
                     Thread.sleep(99999);
                }catch(InterruptedException e) {
                     System.out.println("I'm interrupted!!");
                }
                System.out.println("Hello Thread");
        }
});
thread.start();
thread.interrupt(); 
6、线程的加入
  • 当线程使用join()加入至另一个线程时,另一个线程会等待这个被加入的线程工作完毕,然后再继续它的动作。
  • join()的意思表示将线程加入成为另一个线程的流程之一
Thread threadB = new Thread(new Runnable() {
        public void run() {}
});
threadB.start();
try {
       threadB.join(); //子线程threadB加入主线程main中
}catch(InterruptedException e) {
       e.printStackTrace();
}
7、线程的停止
  • 不建议使用stop()来停止一个线程的运行,而是采用向run方法传递一个Boolean型的信号量,以控制其结束与开始
public class SomeThread implements Runnable {
     private boolean flag = true;
     public void stopThread() {
         this.flag = false;
     }
     public void run() {
         while(isContinue) {
             // ... some statements
         }
     }
}

8、 线程的同步化 (关键字synchronized)
同步化的作用
  • 一个进程中的多个线程共享相同的内存地址空间,这就意味着它们可以访问相同的变量和对象,而且它们从同一堆中分配对象。
  • 如果没有同步,数据很容易就处于不一致状态。例如,如果一个线程正在更新两个相关值,而另一个线程正在读取这两个值,有可能在第一个线程只写了一个值,还没有写另一个值的时候,调度第二个线程运行,这样它就会看到一个旧值和一个新值。
  • 如果一个对象所持有的数据可以被多线程同时共享存取时,必须考虑到「数据同步」的问题。
对象锁(lock)

在这里插入图片描述

实现方式
  • 使用“synchronized”关键词为方法加同步锁
public class ThreadTest implements Runnable { 
     public synchronized void run() {
          for (int i = 0; i < 10; i++) {
                System.out.print(" " + i);
          }
     }
     public static void main(String[] args) {
          Runnable r = new ThreadTest();
          Thread t1 = new Thread(r);
          Thread t2 = new Thread(r);
          t1.start();
          t2.start();
      }
} 

  • 使用“synchronized”关键词为语句块加同步锁

public class ThreadTest implements Runnable {
      public void run() { 
          synchronized(this){ 
                for (int i = 0; i < 10; i++) {
                      System.out.print(" " + i);
                }
          }
      }
      public static void main(String[] args) {
          Runnable r = new ThreadTest();
          new Thread(r).start();
          new Thread(r).start();
      }
}

9、线程的等待与恢复
  • wait()、notify()与notifyAll()是由Object类别所提供的声明为“final”的方法。
  • 在同步化的方法或区块中呼叫wait()方法,当前的线程会被放入对象的等待池中,使线程暂停。
  • 当notify()方法被调用,JVM会通知等待池中的线程加入,回到锁定池的Blocked状态。
  • 被通知的线程是随机的,被通知的线程会与其它线程共同竞争对象的锁定。
  • 如果您呼叫notifyAll(),则所有在等待池中的线程都会被通知回到锁定池的Blocked状态。
    例子:
package com.phy.thread;


import java.util.Scanner;

/**
 * @author :xp
 * @date :Created in 2018/10/26 11:00
 * @description:
 */
class MT implements Runnable{
    public boolean flog = false;
    public  void run() {
        int i = 0 ;
        while(true){
            System.out.println("Hello World"+i);
            i++;
            if(flog){
                synchronized(this){
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class TestDaemon {
    public static void main(String[] args){
        MT mt = new MT();
        Thread t = new Thread(mt);
        t.start();
        Scanner sc = new Scanner(System.in);
        while(true){
            System.out.println("输入0暂停");
            int n = sc.nextInt();
            if(n==0) mt.flog = true;
            System.out.println("输入1继续");
            n = sc.nextInt();
            if(n==1){
                synchronized (mt) {
                    mt.flog = false;
                    mt.notify();
                }
            }
        }
    }

}



10、线程的优先级
  • 线程有其优先权,由1(Thread.MIN_PRIORITY)到10(Thread.MAX_PRIORITY)
  • 优先权越高,排班器越优先排入执行,如果优先权相同,则轮流执行(Round-robin方式)
  • 可以通过线程的setPriority()方法设置某个线程的优先级,这样,当一个资源空闲时,同时等待这个资源的两个线程里边优先级高的会先获得对象执行权
11、容器类的线程安全
  • Collections提供了一系列synchronized方法来传回一个同步化的容器对象,从而保证了容器类的线程安全。
  • 使用Iterator遍访对象时,必须实现同步化。
synchronized(list) {
    Iterator i = list.iterator();     while (i.hasNext()) {
        System.out.println(i.next());
    }
}
线程案例:写出输出顺序为男,女,男,女的线程
  • 方式一

//首先要定义一个User对象这里省略

package com.phy.thread;

import com.phy.entity.User;

/**
 * @author xp
 * @date :Created in 2018/10/26 14:52
 * @description:设计一个线程类,第一次输出小明男提问
 * 第二次输出小红女在提问
 */

//定义一个线程类
class RunnableExample implements Runnable {
    private boolean flag = true;
    User userB = new User("小明","男",1);
    User userG = new User("小红","女",1);

    public RunnableExample() {
    }
    @Override
    public synchronized void run() {
        while (true){
            if(flag) {
                System.out.println(userB.getName() + userB.getSex() + "正在问问题");
            }else {
                System.out.println(userG.getName() + userG.getSex() + "正在问问题");
            }
            flag = !flag;
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//测试函数
public class Test2 {
    public static void main(String args[]){
        Thread threadG = new Thread(new RunnableExample());
        threadG.start();
    }
}


  • 方式二

//首先要定义一个User对象这里省略

package com.phy.thread;

import com.phy.entity.User;

/**
 * @author :xp
 * @date :Created in 2019/3/26 14:52
 * @description:设计一个线程类,输出顺序为男,女,男,女
 * 第二次输出小红女在提问
 */


//定义一个线程类
class RunnableExample implements Runnable {
    private User user;
    boolean flag ;
    User userB = new User("小明","男",1);
    User userG = new User("小红","女",1);

    public RunnableExample(User user,boolean flag) {
        this.user = user;
        this.flag =flag;
    }

    @Override
    public  void run() {
        synchronized(user){
            while (true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if( flag ){
                     System.out.println(userB.getName() + userB.getSex() + "正在问问题");
                }else {
                    System.out.println(userG.getName() + userG.getSex() + "正在问问题");
                }
                user.notifyAll();
                try {
                    user.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
//测试函数
public class Test2 {
    public static void main(String args[]){
        User c= new User();
        Thread threadB = new Thread(new RunnableExample(c,true));
        Thread threadG = new Thread(new RunnableExample(c,false));
        threadB.start();
        threadG.start();
    }
}


写两个线程,一个线程打印 1~52,另一个线程打印字母A-Z。打印顺序为12A34B56C……5152Z
  • 这是线程间的通信的一个很有代表性的例子。过程如下:
    1、创建两个线程实现Runnable接口重写run方法,一个用于打印数字,一个用于打印字母。
    2、创建一个测试类,在测试类中创建一个Object类的对象(作为两个线程的共享资源,以便实现线程间的通信),通过各类的构造方法传递过去。
    3、在两个类的run方法中都要用synchronized保证同步,即加锁。
    4、在数字类中用for循环每打印两个数字就唤醒其他线程,释放锁,进入阻塞状态。 在字母类中每打印一个字母就唤醒其他线程,释放锁,进入阻塞状态。
  • 在写这个程序的时候有几点要注意的地方:
    1、两个线程要使用同一个资源才需相互通信,所以在测试类中创建共享资源,并通过构造方法分别传到各线程类中。
    2、两个线程哪个先运行(执行start())哪个就先获得资源并执行
    3、在run方法体内写进程间的通信wait()和notifyall()时,一定要先写notifyall()再写wait()。
    原因:当你先写wait()时,本进程也进入休眠状态,再写notifyall()唤醒所有线程时本线程以及其他线程被一块唤醒,竞争同一个资源,就会造成死锁。 所以一定要先唤醒其他线程,再让本线程阻塞!
/**
 * @author :xp
 * @date :Created in 2018/10/26 16:12
 * @description:实现两个线程之间的通信
 */
public class ThreadExample {
    public static void main(String[] args) {
        Object obj = new Object();
        Number s = new Number(obj);
        Char z = new Char(obj);
        Thread th1 = new Thread(s);
        Thread th2 = new Thread(z);
        th1.start();//数字的线程先运行,数字先执行
        th2.start();
    }

}

//数字类实现runnable接口
class Number implements Runnable{
    private Object obj;

    public Number() {
    }
    public Number(Object obj) {
        this.obj = obj;
    }
    @Override
    public void run() {
        synchronized(obj){//给共享资源上锁
            for(int i = 1;i < 53;i++ ){
                System.out.println(i);
                if(i % 2 == 0){//保证输出两个数字
                    obj.notifyAll();//唤醒其他线程
                    try {
                        obj.wait();//等待并释放锁
                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                }
            }
        }

    }
}
//字母类
class Char implements Runnable{

    private Object obj;

    public Char() {
    }
    public Char(Object obj) {
        this.obj = obj;
    }

    @Override
    public void run() {
        synchronized(obj){
            for(int i = 0;i < 26;i++ ){
                System.out.println((char)(i+'A'));
                obj.notifyAll();//唤醒其他线程
                try {
                    obj.wait();//释放锁等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值