乐趣区

关于java:java面试必问多线程的实现和同步机制一文帮你搞定多线程编程

前言

过程:一个计算机程序的运行实例,蕴含了须要执行的指令;有本人的独立地址空间,蕴含程序内容和数据;不同过程的地址空间是相互隔离的;过程领有各种资源和状态信息,包含关上的文件、子过程和信号处理。
线程:示意程序的执行流程,是 CPU 调度执行的根本单位;线程有本人的程序计数器、寄存器、堆栈和帧。同一过程中的线程共用雷同的地址空间,同时共享进过程锁领有的内存和其余资源。

多线程的实现

继承 Thread 类

  1. 创立一个类,这个类须要继承 Thread 类
  2. 重写 Thread 类的 run 办法(run 办法中是业务代码)
  3. 实例化此线程类
  4. 调用实例化对象的 start 办法启动线程
package com.test;

public class Demo1 {public static void main(String[] args){ThreadDemo threadDemo = new ThreadDemo();
        threadDemo.start();}
}

class ThreadDemo extends Thread{
    @Override
    public void run() {System.out.println("运行了 run 办法");
    }
}

在多线程编程中,代码的执行后果与代码的执行程序或者调用程序是无关的线程是一个子工作,CPU 以不确定的形式或者是以随机的工夫来调用线程中的 run 办法这体现了线程运行的随机性

package com.test;

public class Demo2 {public static void main(String[] args) {Demo2Thread demo2Thread = new Demo2Thread();
        /*
    *demo2Thread.start 办法才是启动线程
    *demo2Thread.run 办法只是由 main 主线程来调用 run 办法
    */
        demo2Thread.start();
        try {for (int i = 0; i < 3; i++) {System.out.println("运行了 main 办法");
                Thread.sleep(100);
            }
        }catch (Exception e){e.printStackTrace();
        }
    }
}

class Demo2Thread extends Thread{
    @Override
    public void run() {
        try {for (int i = 0; i < 3; i++) {System.out.println("运行了 run 办法");
                Thread.sleep(100);
            }
        }catch (Exception e){e.printStackTrace();
        }
    }
}

start 的执行程序和线程的启动程序是不统一的
1,2,3,4,5 的输入程序是随机的

package com.test;

public class Demo3 {public static void main(String[] args) {Demo3Thread demo3Thread1 = new Demo3Thread(1);
        Demo3Thread demo3Thread2 = new Demo3Thread(2);
        Demo3Thread demo3Thread3 = new Demo3Thread(3);
        Demo3Thread demo3Thread4 = new Demo3Thread(4);
        Demo3Thread demo3Thread5 = new Demo3Thread(5);

        demo3Thread1.start();
        demo3Thread2.start();
        demo3Thread3.start();
        demo3Thread4.start();
        demo3Thread5.start();}
}

class Demo3Thread extends Thread{
    private int i;

    public Demo3Thread(int i){this.i = i;}

    @Override
    public void run() {System.out.println("i=" + i);
    }
}

实现 Runnable 接口

1)创立一个类,整个类须要实现 Runnable 接口
2)重写 Runnable 接口的 run 办法
3)实例化创立的这个类
4)实例化一个 Thread 类,把第 3 步实例化创立的对象通过 Thread 类的构造方法传递给 Thread 类
5)调用 Thread 类的 run 办法

package com.test;

public class Demo4 {public static void main(String[] args) {Demo4Thread thread = new Demo4Thread();
        Thread t = new Thread(thread);
        t.start();
        System.out.println("运行了 main 办法");
    }
}

class Demo4Thread implements Runnable{
    @Override
    public void run() {System.out.println("运行了 run 办法");
    }
}

应用继承 Thread 类的形式开发多线程应用程序是有局限的,因为 Java 是单继承,继承了 Thread 类就无奈继承其余类,所以为了扭转这种局限,用实现 Runnable 接口的形式来实现多线程

成员变量与线程平安

自定义线程类中的成员变量对于其余线程能够是共享或者不共享的,这对于多线程的交互很重要

  1. 不共享数据时
