java基础(三):多线程

来源:互联网 发布:java并发是什么意思 编辑:程序博客网 时间:2024/06/06 04:34

概述

  1. 进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是每一个执行路径,或者叫一个控制单元
  2. 线程:进程中的一个独立的控制单元,线程在控制着进程的执行。一个进程中至少有一个线程。

创建线程

创建线程有两种方法
1. 继承Thread类
  创建流程:
    1.1、定义类继承Thread
    1.2、复写Thread类中的run方法
    1.3、调用线程的start方法(启动线程,调用run方法)
     start方法开启线程并调用run方法,直接调用run方法没有开启线程(仅仅是调用对象方法)
2. 实现Runnable接口
  创建流程:
    2.1、定义类实现Runnable接口
    2.2、覆盖Runnable接口中的run方法
    2.3、通过Thread类建立线程对象
    2.4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
    2.5、调用Thread类的start方法开启线程

这里写图片描述

注:其中stop();方法已过时

  实现方式的好处:
    实现:避免单继承的局限性。独立资源。在定义线程时,建议使用实现方式

同步

  1. 多线程同步(synchronized)前提:
      必须要有两个或者两个以上的线程
      必须是多个线程使用同一个对象的锁
  2. 同步的利弊
      同步好处:解决了多线程的安全问题
      同步弊端:多个线程需要判断锁,比较消耗资源
  3. 函数函数的锁(this)
public class ThisLockDemo {    public static void main(String[] args) {        TicketDemo td = new TicketDemo();        Thread t1 = new Thread(td);        Thread t2 = new Thread(td);        Thread t3 = new Thread(td);        Thread t4 = new Thread(td);        t1.start();        t2.start();        t3.start();        t4.start();    }}class TicketDemo implements Runnable{    private int tick = 100;    @Override    public void run() {        while (true) {            saleTick();//这里是有省略this的        }    }    public synchronized void saleTick() {//可以推断出这里也是this锁        if (tick > 0) {            try {                Thread.sleep(10);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() + " 卖票啦  " + tick--);        }    }}

  验证this锁

public class ThisLockDemo {    public static void main(String[] args) {        TicketDemo td = new TicketDemo();        Thread t1 = new Thread(td);        Thread t2 = new Thread(td);        t1.start();        try{Thread.sleep(10);}catch(InterruptedException e){}        td.flag = false;        t2.start();    }}class TicketDemo implements Runnable{    private int tick = 10;    public boolean flag = true;    Object obj = new Object();    @Override    public void run() {        if (flag) {            while (true) {                synchronized(obj) {                    if (tick > 0) {                        try {Thread.sleep(20);} catch (InterruptedException e) {}                        System.out.println(Thread.currentThread().getName() + " if卖票啦  " + tick--);                    }                }            }        } else {            while (true)                saleTick();        }    }    public synchronized void saleTick() {//this        if (tick > 0) {            try {Thread.sleep(20);} catch (InterruptedException e) {}            System.out.println(Thread.currentThread().getName() + " else卖票啦  " + tick--);        }    }}

  输出结果为:

Thread-0 if卖票啦  10Thread-1 else卖票啦  9Thread-0 if卖票啦  8Thread-1 else卖票啦  7Thread-0 if卖票啦  6Thread-1 else卖票啦  5Thread-0 if卖票啦  4Thread-1 else卖票啦  3Thread-0 if卖票啦  2Thread-1 else卖票啦  1Thread-0 if卖票啦  0

