Java基础之线程(二)

在看了一些线程面试题后又来巩固基础,加深理解

1. 基本概念

  • 程序(Program)
    是为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
  • 进程(process)
    是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在喝消亡过程(生命周期)。
  • 线程(thread)
    (1)进程可进一步细化为线程,是一个程序内部的一条执行路径。
    (2)若一个进行同一时间并行执行多个线程,就是支持多线程的。
    (3)线程作为调度和执行的单位,每个线程拥有独立的执行栈和程序计数器。线程切换的开销小。
    一个进程中的多个线程共享相同的内存单元。
  • 并行与并发
    并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事情。
    并发:一个CPU(采用时间片)同时执行多个任务。

2. 创建多线程的三种方式

2.1 继承Thread类,重写run()方法

在这里插入图片描述
在这里插入图片描述

2.2 实现Runnable接口

在这里插入图片描述
方式一与方式二两种方式的对比:
开发中优先选择:实现Runnable接口的方式
原因:
(1)实现的方式没有类的单继承的局限性
(2) 实现的方式更适合来处理多个线程共享数据的情况

2.3 实现Callable接口(JDK1.5)
Callable接口实际是属于Executor框架中的功能类,Callable结构与Runnable接口的功能类似,但提供了比Runnable更强大的功能,主要体现在如下三点:
    (1)Callable在任务结束后可以提供一个返回值,Runnable无法提供该功能。
    (2)Callable中的call()方法可以跑出异常,而Runnable中的run()不能跑出异常。
  (  3)运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果,它提供能了检查计算是否完成的方法。由于线程输入异步计算模型,因此无法从别的线程中得到函数的返回值,在这种情况下,就可以使用Future来监控目标线程来调用call()方法的情况,当调用Future的get()方法以获取结果时,当前线程会阻塞,直到目标线程的call()方法结束返回结果。 
public class CallableAndFuture{
   //创建线程类
   public static class CallableTest implements Callable{
     public String call() throws Exception{
        return "Hello World!";
     }
   }
   public static void main(String[] args){
     ExecutorService threadPool = Executors.newSingleThreadExecutor();
     Future<String> future = threadPool.submit(new CallableTest());
     try{
          System.out.println("waiting thread to finish");
          System.out.println(future.get());
        }catch{Exception e}{
          e.printStackTrace
        }
   }
}

3. Thread类中常用的方法:

(1) start():启动当前线程,调用当前线程的run()方法。
(2) run():通常需要重新Thread类中的此方法,将创建的线程要执行的操作声明放在此方法中。
(3) currentThread():静态方法,返回执行当前代码的线程
(4) getName():获取当前线程的名字。
(5) setName():设置当前线程的名字
(6) yield():释放当前CPU的执行权。
(7) join():在线程a中调用线程b的jion(),此时线程a进入阻塞状态,知道线程b完全执行完一行a才结束阻塞状态。
(8) stop():已过时。当执行此方法时,强制结束当前线程。
(9) sleep(long millitime) :让当前线程“睡眠”指定的毫秒,在该时间内,当前线程时阻塞状态。
(10) isAlive():判断当前线程是否存活。

4. 线程的通信

详细参考连接
(1) wait():
(2)notify():
(3)notifyAll():
这三个方法定义在Object类中。

5. 线程的分类

守护线程和用户线程
在这里插入图片描述

6. 线程的生命周期

在这里插入图片描述

7. 线程的安全问题

(1)多个线程执行的不确定性引起执行结果的不稳定
案例:设置3个线程卖票,票数为:100张
《情景一》

public class Window1 implements Runnable{
    private int tickets = 100;
    @Override
    public void run() {
        while (true){
            if(tickets > 0 ){
                System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+tickets);
                tickets --;
            }else {
                break;
            }
        }

    }
}

public class Window1Test {
    public static void main(String[] args) {
        Window1 task = new Window1();
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        Thread t3 = new Thread(task);
        t1.setName("窗口1"); 
        t2.setName("窗口2"); 
        t3.setName("窗口3"); 
        t1.start();
       
        t2.start();
        
        t3.start();

    }
}

此时:会发现有多张票号为100的票。
《情景二》
卖票的时候让线程休眠100毫秒:

public class Window1 implements Runnable{
    private int tickets = 100;
    @Override
    public void run() {
        while (true){
            if(tickets > 0 ){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+tickets);
                tickets --;
            }else {
                break;
            }
        }

    }
}

此时:会有 票号为 :-1 的票出现
在这里插入图片描述
BUT:
在这里插入图片描述
在这里插入图片描述

8. 线程的安全问题如何解决?同步机制

当一个线程a在操作ticket的时候,其它线程不能参与进来,直到线程a操作完ticket,其它线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
在java中,我们通过同步机制来解决线程安全问题
方式一:同步代码块:synchronized( 同步监视器){需要被同步的代码}

  1. 什么叫需要被同步的代码:操作共享数据的代码。
  2. 什么叫共享数据:多个线程共同操作的变量
  3. 同步监视器:锁,任何一个类的对象,都可以称作锁(要求多个线程必须共用同一把锁)
public class Window1 implements Runnable{
    private int tickets = 100;
    @Override
    public  void run() {
        while (true){
            synchronized (this){
                if(tickets > 0 ){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+tickets);
                    tickets --;
                }else {
                    break;
                }
            }

        }

    }
}

方式二:同步方法:
此时我们要注意,同步代码块不能被synchronized“包裹”太多,这样会降低代码运行效率。

9. 线程安全的单例模式之懒汉模式

使用同步机制将单例模式中的懒汉式改写为线程安全的
(1)好处:懒汉模式中单例是在需要的时候才去创建的,如果单例已经创建,再次调用获取接口将不会重新创建新的对象,而是直接返回之前创建的对象。
(2)适用于:如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建,这个时候使用懒汉模式就是一个不错的选择。
(3)缺点:但是这里的懒汉模式并没有考虑线程安全问题,在多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,因此需要加锁解决线程同步问题。

//创建多个线程,各自去调run()方法,在各自的run()方法中,又各自调了getInstance()方法
//假设有2个线程要调用getInstance()方法, 第一个线程首次调用是null,进入之后如果遇到线程阻塞情况,此时另一个线程进来了,遇到instance也是null,所以会导致 instance=new Bank();
//被先后执行2次初始化。
class Bank{
    private Bank(){}
    private static Bank instance = null;
    public static synchronized Bank  getInstance(){//类本身也充当一个对象,静态方法绑定在类上面
        if(instance == null){
            instance=new Bank();
        }
        return instance;
    }
}


在这里插入图片描述
但是这个方式效率稍差。因为其实除了最前面的线程(不知道谁会抢到这个线程,第一个?第二个?),后面的大家都在排队等待,此时不需要进入同步代码块等待了,直接取走用就好了。也就是立一个牌子,告诉别人已经有实例了。
在这里插入图片描述

10. 线程的死锁问题

在这里插入图片描述

在这里插入图片描述

public class ThreadDeadLock {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        //线程1
        new Thread(){
            @Override
            public void run() {
                //锁1
                synchronized (s1){
                    s1.append("a");
                    s2.append("1");
                    //演示阻塞,此时该线程在等着拿 s2,大家互相僵持
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //锁2
                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");
                        System.out.println("s1====>"+s1);
                        System.out.println("s2====>"+s2);
                    }
                }

            }
        }.start();
        //线程2
        new Thread((new Runnable() {
            @Override
            public void run() {

                synchronized (s2){
                    s1.append("c");
                    s2.append("3");
                    //演示阻塞,此时该线程在等着拿 s1,大家互相僵持
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");
                        System.out.println("s1--->"+s1);
                        System.out.println("s2---"+s2);
                    }
                }

            }
        })).start();
    }
}

看起来比较“隐蔽”的代码案例:

import javax.swing.*;

class A {
    public synchronized void foo(B b){//同步监视器:A类的对象 a
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入了A实例的foo方法");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用B实例的last方法");
        b.last();
    }
    public synchronized void last(){//同步监视器:A类的对象 a
        System.out.println("进入了A类的last方法內部");
    }
}

class B{
    public synchronized void bar(A a){//同步监视器:b
        System.out.println("当前线程名:"+ Thread.currentThread().getName()+"进入了B实例的bar方法");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用A实例的last方法");
        a.last();
    }
    public synchronized void last(){
        System.out.println("进入了B类的last方法內部");
    }
}
public class DeadLock implements Runnable{
    A a = new A();
    B b = new B();
    public void init(){
        Thread.currentThread().setName("主线程");
        a.foo(b);
        System.out.println("进入了主线程之后");
    }

