多线程基础

来源:互联网 发布:航仕科技 知乎 编辑:程序博客网 时间:2024/05/22 11:32

多线程基础总结,后期会继续总结~~~~~~~`

1.多线程基础

1.1什么是多线程

运行中的exe程序就是一个进程,进程中独立的子任务就是线程

1.2线程和进程的区别

进程是系统进行资源分配的一个独立单位,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,一个进程可以包括多个线程

1.3线程的五大状态


⑴新建状态(New)

当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码

⑵就绪状态(Runnable)

一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。

⑶运行状态(Running)

当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.

⑷阻塞状态(Blocked)

线程运行过程中,可能由于各种原因进入阻塞状态:
1>线程通过调用sleep方法进入睡眠状态;
2>线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
3>线程试图得到一个锁,而该锁正被其他线程持有;
4>线程在等待某个触发条件;
......

所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态

⑸死亡状态(Dead)

有两个原因会导致线程死亡:
1) run方法正常退出而自然死亡,
2) 一个未捕获的异常终止了run方法而使线程猝死。
为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.

2.线程的启动

实现多线程的方式主要有两种:①继承Thread类 ②实现Runnable接口 ③实现Callable接口

继承Thread类

Thread类实现了Runnable接口,继承Thread类最大的缺点就是不支持多继承

代码:

package Thread1;public class MyThread extends Thread {   @Override    public void run() {// TODO Auto-generated method stubsuper.run();    System.out.println("线程1");   }}

package Thread1;public class Test {  public static void main(String[] args) {MyThread test=new MyThread();test.start();System.out.println("运行结束");}}
output:

运行结束线程1

如图,运行结果和代码执行或调用顺序无关,线程是一个子任务,CPU以随机的时间调用run方法。start方法的顺序也不是线程执行的顺序。

实现runnable接口

我们看一下Thread的构造函数,Thread有八个构造函数,其中有Thread(Runnable target)和Thread(Runnable target,String name)可以传递Runnable接口,说明构造函数支持传入Runnable对象

package Thread1;public class MyRunable implements Runnable {@Overridepublic void run() {// TODO Auto-generated method stub      System.out.println("运行中");}}
package Thread1;public class Test {  public static void main(String[] args) {    Runnable runnable=new MyRunable();    Thread test=new Thread(runnable);    test.start();    System.out.println("结束了");}}
output:

结束了
运行中
⑶实现callable()接口

①Callable规定的方法是call(),Runnable规定的方法是run().
②Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
③call方法可以抛出异常,run方法不可以
④运行Callable任务可以拿到一个Future对象,Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如果线程没有执行完,Future.get()方法可能会阻塞当前线程的执行;如果线程出现异常,Future.get()会throws InterruptedException或者ExecutionException;如果线程已经取消,会跑出CancellationException。取消由cancel 方法来执行。isDone确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明Future<?> 形式类型、并返回 null 作为底层任务的结果。

Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

package Thread15;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class CallableImpl implements Callable<String> {    public CallableImpl(String acceptStr) {        this.acceptStr = acceptStr;    }    private String acceptStr;    @Override    public String call() throws Exception {        // 任务阻塞 1 秒        Thread.sleep(1000);        return this.acceptStr + " append some chars and return it!";    }    public static void main(String[] args) throws ExecutionException, InterruptedException {        Callable<String> callable = new CallableImpl("my callable test!");        FutureTask<String> task = new FutureTask<>(callable);        long beginTime = System.currentTimeMillis();        // 创建线程        new Thread(task).start();        // 调用get()阻塞主线程,反之,线程不会阻塞        String result = task.get();        long endTime = System.currentTimeMillis();        System.out.println("hello : " + result);        System.out.println("cast : " + (endTime - beginTime) / 1000 + " second!");    }}
输出:

hello : my callable test! append some chars and return it!cast : 1 second!
3.实例变量与线程安全

自定义线程类的实例变量针对其它线程可以有共享与不共享之分,这在多个线程进行交互时时一个很重要的技术点。
首先来讲什么叫非线程安全:是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改,值不同步的情况,进而影响程序的执行流程。

线程不安全的情况:

package Thread3;public class MyThread extends Thread {  private int count=5;  @Overridepublic void run() {// TODO Auto-generated method stubsuper.run();count--;System.out.println(this.currentThread().getName()+"和"+count);}    }
package Thread3;public class Test {  public static void main(String[] args) {MyThread test=new MyThread();Thread a=new Thread(test, "A");Thread b=new Thread(test, "B");Thread c=new Thread(test, "C");Thread d=new Thread(test, "D");Thread e=new Thread(test, "E");a.start();b.start();c.start();d.start();e.start();}}
output:

A和3D和1B和3C和2E和0
产生了线程安全问题,只需要在run前面加synchronized即可

package Thread3;public class MyThread extends Thread {  private int count=5;  @Override  synchronized public void run() {// TODO Auto-generated method stubsuper.run();count--;System.out.println(this.currentThread().getName()+"和"+count);}  }
output:

A和4D和3C和2B和1E和0
4.停止线程
一般有三种方式可以停止线程:

使用stop方法强行终止线程(不推荐)

②使用退出标志

③使用interrupt方法中断线程。

⑴使用stop方法强行终止线程

使用stop方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,因此,并不推荐使用stop方法来终止线程。

⑵使用退出标志

public class StopThread extends Thread {    private boolean _run = true;    public void stopThread(boolean run) {        this._run = !run;    }    @Override    public void run() {        while(_run) {            // 数据处理        }    }}
⑶使用interrupt方法中断线程(待完善)

如果一个线程由于等待某些事件的发生而被阻塞,比如当一个线程由于需要等候键盘输入而被阻塞,或者调用Thread.join()方法,或者Thread.sleep()方法,在网络中调用ServerSocket.accept()方法,或者调用了DatagramSocket.receive()方法时,都有可能导致线程阻塞,使线程处于处于不可运行状态时,即使主程序中将该线程的共享变量设置为true,但该线程此时根本无法检查循环标志,当然也就无法立即中断。

这里分为两种情况

①线程处于阻塞状态

线程处于阻塞状态,如使用了sleep,同步锁的wait,socket中的receiver,accept等方法时,会使线程处于阻塞状态。当调用线程的interrupt()方法时,会抛出InterruptException异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后break跳出循环状态,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用interrupt方法线程就会结束,实际上是错的, 一定要先捕获InterruptedException异常之后通过break来跳出循环,才能正常结束run方法。

public class ThreadSafe extends Thread {    public void run() {         while (true){            try{                    Thread.sleep(5*1000);//阻塞5妙                }catch(InterruptedException e){                    e.printStackTrace();                    break;//捕获到异常之后,执行break跳出循环。                }        }    } }
②线程未处于阻塞状态

线程未处于阻塞状态,使用isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。

public class ThreadSafe extends Thread {    public void run() {         while (!isInterrupted()){            //do something, but no throw InterruptedException        }    } }
③整合上面两个:

为什么要区分进入阻塞状态和和非阻塞状态两种情况了,是因为当阻塞状态时,如果有interrupt()发生,系统除了会抛出InterruptedException异常外,还会调用interrupted()函数,调用时能获取到中断状态是true的状态,调用完之后会复位中断状态为false,所以异常抛出之后通过isInterrupted()是获取不到中断状态是true的状态,从而不能退出循环,因此在线程未进入阻塞的代码段时是可以通过isInterrupted()来判断中断是否发生来控制循环,在进入阻塞状态后要通过捕获异常来退出循环。因此使用interrupt()来退出线程的最好的方式应该是两种情况都要考虑:

public class ThreadSafe extends Thread {    public void run() {         while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出            try{                Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出            }catch(InterruptedException e){                e.printStackTrace();                break;//捕获到异常之后,执行break跳出循环。            }        }    } }

5.suspend()和resume()方法(已基本弃用 

⑴暂停线程意味着线程还可以恢复,采用suspend()方法暂停,采用resume()方法恢复线程
package Thread4;public class resumethread extends Thread{private long i=0;    public long getI() {      return i;}    public void setI(long i) {    this.i = i;}    @Override    public void run() {    while(true){     i++;    }  }}

package Thread4;public class RunResume {public static void main(String[] args) {// TODO Auto-generated method stub  try {      resumethread thread =new resumethread();        thread.start();            thread.suspend();  //推迟2        System.out.println("B="+System.currentTimeMillis()+"i="+thread.getI());        thread.resume();  //推迟3        resumethread.sleep(500);        System.out.println("B="+System.currentTimeMillis()+"i="+thread.getI());        } catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}       }}
output:

B=1506930077856i=0B=1506930078369i=189219766
⑵容易引发的问题:

①极易造成公共的同步对象的独占,使得其它线程无法访问公共同步对象。
简单来说就是两个线程用一个对象类,前一个线程推迟了,则第二个线程也无法使用对象的方法,被第一个线程独占了!

②suspend()方法容易发生死锁。 调用 suspend()的时候, 目标线程会停
下来, 但却仍然持有在这之前获得的锁定。 此时, 其他任何线程都不能访问锁定的资
源, 除非被"挂起"的线程恢复运行

③有时候会出现值不同步的情况

6.sleep方法
sleep方法的作用是让当前线程暂停指定的时间(毫秒),这里要注意几点:

①必须捕获异常。

②线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态

③sleep方法没有释放锁

package Thread5;public class MyThread extends Thread{ public void run() {        for (int i = 0; i < 10; i++) {            if ((i) % 5 == 0) {                System.out.println("-------" + i);            }                       try {            System.out.print(System.currentTimeMillis());                Thread.sleep(100);                System.out.print("    线程睡眠1秒");                System.out.println(System.currentTimeMillis());            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public static void main(String[] args) {        new MyThread().start();    }}

输出:

-------01506951478531    线程睡眠1秒15069514786311506951478631    线程睡眠1秒15069514787311506951478731    线程睡眠1秒15069514788311506951478831    线程睡眠1秒15069514789311506951478931    线程睡眠1秒1506951479031-------51506951479031    线程睡眠1秒15069514791311506951479131    线程睡眠1秒15069514792311506951479231    线程睡眠1秒15069514793311506951479331    线程睡眠1秒15069514794311506951479431    线程睡眠1秒1506951479531
7.yeild方法
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的

package Thread4;public class YieldExample {public static void main(String[] args)   {      Thread producer = new Producer();      Thread consumer = new Consumer();       producer.setPriority(Thread.MIN_PRIORITY); //Min Priority      consumer.setPriority(Thread.MAX_PRIORITY); //Max Priority       producer.start();      consumer.start();   }} class Producer extends Thread{   public void run()   {      for (int i = 0; i < 5; i++)      {         System.out.println("I am Producer : Produced Item " + i);         Thread.yield();      }   }} class Consumer extends Thread{   public void run()   {      for (int i = 0; i < 5; i++)      {         System.out.println("I am Consumer : Consumed Item " + i);         Thread.yield();      }   }}
输出:
I am Producer : Produced Item 0I am Consumer : Consumed Item 0I am Producer : Produced Item 1I am Consumer : Consumed Item 1I am Producer : Produced Item 2I am Consumer : Consumed Item 2I am Producer : Produced Item 3I am Consumer : Consumed Item 3I am Consumer : Consumed Item 4I am Producer : Produced Item 4
8.join方法
⑴join的作用:Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。

package Thread4;public class joinTest {public static void main(String [] args) throws InterruptedException {        ThreadJoinTest t1 = new ThreadJoinTest("小明");        ThreadJoinTest t2 = new ThreadJoinTest("小东");        t1.start();        /**join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:         程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕         所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会         */        t1.join();          //我们把join放这里,只有当小明打印完之后才会继续执行        t2.start();            }}class ThreadJoinTest extends Thread{    public ThreadJoinTest(String name){        super(name);    }    @Override    public void run(){        for(int i=0;i<10;i++){            System.out.println(this.getName() + ":" + i);        }    }}
join里面也可以传递参数,但是join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。
public class JoinTest {    public static void main(String [] args) throws InterruptedException {        ThreadJoinTest t1 = new ThreadJoinTest("小明");        ThreadJoinTest t2 = new ThreadJoinTest("小东");        t1.start();        /**join方法可以传递参数,join(10)表示main线程会等待t1线程10毫秒,10毫秒过去后,         * main线程和t1线程之间执行顺序由串行执行变为普通的并行执行         */        t1.join(10);        t2.start();    }}class ThreadJoinTest extends Thread{    public ThreadJoinTest(String name){        super(name);    }    @Override    public void run(){        for(int i=0;i<1000;i++){            System.out.println(this.getName() + ":" + i);        }    }}
⑵join的原理:

 其实,join方法是通过调用线程的wait方法来达到同步的目的的。例如,A线程中调用了B线程的join方法,则相当于A线程调用了B线程的wait方法,在调用了B线程的wait方法后,A线程就会进入阻塞状态,具体看下面的源码:

   

public final synchronized void join(long millis)    throws InterruptedException {        long base = System.currentTimeMillis();        long now = 0;        if (millis < 0) {            throw new IllegalArgumentException("timeout value is negative");        }        if (millis == 0) {            while (isAlive()) {                wait(0);            }        } else {            while (isAlive()) {                long delay = millis - now;                if (delay <= 0) {                    break;                }                wait(delay);                now = System.currentTimeMillis() - base;            }        }    }

9.wait()、notify()和notifyAll()

wait,notify,notifyAll 是定义在Object类的实例方法,用于控制线程状态。

三个方法都必须在synchronized 同步关键字所限定的作用域中调用,否则会报错java.lang.IllegalMonitorStateException ,意思是因为没有同步,所以线程对对象锁的状态是不确定的,不能调用这些方法。

⑴作用:

①wait():线程等待。

 public final void wait()  throws InterruptedException,IllegalMonitorStateException
该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。进入wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时,没有持有适当的锁,则抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch结构。
wait可以加上两个参数;

wait(long)和wait(long,int)
显然,这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从wait(long)返回。另外,需要知道,如果设置了超时时间,当wait()返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为wait()方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在notify之前改变标志位的值,在wait()方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还需要另外一个标志位来循环判断是否调用wait()方法。
②notify(): 唤醒一个正在等待该对象的线程。

 public final native void notify() throws IllegalMonitorStateException
该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用notify()时没有持有适当的锁,也会抛出IllegalMonitorStateException。
该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知,并使它等待获取该对象的对象锁(notify后,当前线程不会马上释放该对象锁,wait所在的线程并不能马上获取该对象锁,要等到程序退出synchronized代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象notify的线程们。当第一个获得了该对象锁的wait线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用notify语句,则即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。这里需要注意:它们等待的是被notify或notifyAll,而不是锁。这与下面的notifyAll()方法执行后的情况不同。
③notifyAll(): 唤醒所有正在等待该对象的线程。

public final native void notifyAll() throws IllegalMonitorStateException
该方法与notify()方法的工作方式相同,重要的一点差异是:
notifyAll使所有原来在该对象上wait的线程统统退出wait的状态(即全部被唤醒,不再等待notify或notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll线程退出调用了notifyAll的synchronized代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出synchronized代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。
2.例子1:生产者消费者模式

这个例子可以控制最大生产几个

生产者:

package Thread6;import java.util.List;public class Product implements Runnable {     private List container = null; private int count; public Product(List lst) {  this.container = lst; } public void run() {  while (true) {   synchronized (container) {    if (container.size() > MultiThread.MAX) {     // 如果容器超过了最大值,就不要在生产了,等待消费    try {container.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}    }    try {     Thread.sleep(1000);    } catch (InterruptedException e) {     e.printStackTrace();    }    container.add(new Object());    container.notify();    System.out.println("我生产了" + (++count) + "个");   }  } }}
消费者:

package Thread6;import java.util.List;public class Consume implements Runnable  { private List container = null; private int count; public Consume(List lst) {  this.container = lst; } public void run() {  while (true) {   synchronized (container) {    if (container.size() == 0) {     try {      container.wait();// 容器为空,放弃锁,等待生产     } catch (InterruptedException e) {      e.printStackTrace();     }    }    try {     Thread.sleep(1000);    } catch (InterruptedException e) {     e.printStackTrace();    }    container.remove(0);    container.notify();    System.out.println("我吃了" + (++count) + "个");   }  } }}
package Thread6;import java.util.ArrayList;import java.util.List;public class MultiThread {private List container = new ArrayList(); public final static int MAX = 1; public static void main(String args[]) {  MultiThread m = new MultiThread();  new Thread(new Consume(m.getContainer())).start();  new Thread(new Product(m.getContainer())).start(); } public List getContainer() {  return container; } public void setContainer(List container) {  this.container = container; }}
这里当容器内生产的多于1个(即等于2个)生产者就会调用wait方法。

我生产了1个我生产了2个我吃了1个我生产了3个我吃了2个我生产了4个我吃了3个我吃了4个我生产了5个我生产了6个我吃了5个我生产了7个我吃了6个我吃了7个我生产了8个我生产了9个……
可以看到容器里最多有两个!

10.线程优先级

优先级高的线程获得的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。

①线程优先级用thread.setPriority(int a)( 1<=a<=10)方法来进行赋值
②线程优先级有继承性,如果主线程启动threadA线程且threadA线程没有另外赋予优先级,则threadA线程优先级和main线程一样(即通过A线程启动线程B,线程B没有设置优先级则优先级同A一致)。线程默认优先级为5.
③CPU尽量将执行资源让给线程优先级高的,即线程优先级高的总是会大部分先执行,但是不代表高优先级的线程全部都先执行完再执行低优先级的线程

④在start方法前面设置

package Demo;class MyThread extends Thread{      public MyThread(String name) {        super(name);    }    public void run(){        for (int i=0; i<500; i++) {            System.out.println(Thread.currentThread().getName()                    +"("+Thread.currentThread().getPriority()+ ")"                    +", loop "+i);        }    } }; public class Demo {      public static void main(String[] args) {          System.out.println(Thread.currentThread().getName()                +"("+Thread.currentThread().getPriority()+ ")");        Thread t1=new MyThread("t1");    // 新建t1        Thread t2=new MyThread("t2");    // 新建t2        t1.setPriority(1);                // 设置t1的优先级为1        t2.setPriority(10);                // 设置t2的优先级为10        t1.start();                        // 启动t1        t2.start();                        // 启动t2    }  }

可以看到当线程t2运行完之后t1还有很多没运行。
11.守护线程

JAVA线程中有两种线程:用户线程,守护线程。

守护线程是一种特殊的线程,它到特性有陪伴的含义,当进程中不存在非守护线程时,守护线程自动销毁,典型的守护线程就是垃圾回收线程,当进程中没有非守护线程了,则垃圾回收线程也没有存在的必要了,自动销毁。

Thread daemonTread = new Thread();       // 设定 daemonThread 为 守护线程,default false(非守护线程)   daemonThread.setDaemon(true);      // 验证当前线程是否为守护线程,返回 true 则为守护线程   daemonThread.isDaemon();  
这里有几点需要注意:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。 

package Thread7;public class Test {  public  static  void main(String[] args) {            Thread t1 =  new MyCommon();            Thread t2 =  new Thread( new MyDaemon());            t2.setDaemon( true); //设置为守护线程            t2.start();            t1.start();    }  }  class MyCommon  extends Thread {     public  void run() {             for ( int i = 0; i < 5; i++) {                    System.out.println( "线程1第" + i +  "次执行!");                     try {                            Thread.sleep(7);                    }  catch (InterruptedException e) {                            e.printStackTrace();                    }            }    }  }  class MyDaemon  implements Runnable {     public  void run() {             for ( long i = 0; i < 999; i++) {                    System.out.println( "守护线程第" + i +  "次执行!");                     try {                            Thread.sleep(7);                    }  catch (InterruptedException e) {                            e.printStackTrace();                    }            }    }  }
output:

线程1第0次执行!守护线程第0次执行!守护线程第1次执行!线程1第1次执行!守护线程第2次执行!线程1第2次执行!线程1第3次执行!守护线程第3次执行!守护线程第4次执行!线程1第4次执行!守护线程第5次执行!
可见守护线程并没有执行完就结束了,当非守护线程结束后守护线程自动销毁。

12.多线程之内存可见性

⑴什么是内存可见性

一个线程对共享变量值的修改能够及时被其它线程看到,我们把这个共享变量叫做可见的。
这里我们要知道:
①所有变量都存储在主内存中。
②每个线程都有自己独立的工作内存,里面保存该线程使用到的变量的副本。


另外JAVA内存模型有如下两条规定
①线程对共享变量的所有操作必须在自己的工作内存中进行,不能直接从主内存中读写。
②线程间变量值传递必须通过主内存。
如果线程1对共享变量的操作希望线程2看到,需要如下两个步骤:
①将共享变量刷新到主内存 ②更新到线程2工作内存

⑵Synchronized方法

这里要认清一点:即使不加关键字很多时候也能得到及时更新,只是有时候不能及时更新
synchronized能够实现原子性和可见性。
JMM对synchronized的两条规定:

①线程解锁时把共享变量值刷新到主内存。
②加锁时清空工作内存共享变量的值,使用共享变量从主内存读取最新的值
⑶使用volatile

volatile能够保证可见性但不能保证原子性
volatile如何保证可见性:加入内存屏障和禁止重排序。
对volatile变量执行写操作,会强制刷新到主内存中区。读的时候也会强制从主内存中读。
不能保证原子性:

⑷synchronized和volatile的比较

volatile不需要加锁,比synchronized更轻量级,不会阻塞线程,效率更高
从内存可见性角度讲,volatile读相当于加锁,volatile写相当于解锁
synchronized技能保证可见性,又能保证原子性,而volatile只能保证可见性,不能保证原子性。
如果能用volatile解决问题,还是应尽量使用volatile,因为它的效率更高

13.死锁

⑴什么是死锁

是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外部处理作用,它们都将无限等待下去。

死锁条件:

①互斥条件:一个资源每次只能被一个进程(线程)使用。

②请求与保持条件:一个进程(线程)因请求资源而阻塞时,对已获得的资源保持不放。

③不剥夺条件 : 此进程(线程)已获得的资源,在末使用完之前,不能强行剥夺。

④循环等待条件 : 多个进程(线程)之间形成一种头尾相接的循环等待资源关系。

⑵写一个死锁的栗子:

package Thread8;public class DeadLockDemo02 {public static void main(String[] args) {            Test a =  new Test(true);            Test b = new Test(false);                        Thread t0 =  new Thread(a);            Thread t1 = new Thread(b);                        t0.start();            t1.start();        }    }    class Test implements Runnable{        private static Object oa = new Object();   //加上static才会共享资源产生死锁    private static Object ob = new Object();       private boolean flag ;        public Test(boolean flag) {            this.flag = flag;        }        @Override        public void run() {            if (flag) {                    synchronized (oa) {                        System.out.println("if...lockoa1");                        synchronized (ob) {                            System.out.println("if...lockob1");                        }                 }            }else {                      synchronized (ob) {                        System.out.println("if...lockob2");                           synchronized (oa) {                            System.out.println("if...lockoa2");                        }                    }            }        }    }    
输出有两种情况:

①输出四个值, 比如

if...lockob2if...lockoa2if...lockoa1if...lockob1
②输出两个值,也就是发生了死锁!

if...lockob2if...lockoa1
⑶如何解决死锁

①加锁顺序

如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生

比如上面代码重写run方法改为:

  @Override        public void run() {            if (flag) {                    synchronized (oa) {                        System.out.println("if...lockoa1");                        synchronized (ob) {                            System.out.println("if...lockob1");                        }                 }            }else {                      synchronized (oa) {                        System.out.println("if...lockob2");                           synchronized (ob) {                            System.out.println("if...lockoa2");                        }                    }            }        }    
就不会产生死锁了。
②加锁时限

另外一个可以避免死锁的方法是在尝试获取锁的时候加一个超时时间,这也就意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。若一个线程没有在给定的时限内成功获得所有需要的锁,则会进行回退并释放所有已经获得的锁,然后等待一段随机的时间再重试。
这种存在问题,首先在Java中不能对synchronized同步块设置超时时间,其次超时并不一定是死锁导致的,尤其是线程多的情况下。

③死锁检测

每当一个线程获得了锁,会在线程和锁相关的数据结构中(map、graph等等)将其记下。除此之外,每当有线程请求锁,也需要记录在这个数据结构中。给这些线程设置优先级,当检测出死锁时,让一个(或几个)线程回退,剩下的线程就像没发生死锁一样继续保持着它们需要的锁。如果赋予这些线程的优先级是固定不变的,同一批线程总是会拥有更高的优先级。为避免这个问题,可以在死锁发生的时候设置随机的优先级。



原创粉丝点击