java基础(三):多线程

本文深入探讨了多线程的基础概念,包括进程与线程的区别、创建线程的方法及其实现方式的选择,并详细讲解了多线程同步机制、死锁问题及线程间的通讯方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

  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卖票啦  10
Thread-1 else卖票啦  9
Thread-0 if卖票啦  8
Thread-1 else卖票啦  7
Thread-0 if卖票啦  6
Thread-1 else卖票啦  5
Thread-0 if卖票啦  4
Thread-1 else卖票啦  3
Thread-0 if卖票啦  2
Thread-1 else卖票啦  1
Thread-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卖票啦  10
Thread-0 if卖票啦  9
Thread-0 if卖票啦  8
Thread-0 if卖票啦  7
Thread-0 if卖票啦  6
Thread-0 if卖票啦  5
Thread-0 if卖票啦  4
Thread-1 else卖票啦  3
Thread-1 else卖票啦  2
Thread-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卖票啦  10
Thread-1 else卖票啦  9
Thread-0 if卖票啦  8
Thread-1 else卖票啦  7
Thread-0 if卖票啦  6
Thread-1 else卖票啦  5
Thread-0 if卖票啦  4
Thread-1 else卖票啦  3
Thread-0 if卖票啦  2
Thread-1 else卖票啦  1
Thread-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卖票啦  10
Thread-0 if卖票啦  9
Thread-0 if卖票啦  8
Thread-0 if卖票啦  7
Thread-0 if卖票啦  6
Thread-1 else卖票啦  5
Thread-1 else卖票啦  4
Thread-1 else卖票啦  3
Thread-1 else卖票啦  2
Thread-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 lockb
if 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 ... women
divid ... 男
张三 ... 男
divid ... women
张三 ... 男
张三 ... women
divid ... 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 ... women
divid ... women
divid ... women
divid ... women
divid ... women
divid ... women
divid ... 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方法等等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值