  结果出现0号票,出现安全隐患。
    分析:此多线程程序加了锁还出现安全隐患,说明同步的两个前提没有满足。
      第一个条件是必须两个或者两个以上的线程,此条件满足。
      既然满足了第一个条件,那么肯定是第二个条件不满足了(使用同一个锁)。
  来验证下this锁会不会出现安全问题:

public class ThisLockDemo {    public static void main(String[] args) {        TicketDemo td = new TicketDemo();        Thread t1 = new Thread(td);        Thread t2 = new Thread(td);        t1.start();        try{Thread.sleep(10);}catch(InterruptedException e){}        td.flag = false;        t2.start();    }}class TicketDemo implements Runnable{    private int tick = 10;    public boolean flag = true;    //Object obj = new Object();    @Override    public void run() {        if (flag) {            while (true) {                synchronized(this) {                    if (tick > 0) {                        try {Thread.sleep(20);} catch (InterruptedException e) {}                        System.out.println(Thread.currentThread().getName() + " if卖票啦  " + tick--);                    }                }            }        } else {            while (true)                saleTick();        }    }    public synchronized void saleTick() {//this        if (tick > 0) {            try {Thread.sleep(20);} catch (InterruptedException e) {}            System.out.println(Thread.currentThread().getName() + " else卖票啦  " + tick--);        }    }}

  输出结果:

Thread-0 if卖票啦  10Thread-0 if卖票啦  9Thread-0 if卖票啦  8Thread-0 if卖票啦  7Thread-0 if卖票啦  6Thread-0 if卖票啦  5Thread-0 if卖票啦  4Thread-1 else卖票啦  3Thread-1 else卖票啦  2Thread-0 if卖票啦  1

  数次运行,结果并没有出现0号票,说明同步函数的锁是this

  4.静态同步函数的锁是class对象
    静态方法中是没有this的,那么锁是谁呢?来验证下

public class ThisLockDemo {    public static void main(String[] args) {        TicketDemo td = new TicketDemo();        Thread t1 = new Thread(td);        Thread t2 = new Thread(td);        t1.start();        try{Thread.sleep(10);}catch(InterruptedException e){}        td.flag = false;        t2.start();    }}class TicketDemo implements Runnable{    private static int tick = 10;    public boolean flag = true;    @Override    public void run() {        if (flag) {            while (true) {                synchronized(this) {//这里锁是obj(new Object())输出的结果同样存在安全隐患                    if (tick > 0) {                        try {Thread.sleep(20);} catch (InterruptedException e) {}                        System.out.println(Thread.currentThread().getName() + " if卖票啦  " + tick--);                    }                }            }        } else {            while (true)                saleTick();        }    }    public static synchronized void saleTick() {//this        if (tick > 0) {            try {Thread.sleep(20);} catch (InterruptedException e) {}            System.out.println(Thread.currentThread().getName() + " else卖票啦  " + tick--);        }    }}

  输出结果:

Thread-0 if卖票啦  10Thread-1 else卖票啦  9Thread-0 if卖票啦  8Thread-1 else卖票啦  7Thread-0 if卖票啦  6Thread-1 else卖票啦  5Thread-0 if卖票啦  4Thread-1 else卖票啦  3Thread-0 if卖票啦  2Thread-1 else卖票啦  1Thread-0 if卖票啦  0

  很显然,锁不是this,静态方法进内存的时候是没有对象的,是由类直接调用的,类进内存会封装成class类型的对象(即字节码文件对象):类名.class 该对象的类型是Class
  那么试试:类名.class锁实验下

public class ThisLockDemo {    public static void main(String[] args) {        TicketDemo td = new TicketDemo();        Thread t1 = new Thread(td);        Thread t2 = new Thread(td);        t1.start();        try{Thread.sleep(10);}catch(InterruptedException e){}        td.flag = false;        t2.start();    }}class TicketDemo implements Runnable{    private static int tick = 10;    public boolean flag = true;    @Override    public void run() {        if (flag) {            while (true) {                synchronized(TicketDemo.class) {                    if (tick > 0) {                        try {Thread.sleep(20);} catch (InterruptedException e) {}                        System.out.println(Thread.currentThread().getName() + " if卖票啦  " + tick--);                    }                }            }        } else {            while (true)                saleTick();        }    }    public static synchronized void saleTick() {//this        if (tick > 0) {            try {Thread.sleep(20);} catch (InterruptedException e) {}            System.out.println(Thread.currentThread().getName() + " else卖票啦  " + tick--);        }    }}

