多线程

线程与进程

进程是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间(不共享的堆栈)。
线程是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程最少有一个线程。线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。

线程调度

  1. 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  2. 抢占式调度: 优先让优先级高的线程使用 CPU(优先级高抢到时间片的概率越高),如果线程的优先级相同,那么会随机选择一个(线程随机性)。Java使用的是抢占式调度。

同步与异步

同步: 排队执行 , 效率低但是安全。
异步: 同时执行 , 效率高但是数据不安全。

创建多线程

方法1: 继承Thread

  1. 编写类继承Thread类,重写run(),将分支线程要执行的任务代码声明在run()中
  2. 创建Thread类的子类对象
  3. 通过此对象调用start(),启动新线程
public class Demo {
  public static void main(String[] args){//main方法是运行在主线程里
    MyThread m = new MyThread();
    m.start();//开启新任务
    for(int i=0; i<10; i++)
      System.out.println("a" + i);
  }
} 
public class MyThread extends Thread{//分支线程
  public void run(){
    for(int i=0; i<10; i++)
    System.out.println("b" + i);
  }
}

通过匿名内部类继承Thread实现多线程

public static void main(String[] args){
  new Thread(){ //匿名内部类
    public void run(){
      for(int i=0; i<10; i++)
      System.out.println("b" + i);
    }
  }.start();
for(int i=0; i<10; i++)
    System.out.println("a" + i);
}

方法2: 实现Runnable

  1. 编写类实现Runnable接口,重写run()
  2. 创建任务对象
  3. 创建线程,并传入任务对象
  4. 执行这个线程
public class Main {
  public static void main(String[] args){
    MyRunnable r = new MyRunnable();
    Thread t = new Thread(r);
    t.start();
    //new Thread(new MyRunnable()).start();
    ...
    }
}  
public class MyRunnable implements Runnable{//给线程执行的任务
   public void run(){
     ...
  }
}  

方法3: 带返回值的线程Callable

  1. 编写类实现Callable接口 , 实现call方法
  2. 创建Callable类对象
  3. 创建FutureTask对象,并传入Callable类对象
  4. 通过Thread,启动线程
public class Demo{
  public static void main(String[] args){
    Callable<Integer> c = new MyCallable();
    FutureTask<Integer> task = new FutureTask<>(c);//创建任务对象
    new Thread(task).start();
    Integer j = task.get();
    ...
  }
  static class MyCallable implements Callable<Integer>{
    public Integer call() throws Exception{
      ...
      return 100;
    }
  }
}

Callable获取返回值

Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行(先执行子线程的内容再接着执行主线程的内容),如果不调用不会阻塞。

FutureTask 类的普通方法

Object get():等待计算完成,然后检索其结果。
boolean isDone():判断子线程是否执行完毕,返回true如果任务已完成。
boolean cancel(boolean mayInterruptIfRunning):尝试取消执行此任务。
参数: mayInterruptIfRunning - true如果执行该任务的线程应该被中断; 否则,正在进行的任务被允许完成
返回: false如果任务无法取消,通常是因为它已经正常完成; true任务成功取消

Thread类中的常用方法

构造方法

Thread():分配一个新的 Thread对象。
Thread(Runnable target)
Thread(Runnable target, String name):给线程起名字

普通方法

boolean isAlive():测试这个线程是否活着(执行完run方法之后线程就死亡了)
void start():启动当前线程(即调用start方法的线程),并调用当前线程的run方法
long getId():返回此线程的标识符
String getName():返回此线程的名称。
int getPriority():返回此线程的优先级。
void run():将创建的线程需要执行的操作写在run方法中
void setDaemon(boolean on):将此线程标记为daemon线程或用户线程。
void setName(String name):将此线程的名称更改为等于参数 name 。
void setPriority(int newPriority):更改此线程的优先级。
static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),用Thread.sleep(1000)调用
static Thread currentThread():返回当前线程对象,静态方法用Thread.currentThread()调用
eg. Thread.currentThread().getName()//返回当前线程对象的名称
void interrupt():中断这个线程。

线程不安全问题

java允许多线程并发控制,当多线程同时操作一个可共享的资源变量时,将会导致数据不准确,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程调用,从而保证了该变量的唯一性和准确性

隐式锁 synchronized

解决方案1:同步代码块

格式:synchronized(锁对象){}
java中任何对象都可以作为锁对象/打上锁标记
public class Main {
    public static void main(String[] args) {
        Runnable run = new Ticket();//创建线程任务
        //两个线程执行同一个任务对象run,使用同一个锁对象o
        new Thread(run).start();
        new Thread(run).start();
    }
    static class Ticket implements Runnable {
        private Object o = new Object();//锁对象
        public void run() {
            while (true) {
                //把o放在这里,两个线程就有2个锁对象,两个线程就不会排队
                //private Object o = new Object();
                synchronized (o) {
                    //被锁住/排队的内容是if语句
                    ...
                }
            }
        }
    }
}

解决方案2:同步方法

同步非静态方法

对方法加synchronized修饰符,相等于整个方法都用this实例加锁:synchronized(this){},实现排队执行此方法。
java的成员同步方法,使用的锁是调用该方法的当前对象,即this。
下面两种写法是等价的:

public class Main {
  public static void main(String[] args){
    Runnable run = new Ticket();//创建任务对象
    new Thread(run).start();
    new Thread(run).start();
  }
  static class Ticket implements Runnable{
    private int count = 10;
    public void run(){
      while(true){
        sale();
      }
  }
      (写法1)public void sale()() {
        synchronized(this) {//this是调用此方法的线程对象
          ...
      } 
      (写法2)public synchronized void sale() { // 当前线程对象调用此方法时,其他线程对象不可调用
       ...
    }
  }
} 

同步静态方法

对一个静态方法添加synchronized修饰符,相当于锁住的是该类的Class实例:synchronized(类名.class){}
java的静态同步方法,使用的锁是该方法所在类的类对象,即类名.class。

public synchronized static void sale() {
    ...
}
public class Ticket implements Runnable{
    public static void sale () {
        synchronized(Ticket.class){
            ...
        }
    }
}

显式锁 Lock

解决方案3: 创建锁对象,手动上锁解锁

Lock的子类是ReentrantLock
在任务类中创建ReentrantLock对象属性

public class Main {
  public static void main(String[] args){
    Runnable run = new Ticket();
    new Thread(run).start();
    new Thread(run).start();
}
  public class Ticket implements Runnable{
    //创建显示锁
    private Lock l = new ReentrantLock();
    public void run(){
    while(true){
      l.lock(); //上锁
      ...
      l.unlock();//解锁
    }
  }
}

等待唤醒机制

wait(): 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。当前线程必须拥有此对象的监视器(锁),否则抛出java.lang.IllegalMonitorStateException
notify(): 会唤醒线程池中任意一个等待的线程。
notifyAll(): 会唤醒线程池中所有的等待的线程。

这些方法属于Object类,必须使用在同步中,因为必须要标识wait、notify等方法所属的锁。同一个锁上的notify,只能唤醒该锁上wait的线程。默认是this.wait();this.notify();this.notifyAll()。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。