package com.test;

public class Demo5 {public static void main(String[] args) {Thread t1 = new Demo5Thread();
        Thread t2 = new Demo5Thread();
        Thread t3 = new Demo5Thread();
        t1.start();
        t2.start();
        t3.start();}
}

class Demo5Thread extends Thread{
    private int i = 5;
    @Override
    public void run() {while(i > 0){
            i--;
            System.out.println(Thread.currentThread().getName() + "i =" + i);
        }
    }
}                    

每个线程都有各自的 i 变量,i 变量的值相互之间不影响

  1. 共享数据时
package com.test;

public class Demo6 {public static void main(String[] args) {Thread t = new Demo6Thread();
            /*
        为什么能将 Thread 类的对象传递给 Thread 类?因为 Thread 类自身就实现了 Runnable 接口
             */
         Thread thread1 = new Thread(t);
        Thread thread2 = new Thread(t);
        Thread thread3 = new Thread(t);
        Thread thread4 = new Thread(t);
        Thread thread5 = new Thread(t);

    thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();}
}

class Demo6Thread extends Thread{
    private int i = 5;
    @Override
    public void run() {
            i--;
            System.out.println(Thread.currentThread().getName() + "i =" + i);        
    }
}            

共享数据时,将数据所在类的对象传递给多个 Thread 类即可
共享数据有概率呈现不同线程产生雷同的 i 的值,这就是非线程平安

线程罕用 API

  1. currentThread 办法
    返回代码被哪个线程调用的详细信息
package com.test;

public class Demo7 {public static void main(String[] args) {
        //main 线程调用 Demo7Thread 的构造方法
        Thread thread = new Demo7Thread();
        //Thread- 0 线程调用 run 办法
        thread.start();
        System.out.println("main 办法" + Thread.currentThread().getName());
    }
}

class Demo7Thread extends Thread{public Demo7Thread(){System.out.println(Thread.currentThread().getName() + "的构造方法");
    }
    @Override
    public void run() {System.out.println(Thread.currentThread().getName() + "的 run 办法");
    }
}

输入后果为:
main 的构造方法
main 办法 main
Thread- 0 的 run 办法

main 办法会被名称为 main 的线程调用,在新建线程类的对象时,线程类的构造方法会被 main 线程调用;
线程类对象的 start 办法会调用 run 办法,此时线程类默认的名称为 Thread-0

  1. isAlive 办法
    判断以后的线程是否处于流动的状态,活动状态就是线程曾经启动并且没有完结运行的状态
package com.test;

public class Demo8 {public static void main(String[] args) {Thread t = new Demo8Thread();
        System.out.println("线程启动前:" + t.isAlive());
        t.start();
        System.out.println("线程启动后:" + t.isAlive());
    }
}

class Demo8Thread extends Thread{
    @Override
    public void run() {System.out.println("run 办法的运行状态" + this.isAlive());
    }
}

输入后果为:
线程启动前:false
线程启动后:true
run 办法的运行状态 true

true 示意线程正处于活动状态,false 则示意线程正处于非活动状态

  1. sleep 办法
    使以后正在执行的线程在指定的毫秒数内暂停执行
package com.test;

public class Demo8 {public static void main(String[] args) {Thread t = new Demo8Thread();
        System.out.println("线程启动前工夫:" + System.currentTimeMillis());
        t.start();
        System.out.println("线程启动后工夫:" + System.currentTimeMillis());
    }
}

class Demo8Thread extends Thread{
    @Override
    public void run() {System.out.println("线程 sleep 前的工夫:" + System.currentTimeMillis());
        try {Thread.sleep(300);
        }catch (Exception e){e.printStackTrace();
        }
        System.out.println("线程 sleep 后的工夫:" + System.currentTimeMillis());
    }
}
  1. getId 办法
    获取以后线程的惟一标识
package com.test;

