多线程

线程与进程

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

线程调度

  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()。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352