  输出结果:

Thread-0 if卖票啦  10Thread-0 if卖票啦  9Thread-0 if卖票啦  8Thread-0 if卖票啦  7Thread-0 if卖票啦  6Thread-1 else卖票啦  5Thread-1 else卖票啦  4Thread-1 else卖票啦  3Thread-1 else卖票啦  2Thread-1 else卖票啦  1

  由此可以推断出静态函数的锁是类进内存会封装成class类型的对象(即字节码文件对象):类名.class

  多线程下的延迟加载单例模式(双重判断锁)

class Single {    private static Single s = null;    public static Single getInstance() {        if (s == null) {            synchronized (Single.class) {                if (s==null) {                    s = new Single();                }            }        }        return s;    }}

  线程死锁:同步中嵌套同步,但是锁却不同步,容易导致死锁。线程死锁时,第一个线程等待第二个线程释放资源,而同时第二个线程又在等待第一个线程释放资源。这里举一个通俗的例子:如在人行道上两个人迎面相遇,为了给对方让道,两人同时向一侧迈出一步,双方无法通过,又同时向另一侧迈出一步,这样还是无法通过。假设这种情况一直持续下去,这样就会发生死锁现象。 导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。
  来看个小例子

public class DeadLockDemo {    public static void main(String[] args) {        TicketDemo td = new TicketDemo();        Thread t = new Thread(td);        Thread t1 = new Thread(td);        t.start();        try{Thread.sleep(10);}catch(Exception e){}        td.flag = false;        t1.start();    }}class TicketDemo implements Runnable {    private int tick = 1000;    Object obj = new Object();    boolean flag = true;    @Override    public void run() {        if(flag) {            while (true) {                synchronized (obj) {//object锁                    saleTick();//this锁                }            }        } else {            while (true){                saleTick();            }        }    }    public synchronized void saleTick() {//this锁        synchronized (obj) {//object锁            if (tick > 0) {                try{Thread.sleep(10);}catch(Exception e){}                System.out.println(Thread.currentThread().getName() + " 线程名称  " + tick--);            }        }    }}

  输出结果(每次运行结果不一样,如果数值较小容易出现和谐状态,可以将数值调大,即可产生死锁)
线程死锁结果

  再举个简单点的死锁例子:

public class DeadLockDemo2 {    public static void main(String[] args) {        Thread t = new Thread(new DeadLock(true));        Thread t1 = new Thread(new DeadLock(false));        t.start();        t1.start();    }}class DeadLock implements Runnable {    private boolean flag;    public DeadLock(boolean flag) {        this.flag = flag;    }    @Override    public void run() {        if (flag) {            while (true) {//如果你电脑上运行时和谐情况比较多,            //就加个循环,只要数据够大,在这种嵌套锁(锁对象不同)的情况下肯定会死锁                synchronized (MyLock.locka) {                    System.out.println("if locka");                    synchronized (MyLock.lockb) {                        System.out.println("if lockb");                    }                }            }        } else {            while (true) {                synchronized (MyLock.lockb) {                    System.out.println("else lockb");                    synchronized (MyLock.locka) {                        System.out.println("else locka");                    }                }            }        }    }}class MyLock {    static Object locka = new Object();    static Object lockb = new Object();}