public class Demo9 {public static void main(String[] args) {Thread t = Thread.currentThread();
        System.out.println(t.getName() + "," + t.getId());
        Thread thread = new Thread();
        System.out.println(thread.getName() + "," + thread.getId());
    }
}
  1. 进行线程
    进行一个线程,即线程在实现工作之前,就完结以后正在执行的操作
    1)应用退出标记,使线程失常进行,即 run 办法运行完后线程终止
package com.test;

public class Demo10 {public static void main(String[] args) {Demo10Thread thread = new Demo10Thread();
        thread.start();
        try {Thread.sleep(2000);
        }catch (Exception e){e.printStackTrace();
        }
        /*
        stopThread 办法,将 flag 变为 false,为什么可能传递到以后线程中?我感觉是因为以后线程是始终在运行的,while()中的条件始终成立
        所以当调用了 stopThread 办法,将 flag 变为 false,while 循环就完结了,run 办法中的代码也完结了
        所以线程进行了
         */
        thread.stopThread();}
}

class Demo10Thread extends Thread{
    private Boolean flag = true;
    @Override
    public void run() {
        try {while (flag){System.out.println("线程正在运行");
                Thread.sleep(1000);
            }
            System.out.println("线程完结运行");
        }catch (Exception e){e.printStackTrace();
        }
    }
    public void stopThread(){flag = false;}
}

2)stop 办法强制完结线程

package com.test;

public class Demo11 {public static void main(String[] args) {Demo11Thread thread = new Demo11Thread();
        thread.start();
        try {Thread.sleep(2000);
        }catch (Exception e){e.printStackTrace();
        }
        //stop 办法中的斜杠示意办法曾经被作废,不倡议应用此办法
        thread.stop();}
}

class Demo11Thread extends Thread{
    private Boolean flag = true;
    @Override
    public void run() {
        try {while (flag){System.out.println("线程正在运行~~~");
                Thread.sleep(1000);
            }
            System.out.println("线程完结运行~~~");
        }catch (Exception e){e.printStackTrace();
        }catch (ThreadDeath e){// 捕捉线程终止的异样
            System.out.println("进入 catch 块");
            e.printStackTrace();}
    }
}

stop 强制进行线程可能使一些清感性的工作得不到实现;还会对锁定的对象进行解锁,使数据得不到同步的解决,导致数据不统一

3)interrupt 办法中断线程

package com.test;
public class Demo12 {public static void main(String[] args) {Demo12Thread thread = new Demo12Thread();
        thread.start();
        thread.interrupt();
        System.out.println("thread 线程是否曾经进行?" + thread.isInterrupted() + "," + thread.getName());
        System.out.println("以后线程是否曾经进行?" + Thread.interrupted() + "," + Thread.currentThread().getName());
    }
}

class Demo12Thread extends Thread{  
    @Override
    public void run() {for(int i = 0; i < 5; i++){System.out.println(i);
        }
    }
}

调用 interrupt 办法不会真正的完结线程,而是给以后线程打上一个进行的标记
Thread 类提供了 interrupt 办法测试以后线程是否曾经中断,isInterrupted 办法测试线程是否曾经中断

执行后果为:

thread 线程是否曾经进行?true, Thread-0
0
1
2
3
4
以后线程是否曾经进行?false, main

thread.isInterrupted 办法查看线程类是否被打上进行的标记,Thread.interrupted 办法查看主线程是否被打上进行的标记

暂停线程

暂停线程应用 suspend 办法,重启暂停线程应用 resume 办法
suspend 办法暂停线程后,i 的值就不会持续减少。两次 ” 第一次 suspend” 输入的后果统一
resume 办法重启暂停线程后,i 的值会持续减少,再应用 suspend 办法暂停线程,两次 ”resume 后第二次 suspend:” 输入的后果统一

package com.test;

public class Demo13 {public static void main(String[] args) throws InterruptedException {Demo13Thread thread = new Demo13Thread();
    thread.start();
    Thread.sleep(100);
    thread.suspend();
    System.out.println("第一次 suspend:" + thread.getI());
        Thread.sleep(100);
        System.out.println("第一次 suspend:" + thread.getI());
        thread.resume();
        Thread.sleep(100);
        thread.suspend();
        System.out.println("resume 后第二次 suspend:" + thread.getI());
        Thread.sleep(100);
        System.out.println("resume 后第二次 suspend:" + thread.getI());
    }
}

