Java中的多线程

2019-07-14 09:38发布

首先,我们引出进程的概念: 1.进程: 任何一个程序在运行起来之后就是进程,生命周期为程序开始到程序结束。 每个进程在运行周期内,有自己一个PCB、虚拟地址空间、页表、以及页表映射的物理内存空间。PCB中存放着进程的数据 上下文、进程状态、PID、优先级、内存指针。由操作系统通过调度PCB来管理进程,操作系统对PCB的管理就是对进程的管理。 2.线程: 上面我们从操作系统角度浅谈了一下进程的概念; 我们有很多时候希望可以同时推进多个任务,但是,我们看到每个进程在创建时有许多的东西被创建。 因此,太麻烦,我们这个时候引出了线程的概念,在一个程序里同时执行多个任务; 创建一个进程,然后再这一个进程里创建多个任务流,一个任务流就称之为一个线程;因此,线程一套就是共享数据,因此线程就相比进程显得更加轻便简单。 在一个进程里最少都有一个线程,因此线程叫做轻量级进程。 如何创建线程呢? 大体上有三种: 1.继承Thread类; 2.实现Runnable接口; 3.实现Callable接口。 1.继承Thread类: Java中java.lang.Thread这个类表示线程,一个类可以继承Thread类并覆写其run方法来实现一个线程; public class HelloThread extends Thread{ public void run(){ System.out.println("Hello"); } } run方法是固定的public权限,没有参数,没有返回值,不抛出受查异常。 类似于单线程中的main方法,线程run方法的第一条语句开始执行直到结束。 public static void main(String[] args){ Thread thread = new HelloThread(); thread.start(); } 上述代码为:在main方法中创建了一个线程对象,并调用其start方法,当调用start方法后,HelloThread中的run方法开始执行。 start表示启动线程,使其成为一条单独的执行流,操作系统会分配线程相关的资源,每个线程会有单独的程序计数器和栈(后面会单独将栈跟程序计数器),操作系统会将此线程作为一个独立的个体进行调度, 执行的起点就是run方法。 当直接在main方法中调用run方法的话,只是单纯的调用了一个方法,并没有体现线程的概念,这个时候可以使用currentThread()来查看当前线程。 2.实现Runnable接口 通过继承Thread类实现线程比较简单,但是Java里面只支持单继承,如果已经有了父类,就不可以在继承Thread类,于是就有了Runnable接口。 Runnable接口里只有一个run方法。 public class HelloRunnable implements Runnable{ public void run(){ System.out.println("hello"); } } 想要启动线程,还要创建一个Thread对象,在传递一个Runnable对象: public static void main(String[] args){ Thread helloThread = new Thread(new HelloRunnable()); helloThrea.start(); } 还可以使用匿名内部类和Lambda表达式来实现接口: 1.匿名内部类: public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("hello"); } }); thread.start(); } 2.Lambda表达式: public static void main(String[] args) { Runnable runnable = ()-> System.out.println("hello"); new Thread(runnable).start(); } 使用Runnable接口可以避免单继承局限以及更好的体现程序共享的概念。 3.实现Callable接口 Runnable接口中run方法没有返回值,但有些情况需要有返回值,此时就需要实现Callable接口。该接口中没有run方法,就只有一个call方法。 public interface Callable { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; } call方法的意义跟run方法一样,里面是线程实现的主体代码: class MyThread implements Callable { private int ticket = 10 ; // 一共10张票 public String call() throws Exception { while(this.ticket>0){ System.out.println("剩余票数:"+this.ticket -- ); } return "票卖完了,下次吧。。。" ; } } 然后再调用start方法来启动线程,但是Thread类中不可以接受Callable类型的参数; 但是有一个类FutureTask可接收Callable类型的参数; 然后FutureTask实现了RunnableFuture接口; RunnableFuture接口继承了Runnable接口; 同样,Thread类也继承了Runnable接口。 public static void main(String[] args) { FutureTask futureTask = new FutureTask(new Mythread); new Thread(futureTask).start(); } 这样就启动了覆写了Callable接口的线程。 4.线程有一些基本属性及方法。 属性:id、名称、优先级、状态 方法:sleep方法、yield方法、join方法、stop方法、interrupt方法、 每一个线程都有id、名称,id是一个递增的整数,每创建一个线程就加一。名称的默认值是Thread-后加编号,也可以自己指定,在Thread构造方法中,也可通过setName方法来设置。 优先级,可能会优先执行的线程,在程序中设置优先级可能会映射到操作系统中。 设置优先级:public final void setPriority(int newPriority) 取得优先级:public final int getPriority() 状态:表示当前线程的状态。 1.public  static  native  void  sleep(long  millis) throws InterruptedException; 调用该方法会让当前线程睡眠指定的时间,单位是毫秒: 睡眠期间,该线程会让出CPU,但是不会释放对象锁。 2.public static native void yield(); yield方法会让出CPU让其他线程先执行,但是不会释放对象锁;yield方法会让相同优先级的线程获得CPU的时间,yield方法是将线程从运行状态调整至准备状态,并不是变为阻塞状态。 3.public  final  void  join()  throws InterruptedException; 一个线程等待另一个线程的退出,然后再往下执行,在等待的过程中,这个等待可能被中断,如果被中断,会抛出异常。  4. public final void stop(); stop方法会解除线程获取的所有锁定,当一个线程对象调用stop方法时候,该对象所运行的线程会立即停止。 因为stop方法会造成线程不安全的情况,因此,已经被标记为过时方法。 5. public void interrupt(); interrupt只是对线程进行中断,中断并不是强迫终止一个线程,它给线程传递一个取消信号,由线程来决定如何以及何时退出。 a.对于正在执行的线程,interrupt方法只是设置线程的中断标志位,没有任何其他的作用。 b.当线程调用join/wait/sleep方法后,线程对象调用interrupt方法会抛出异常。抛出异常后,中断标志位会被清空,而不是被设置: public static void main(String[] args) { Thread t = new Thread(){ @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println(isInterrupted()); } } }; t.start(); try { Thread.sleep(100); } catch (InterruptedException e) { } t.interrupt(); } 上述的输出为false。 c.如果线程正在等待锁,调用interrupt方法只会设置线程的中断标志位,线程依旧处于等待的状态: public class Main { private static Object lock = new Object(); private static class A extends Thread{ @Override public void run() { synchronized (lock){ while (!Thread.currentThread().isInterrupted()){ System.out.println("哈哈哈"); } } System.out.println("exit"); } } public static void test() throws InterruptedException { synchronized (lock){ A a = new A(); a.start(); Thread.sleep(1000); a.interrupt(); a.join(); } } public static void main(String[] args) throws InterruptedException { test(); } } 上面的代码,主线程持有了lock锁,然后启动线程a,然而线程a也尝试获得lock锁,所以就进入等待锁队列,随后调用interrupt方法和join方法,来等待a线程结束。 然而运行的结果是线程并没有结束。 5.守护线程 守护线程是一种特殊线程,线程分为两种,“用户线程”,“守护线程”。 只要当前JVM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当最后 一个非守护线程结束时,守护线程才会随着JVM一同停止工作。 将当前线程标记为守护线程或用户线程:public final void setDaemon(boolean on),on为true为守护线程 判断守护线程:public final boolean isDaemon(),返回值为true为守护线程。 垃圾回收机制就是典型的守护线程。 6.synchronized与同步: public class Main implements Runnable { private static int counter = 0; @Override public void run() { for (int i = 0;i<1000;i++){ counter++; } } public static void main(String[] args) throws InterruptedException { int num=1000; Thread [] threads = new Thread[num]; for(int i=0;i 上述代码创造了1000个线程,每个线程对counter循环加了1000次,主线程会等待所有线程结束后打印counter的值,期望的结果是100万,然而打印出来却是90多万。因为上述代码的counter++操作不是原子性操作。 counter++分为三个步骤:        1.取得counter的值;        2.在当前基础上加1;        3.将新值赋给counter。 两个线程可能同时执行第一步,然后重复写入相同的值。 因为上述的情况就需要引入锁的概念。 synchronized是为了防止多个线程同时执行同个对象的同步代码块; synchronized可用于修饰类的实例方法、静态方法和代码块;     1.实例方法:       class Counter{ private int counter = 0; public synchronized void increaseNum(){ counter++; } public synchronized int getCounter(){ return counter; } } public class Main implements Runnable { Counter counter; public Main(Counter counter) { this.counter = counter; } @Override public void run() { for (int i = 0;i<1000;i++){ counter.increaseNum(); } } public static void main(String[] args) throws InterruptedException { int num=1000; Counter counter = new Counter(); Thread [] threads = new Thread[num]; for(int i=0;i 将代码中加入synchronized关键字来修饰,这样方法内的代码就变成了原子操作,这次无论怎样结果都为100万; 在上述代码中传入的对象只有一个counter,是同一个对象; 因此,synchronized实例方法实际保护的是同一个对象的方法调用,确保只能有一个线程来执行。 synchronized保护的是对象而非代码,只要访问同一个对象的synchronized方法,即使不同的代码,也会同步顺序访问。 2.静态方法 class Counter{ private static int counter = 0; public static synchronized void increaseNum(){ counter++; } public static synchronized int getCounter(){ return counter; } } public class Main implements Runnable { @Override public void run() { for (int i = 0;i<1000;i++){ Counter.increaseNum(); } } public static void main(String[] args) throws InterruptedException { int num=1000; Thread [] threads = new Thread[num]; for(int i=0;i 上述实例方法中保护的是实例对象是this,对于静态方法保护的是类对象。 3.代码块 class Counter{ private int counter = 0; public void increaseNum(){ synchronized (this){ counter++; } } public int getCounter(){ synchronized (this){ return counter; } } } public class Main implements Runnable { Counter counter; public Main(Counter counter) { this.counter = counter; } @Override public void run() { for (int i = 0;i<1000;i++){ counter.increaseNum(); } } public static void main(String[] args) throws InterruptedException { int num=1000; Counter counter = new Counter(); Thread [] threads = new Thread[num]; for(int i=0;i synchronized的特征:           1.可重入性;           2.内存可见性。 可重入性:对同一个执行线程,它获得了锁之后,在调用其它需要同样锁的代码的时候,就可以直接调用; 内存可见性:在释放锁的时候,所有写入都会写回内存,而获得锁后,都会从内存中读取最新的数据;而若是只需要保证内存可见性,可用volatile修饰变量。 死锁: 使用synchronized或者其他锁,应注意死锁; public class Main { private static Object locka = new Object(); private static Object lockb = new Object(); private static void startThreadA(){ Thread athread = new Thread(new Runnable() { @Override public void run() { synchronized (locka){ try { Thread.sleep(1000); } catch (InterruptedException e) { } synchronized (lockb){ } } } }); athread.start(); } private static void startThreadB(){ Thread bthread = new Thread(new Runnable() { @Override public void run() { synchronized (lockb){ try { Thread.sleep(1000); } catch (InterruptedException e) { } synchronized (locka){ } } } }); bthread.start(); } public static void main(String[] args) throws InterruptedException { startThreadA(); startThreadB(); } } 这样athread与bthread陷入了互相等待的过程。 以上就是我对线程的一些浅薄的理解,好了,不说了,继续敲代码了