    @Override
    public void run() {
        Thread.currentThread().setName("副线程");
        b.bar(a);
        System.out.println("进入了副线程之后");

    }

    public static void main(String[] args) {
        DeadLock d1 = new DeadLock();
        new Thread((d1)).start();
        d1.init();
    }
}

主线程:要先获得A类对象a(同步监视器),再获得B类对象b(同步监视器)。
分线程:要先获得B类对象b(同步监视器),再获得A类对象a(同步监视器)。
他们两个正好相反。

11. 解决线程安全的另一种方式:Lock(锁)

JDK 5.0 新增
java.util.concurrent.locks.Lock 接口:
ReentrantLock类实现了Lock接口
在这里插入图片描述

import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
    public static void main(String[] args) {
        Windows w= new Windows();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }

}

class Windows implements Runnable{
    private int tickets = 100;
    // 1. 实例化 ReentrantLock
    ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
           try {
               // 2.调用锁定方法 lock()
               lock.lock();
               if(tickets > 0){
                   try {
                       Thread.sleep(100);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName()+"线程售票,票号为:"+tickets);
                   tickets --;
               }else {
                   break;
               }
           }finally {
               //3. 调用解锁方法 unlock()
               lock.unlock();

           }
        }
    }
}

在这里插入图片描述

12. 线程练习题

在这里插入图片描述

版本一: 《自己瞎琢磨写的》
没有线程安全机制的时候:

import java.util.concurrent.locks.ReentrantLock;

public class AccountTest {
    public static void main(String[] args) {
        Account a = new Account();
        Thread t1 = new Thread(a);
        Thread t2 = new Thread(a);

        t1.setName("用户1");
        t2.setName("用户2");

        t1.start();
        t2.start();


    }
}

class Account implements Runnable{
    private double money = 0;
//    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        for(int i = 0; i<3;i++){
            try {
//                lock.lock();
                Thread.sleep(100);
                save();
                System.out.println(Thread.currentThread().getName()+"向里面存钱==》"+"当前余额"+money);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
//                lock.unlock();
            }
        }
    }
    public void save(){
        money += 1000;
    }
}

打印结果:
在这里插入图片描述

加入线程安全机制后:

import java.util.concurrent.locks.ReentrantLock;

public class AccountTest {
    public static void main(String[] args) {
        Account a = new Account();
        Thread t1 = new Thread(a);
        Thread t2 = new Thread(a);

        t1.setName("用户1");
        t2.setName("用户2");

        t1.start();
        t2.start();


    }
}

class Account implements Runnable{
    private double money = 0;
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        for(int i = 0; i<3;i++){
            try {
                lock.lock();
                Thread.sleep(100);
                save();
                System.out.println(Thread.currentThread().getName()+"向里面存钱==》"+"当前余额"+money);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    public void save(){
        money += 1000;
    }
}

在这里插入图片描述
版本二:正式答案版

public class AccountTest01 {
    public static void main(String[] args) {
        Account1 acct = new Account1(0);
        Customer c1 = new Customer(acct);
        Customer c2 = new Customer(acct);

        c1.setName("储户1");
        c2.setName("储户2");

        c1.start();
        c2.start();
    }
}

class Account1{
    private double balance;
    public Account1(double balance){
        this.balance =  balance;
    }
    public synchronized void deposit(double amt){
        this.balance += amt;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:" + this.balance);
    }
}

class Customer extends Thread{
    private Account1 acct;
    public Customer(Account1 acct){
        this.acct = acct;
     }

    @Override
    public void run() {
        for(int i = 0;i<3;i++){
            acct.deposit(1000);
        }

    }
}

13. 线程的通信

在这里插入图片描述

wait()、notify()、notifyAll()
定义在:java.lang.Object类中
只能出现在同步代码块或者同步方法中。
案例:线程1和线程2 交替打印1-100

wait()会释放锁,而sleep()不会.
当一个线程触发notify()的时候,尽管它将其他线程唤醒了,但是此时它已经拿到锁了,因此再有其他线程进入的时候也需要等待,直到执行 wait()的时候,锁才会被释放。
在这里插入图片描述

14. JDK1.5 新增线程的创建方式

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值