class Demo13Thread extends Thread{
    private long i;
    public long getI() {return i;}        
    public void setI(long i) {this.i = i;}
    @Override
    public void run() {while (true){i++;}
    }
}

suspend 办法会使线程独占公共的同步对象,使其余线程无法访问公共的同步对象
suspend 办法还可能会造成共享对象的数据不同步

yield 办法

yield 办法是使以后线程放弃 CPU 资源,将资源让给其余的线程,然而放弃的工夫不确定,可能刚刚放弃,马上又获取 CPU 工夫片

package com.test;

public class Demo15 {public static void main(String[] args) {Demo15Thread thread = new Demo15Thread();
        thread.start();}
}

class Demo15Thread extends Thread{
    @Override
    public void run() {long start = System.currentTimeMillis();
        int count = 0;
        for(int i = 0; i < 50000; i++){Thread.yield();// 使以后线程放弃 CPU 资源,然而放弃的工夫不确定
            count = count + i;
        }    
        long end = System.currentTimeMillis();
        System.out.println("破费工夫:" + (end - start));
    }
}

线程的优先级

在操作系统中,线程是能够划分优先级的,优先级较高的线程可能失去更多的 CPU 资源,即 CPU 会优先执行优先级较高的线程对象中的工作。设置线程优先级有助于帮忙 ” 线程调度器 ” 确定下一次抉择哪个线程优先执行
设置线程的优先级应用 setPriority 办法,优先级分为 1~10 级,如果设置的优先级小于 1 或者大于 10,JVM 会抛出 IllegalArgumentException 异样,JDK 默认设置了 3 个优先级常量,MIN_PRIORITY=1(最小值),NORM_PRIORITY=5(两头值,也是默认值),MAX_PRIORITY=10(最大值)
获取线程的优先级应用 getPriority 办法

package com.test;

public class Demo16 {public static void main(String[] args) {System.out.println("主线程的运行优先级是:" + Thread.currentThread().getPriority());
        System.out.println("设置主线程的运行优先级");
        Thread.currentThread().setPriority(8);
        System.out.println("主线程的运行优先级是:" + Thread.currentThread().getPriority());
    /*
        线程的优先级具备继承性,原本默认的线程的优先级为 5
        然而将主线程的优先级设置为 8,此子线程也会继承主线程的优先级 8
         */
        Thread t = new Demo16Thread();
        t.start();}
}

class Demo16Thread extends Thread{
    @Override
    public void run() {System.out.println("线程的优先级是:" + this.getPriority());
    }
}

优先级较高的线程,先执行的概率较大

线程的同步机制

Java 多线程中的同步,指的是如何在 Java 语言中开发出线程平安的程序,或者如何在 Java 语言中解决线程不平安时所带来的问题
“ 线程平安 ” 与 ” 非线程平安 ” 是多线程技术中的经典问题。” 非线程平安 ” 就是当多个线程拜访同一个对象的成员变量时,读取到的数据可能是被其余线程批改过的(脏读)。” 线程平安 ” 就是获取的成员变量的值是通过同步解决的,不会有脏读的景象

synchronized 同步办法

局部变量是线程平安的
局部变量不存在线程平安的问题,永远都是线程平安的,这是由局部变量是公有的个性造成的

package com.test.chap2;

public class Demo1 {public static void main(String[] args) {Service service = new Service();
        ThreadDemo1 t1 = new ThreadDemo1(service);
        t1.start();
        ThreadDemo2 t2 = new ThreadDemo2(service);
        t2.start();}
}

class Service {public void add(String name){
        int number = 0;//number 是办法内的局部变量 
        if("a".equals(name)){
            number = 100;
            System.out.println("传入的参数为 a,批改 number 的值为:" + number);
            try {
            // 这里使线程休眠是为了期待其余线程批改 number 的值
                Thread.sleep(1000);
            } catch (InterruptedException e) {e.printStackTrace();
            }
        }else {
            number = 200;
            System.out.println("传入的参数不为 a,批改 number 的值为:" + number);
        }   
   }
}