  我的输出结果:

else lockbif locka//到这里就锁住了。

线程间通讯

等待唤醒机制
  主要方法:wait();notify();notifyAll();
   都使用在同步中,因为要对持有监视器(锁)的线程操作,所以必须在同步中使用
  这些方法在操作同步中线程时,都必须要标识他们所操作线程中的锁对象。只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。不可以对不同锁中的线程进行唤醒。也就是说等待和唤醒必须是同一个锁。
  示例:

/*需求:多个线程操作同一个资源。如,一个线程存名字和性别。另外一个线程获取姓名和性别。*/public class InputOutput {    public static void main(String[] args) {        Person p = new Person();        Input i = new Input(p);        Output o = new Output(p);        Thread t = new Thread(i);        Thread t1 = new Thread(o);        t.start();        t1.start();    }}class Input implements Runnable {    private Person p;    public Input(Person p) {        this.p = p;    }    @Override    public void run() {        while (true) {            System.out.println(p.name + " ... " + p.sex);        }    }}class Output implements Runnable {    private Person p;    public Output(Person p) {        this.p = p;    }    @Override    public void run() {        boolean flag = true;        while (true) {            if (flag) {                p.name = "张三";                p.sex = "男";                flag = false;            } else {                            p.name = "divid";                p.sex = "women";                flag = true;            }        }    }}class Person {    String name;//此为演示代码,工作中一般将属性私有化,并提供get和set方法    String sex;}

得到结果为:

//部分结果张三 ... 男divid ... womendivid ... 男张三 ... 男divid ... women张三 ... 男张三 ... womendivid ... women张三 ... women

可以看出,多线程之间通讯存在安全隐患。修改代码(加锁):

public class InputOutput {    public static void main(String[] args) {        Person p = new Person();        Input i = new Input(p);        Output o = new Output(p);        Thread t = new Thread(i);        Thread t1 = new Thread(o);        t.start();        t1.start();    }}class Input implements Runnable {    private Person p;    public Input(Person p) {        this.p = p;    }    @Override    public void run() {        while (true) {            synchronized(p) {//此处的锁是用的p(因为p是两个线程操纵的共同数据,记住:为保证同步,锁的对象必须相同)                System.out.println(p.name + " ... " + p.sex);            }        }    }}class Output implements Runnable {    private Person p;    public Output(Person p) {        this.p = p;    }    @Override    public void run() {        boolean flag = true;        while (true) {            synchronized(p) {                if (flag) {                    p.name = "张三";                    p.sex = "男";                    flag = false;                } else {                                p.name = "divid";                    p.sex = "women";                    flag = true;                }            }        }    }}class Person {    String name;//此为演示代码,工作中一般将属性私有化,并提供get和set方法    String sex;}

结果:

张三 ... 男张三 ... 男张三 ... 男张三 ... 男张三 ... 男张三 ... 男张三 ... 男张三 ... 男张三 ... 男divid ... womendivid ... womendivid ... womendivid ... womendivid ... womendivid ... womendivid ... women

得到的结果虽然真确了,但是却不是我要的:存一个,打印一个。修改后

//优化后的代码public class InputOutput {    public static void main(String[] args) {        Person p = new Person();        new Thread(new Input(p)).start();//生产1        new Thread(new Input(p)).start();//生产2              new Thread(new Output(p)).start();//消费1        new Thread(new Output(p)).start();//消费2    }}class Input implements Runnable {    private Person p;    public Input(Person p) {        this.p = p;    }    @Override    public void run() {        while (true) {            p.out();        }    }}class Output implements Runnable {    private Person p;    public Output(Person p) {        this.p = p;    }    @Override    public void run() {        int x = 0;        while (true) {            if (x == 0) {                p.setPerson("张三","男");            } else {                            p.setPerson("Divid","women");            }            x = (x + 1) % 2;        }    }}class Person {    private String name;    private String sex;    boolean flag = false;    //此为演示代码,一般将属性私有化,并提供get和set方法    //使用this作为锁的对象    public synchronized void setPerson(String name,String sex) {        while(this.flag) {//加入两个村的线程都wait了。notifyAll唤醒所有线程后就会再次判断标记,如果是if则直接往下运行,会出现生产两次,消费一次、生产一次消费两次的情况            try {this.wait();} catch (InterruptedException e) {}        }        this.name = name;        this.sex = sex;        this.flag = true;        //唤醒所有线程(flag判断有if变成while后)(因为线程是先进先出,假设生产2生产一次后wait了,然后生产1判断标记后wait了,再消费1消费一次后wait了,接着唤醒一次,消费2wait了。生产2就活了,2生产一次,改变标记,唤醒一次,生产1醒了,判断标记后又wait,所有的线程都wait了。。。就挂了。所以必须notifyAll)        this.notifyAll();    }    public synchronized void out() {        while(!this.flag) {            try {this.wait();} catch (InterruptedException e) {}        }        System.out.println(this.name + " ... ... " + this.sex);        this.flag = false;        this.notifyAll();    }}

运行结果正常。

//可以copy代码自己运行下张三 ... 男divid ... women张三 ... 男divid ... women张三 ... 男divid ... women

解释为什么要用notifyAll而不是notify
notifyAll解释

JDK1.5之后用lock(多个Condition对象,可以完美的控制锁)

import java.util.concurrent.locks.*;public class ProduceTest {    public static void main(String[] args) {        Resource r = new Resource();        Consumer c = new Consumer(r);        Producer p = new Producer(r);        Thread t1 = new Thread(c);        Thread t2 = new Thread(c);        Thread t3 = new Thread(p);        Thread t4 = new Thread(p);        t1.start();        t2.start();        t3.start();        t4.start();    }}class Consumer implements Runnable {    private Resource r;    public Consumer(Resource r) {        this.r = r;    }    @Override    public void run() {        while (true) {            r.out();        }    }}class Producer implements Runnable {    private Resource r;    public Producer(Resource r) {        this.r = r;    }    @Override    public void run() {        while (true) {            r.setResource("zhangsan");        }    }}class Resource {    private String name;    private int count = 0;    boolean flag = false;    private final Lock lock = new ReentrantLock();    private final Condition  p_cd = lock.newCondition();//多个Condition对象    private final Condition  c_cd = lock.newCondition();//可以查看api:java.util.concurrent.locks 包里面    public void setResource(String name) {        lock.lock();        try {            while (this.flag) {                //生产的锁                try {p_cd.await();} catch (InterruptedException e) {}            }            this.name = name + "--" + count++;            System.out.println(Thread.currentThread().getName()+ " ....生产者.... " + this.name );            this.flag = true;            //唤醒消费线程            c_cd.signal();          } finally {//解锁(必须做)            lock.unlock();        }    }    public void out() {        lock.lock();        try {            while (!this.flag) {                //消费的锁                try {c_cd.await();} catch (InterruptedException e) {}            }            System.out.println(Thread.currentThread().getName()+ " ............消费者........... " + this.name );            this.flag = false;            //唤醒生产的线程            p_cd.signal();          } finally {//解锁(必须做)            lock.unlock();        }    }}

创建线程的三种方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 使用ExecutorService、Callable、Future实现有返回结果的线程
      Callable和FutureTask实现多线程
public class CallableTest {    public static void main(String[] args) throws InterruptedException, ExecutionException {        Callable<String> c = new CallableDemo();        FutureTask<String> ft = new FutureTask<String>(c);        FutureTask<String> ft1 = new FutureTask<String>(c);        Thread t1 = new Thread(ft);        Thread t2 = new Thread(ft1);        t1.start();        t2.start();    }}class CallableDemo implements Callable<String> {    private int count = 1;    @Override    public String call() throws Exception {        System.out.println("CallableDemo count++ = " + (count++));        return null;    }}

    ExecutorService、Callable、Future线程池实现(有返回值的线程)
参考:FelixZh

public class CallableTest {//包都是java.util.concurrent下的    public static void main(String[] args) throws InterruptedException, ExecutionException {        ExecutorService es = Executors.newFixedThreadPool(5);        List<Future<String>> list = new ArrayList<Future<String>>();        for (int x=0; x<5; x++) {            Future<String> submit = es.submit(new CallableDemo());            list.add(submit);        }        es.shutdown();        for (Future<String> f : list) {            System.out.println(f.get().toString());        }    }}class CallableDemo implements Callable<String> {    private int count = 1;    @Override    public String call() throws Exception {        System.out.println("CallableDemo count++ = " + (count++));        return "CallableDemo count++ = " + (count++);    }}

还有守护线程,停止线程,线程优先级,yield和join方法等等。

原创粉丝点击