class ThreadDemo1 extends Thread{
    private Service service;
    public ThreadDemo1(Service service){this.service = service;}
    @Override
    public void run() {service.add("a");
    }
}

class ThreadDemo2 extends Thread{
    private Service service;
    public ThreadDemo2(Service service){this.service = service;}               
    @Override
    public void run() {service.add("b");
    }
}

成员变量不是线程平安的
如果有两个线程,都要操作业务对象中的成员变量,可能会产生 ” 非线程平安 ” 的问题,此时须要在办法前应用 synchronized 关键字进行润饰
number 是 Demo2Service 类的成员变量,Demo2Service 类的 add 办法中,当传入的参数为 a 时,会进入 if 条件,休眠 1s,并将 number 的值改为 100,当传入的参数不为 a 时,不会休眠,将 number 的值改为 200
t3 线程,传入的参数为 a;t4 线程,传入的参数为 b,所以在线程 start 之后,t3 线程会休眠 1s,t4 线程不会休眠,所以 t4 线程会先将 number 的值改为 200 并输入,然而当 t3 线程完结休眠后,输入的 number 的值也是 200,这就产生了线程平安的问题
为了解决此线程不平安的问题,能够在办法前,加上 synchronized 关键字进行润饰,此时调用此办法的线程须要执行完,办法才会被另一个线程所调用

package com.test.chap2;

public class Demo2 {public static void main(String[] args) {Demo2Service service = new Demo2Service();
        ThreadDemo3 t3 = new ThreadDemo3(service);
        t3.start();
        ThreadDemo4 t4 = new ThreadDemo4(service);
        t4.start();}
}

class Demo2Service{
    private int number = 0;
    public void add(String name){if("a".equals(name)){
            number = 100;
            try {
                // 这里使线程休眠是为了期待其余线程批改 number 的值
                Thread.sleep(1000);
            } catch (InterruptedException e) {e.printStackTrace();
            }
            System.out.println("传入的参数为 a,批改 number 的值为:" + number);
        }else {
            number = 200;
            System.out.println("传入的参数不为 a,批改 number 的值为:" + number);
        }
   }
}

class ThreadDemo3 extends Thread{private Demo2Service service = new Demo2Service();
    public ThreadDemo3(Demo2Service service){this.service = service;}
    @Override
    public void run() {service.add("a");
    }
}

class ThreadDemo4 extends Thread{
    private Demo2Service service;
    public ThreadDemo4(Demo2Service service){this.service = service;}
    @Override
    public void run() {service.add("b");
    }
}                        

多个对象应用多个对象锁
synchronized 设置的锁都是对象锁,而不是将代码或者办法作为锁
当多个线程拜访同一个对象时,哪个线程先执行此对象带有 synchronized 关键字润饰的办法,其余线程就只能处于期待状态,直到此线程执行结束,开释了对象锁,其余线程能力继续执行
如果多个线程别离拜访多个对象,JVM 会创立出多个对象锁,此时每个线程之间都不会相互烦扰

锁的主动开释
当一个线程执行的代码呈现了异样,其持有的锁会主动开释

synchronized 同步语句块

synchronized 关键字润饰的办法的不足之处
如果线程 A 和线程 B 都拜访被 synchronized 关键字润饰的 get 办法,线程 B 就必须等线程 A 执行完后,能力执行,这样运行的效率低

synchronized 同步代码块的应用
同步代码块的作用与在办法上增加 synchronized 关键字润饰的作用是一样的

t1 和 t2 两个线程同时拜访 Demo10Service 的 synTest 办法,synTest 办法中局部代码加上了同步代码块,从输入后果能够发现,t1 和 t2 线程会同时拜访 synTest 办法并同时执行非同步代码块的逻辑,然而同步代码块的局部,t1 线程先拜访的话,t2 线程就必须等到 t1 线程执行结束后,能力继续执行
如果在 synTest 办法上加上 synchronized 关键字润饰,t1 线程先拜访 synTest 办法的话,t2 线程就必须等到 t1 线程执行结束后,才会拜访 synTest 办法并执行,总的来说,同步代码块能够锁住局部须要同步执行的代码,而办法中没有锁住的其余代码能够异步执行

package com.test.chap2;

public class Demo10 {public static void main(String[] args) throws InterruptedException {Demo10Service service = new Demo10Service();
        Thread t1 = new Demo10Thread(service);
        t1.setName("A");
        Thread t2 = new Demo10Thread(service);
        t2.setName("B");
        t1.start();
        t2.start();}
}

class Demo10Service{public void synTest(){System.out.println(Thread.currentThread().getName() + "线程拜访 synTest 办法");        
        try {synchronized (this) {System.out.println(Thread.currentThread().getName() + "线程开始~~~");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + "线程完结~~~");
            }
        } catch (InterruptedException e) {e.printStackTrace();
        }
    }
}

class Demo10Thread extends Thread{
    Demo10Service service;
    public Demo10Thread(Demo10Service service){this.service = service;}
    @Override
    public void run() {service.synTest();
    }
}    

volatile 关键字

volatile 关键字的次要作用是使变量在多个线程之间可见
当线程启动后,如果 flag 变量前没有 volatile 关键字润饰,线程会始终卡在 run 办法中的 while 循环中,批改 flag 的值不会失效,而加了 volatile 关键字润饰后,批改 flag 的值会失效,线程会退出 while 循环

在启动线程时,flag 变量存在于公共堆栈及线程的公有堆栈中。JVM 为了线程的运行效率,始终从公有堆栈中取 flag 的值,当执行 service.flag = false 语句时,尽管批改了 flag 的值,然而批改的却是公共堆栈的 flag 值,线程还是从公有堆栈中取 flag 的值,所以并不会退出 while 循环。应用 volatile 关键字润饰成员变量后,会强制 JVM 从公共堆栈中获取变量的值,所以可能退出 while 循环

package com.test.chap2;

public class Demo {public static void main(String[] args) throws InterruptedException {DemoService service = new DemoService();
        Thread t1 = new Thread(service);
        t1.start();
        Thread.sleep(100);
        System.out.println("筹备批改 flag 的值");
        service.flag = false;
        System.out.println(service.flag);
    }
}

class DemoService extends Thread{
    // 没有 volatile 关键字的话,线程会统一处于 while 循环中
    volatile public boolean flag = true;        
    @Override
    public void run() {System.out.println("开始运行 run 办法");
        while (flag){ }
        System.out.println("完结运行 run 办法");
    }
}

synchronized 和 volatile 的区别:
1、volatile 是线程同步的轻量级实现,所以 volatile 的性能要比 synchronized 要好,然而 volatile 只能润饰变量。而 synchronized 能够润饰办法以及代码块。随着 JDK 的版本更新,synchronized 在执行效率上也有很大的晋升,使用率还是较高
2、多线程拜访 volatile 不会阻塞,而拜访 synchronized 会呈现阻塞
3、volatile 能保证数据的可见性,然而不能保障原子性,可能会呈现脏读;而 synchronized 可能保障原子性,也能间接保障可见性,因为其能将公有内存和公共内存中的数据做同步
4、volatile 解决的是变量在多个线程之间的可见性,而 synchronized 解决的是多个线程之间拜访资源的同步性

最初

大家看完有什么不懂的能够在下方留言探讨,也能够关注我私信问我,我看到后都会答复的。也欢送大家关注我的公众号:前程有光,马上金九银十跳槽面试季,整顿了 1000 多道将近 500 多页 pdf 文档的 Java 面试题材料放在外面,助你圆梦 BAT!文章都会在外面更新,整顿的材料也会放在外面。谢谢你的观看,感觉文章对你有帮忙的话记得关注我点个赞反对一下!

退出移动版