JavaSE进阶 第十三章 多线程

1.进程和线程

  • 进程是一个应用程序,一个进程可以启动多个线程
  • 线程是一个进程中的执行单元/执行场景
  • JVM是一个进程,在java程序中至少有两个线程并发,一个主线程调用main方法,一个垃圾回收线程
  • 进程间的内存不共享,线程间堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈(多线程并发)
  • Java的多线程机制,目的是为了提高程序的处理效率
  • 单核CPU无法实现真正的多线程并发(线程间执行互不干扰),但由于CPU处理速度快,可以在多个线程间频繁切换,给人感觉是并发的
    在这里插入图片描述

2.实现线程

2.1继承Thread类

编写一个类,继承java.lang.Thread,重写run方法。创建分支对象,执行start

public class MyThread extends Thread {
    public void run() {
        //这段程序运行在分支线程中
        for (int i = 0; i < 1000; i++) {System.out.println("分支线程---->"+i);}
    }
}
public class ThreadTest02 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();//创建分支对象
        //启动线程,在JVM中开启一个新的栈空间,开出来后方法结束
        //启动成功的线程会自动调用run方法,run方法在分支栈的栈底部,与main平级
        myThread.start();
        //这段程序运行在主线程中
        for (int i = 0; i < 1000; i++) {System.out.println("主线程---->"+i);}
    }
}

在这里插入图片描述

2.2实现Runnable接口

编写一个类,实现java.lang.Runnavle接口,实现run方法。创建线程对象,启动线程

public class MyRunnable implements Runnable {
    public void run() {
        for (int i = 0; i < 1000; i++) {System.out.println("分支线程---->"+i);}
    }
}
ublic class ThreadTest04 {
    public static void main(String[] args) {
        //创建一个可运行对象
        MyRunnable myRunnable = new MyRunnable();
        //将可运行的对象封装成一个线程对象
        Thread thread = new Thread(myRunnable);
        //Thread thread1 = new Thread(new MyRunnable());
        thread.start();
        for (int i = 0; i < 1000; i++) {System.out.println("主线程---->" + i);}
    }
}

使用匿名内部类
实际上还是第二种方法

public class ThreadTest05 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {System.out.println("分支线程---->"+i);}
            }
        });
        t.start();
        for (int i = 0; i < 1000; i++) { System.out.println("主线程---->" + i);}
    }
}

2.3实现Callable接口

JDK8新特性,优点:该方法实现的线程可以获取线程的返回值
缺点:效率低,获取t线程执行效果,当前线程受阻

public class ThreadTest09 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    	//创建一个未来任务对象,参数为一个Callable接口实现类
        FutureTask task = new FutureTask(new Callable() {
            public Object call() throws Exception {//call()相当于run,执行之后可能会有一个执行结果
                System.out.println("call method begin");
                Thread.sleep(1000 * 10);
                System.out.println("call method end");
                int a = 100;
                int b = 200;
                return a + b;
            }
        });
        Thread t = new Thread(task);
        t.start();
        Object object = task.get();//get()方法获取放回结果
        System.out.println("执行结果是" + object);
        System.out.println("HelloWorld");
    }
}

3.线程的生命周期

  • 新建状态
  • 就绪状态
  • 运行状态
  • 阻塞状态
  • 死亡状态
    在这里插入图片描述
    在这里插入图片描述

4.线程的常用方法

  • 获取和修改线程的名字
public class ThreadTest06 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable2());
        //t.setName("tttt");设置线程的名字
        System.out.println(t.getName());//Thread-0
        Thread t2 = new Thread(new MyRunnable2());
        System.out.println(t2.getName());//Thread-1
    }
}
  • 获取当前线程对象
public class MyRunnable2 implements Runnable {
    public void run() {
        for (int i = 0; i < 1000; i++) {
            Thread currentThread = Thread.currentThread();
            System.out.println(currentThread.getName()+"---->"+i);
        }
    }
}
  • sleep方法
    static void sleep(Long mills)
    静态方法,参数是毫秒Thread.sleep(1000);
    作用:让当前对象进入阻塞状态(休眠),放弃占有CPU时间片,让给其他线程使用。
    可以做到:间隔特定时间,去执行一段特定的代码(每隔多久执行一次)
public class ThreadTest07 {
    public static void main(String[] args) {
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("HelloWorld");
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"---->"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

sleep的面试题

public class ThreadTest07 {
    public static void main(String[] args) {
    	Thread t = new Thread(new MyRunnable2());
    	t.start();
	  	 try {
	              t.sleep(1000 * 5);//线程t不会休眠,sleep是静态方法,会让当前main线程休眠
	          } catch (InterruptedException e) {
	              e.printStackTrace();
	          }
    }
}
  • 终止程序的休眠
public class MyRunnable2 implements Runnable {
    public void run() {
        System.out.println(Thread.currentThread().getName() + "---->begin");
        //run()方法中的异常不能throws,因为run方法在父类中没有抛出任何异常
        //子类不允许比父类抛出更多的异常,只能try...catch
        try {
            Thread.sleep(1000 * 60 * 60);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "---->end");
    }
}
public class ThreadTest06 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable2());
        t.start();
        try {
            Thread.sleep(1000 * 5);//5秒后,希望他、线程醒来
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();//终止线程的休眠,依靠java的异常处理机制
    }
}
  • 强制终止线程的执行(已过时,不建议使用)
    容易丢失数据
try {Thread.sleep(1000 * 5);} catch (InterruptedException e) {e.printStackTrace();}
//5秒后强制终止t线程
t.stop();
  • 合理的终止一个线程的执行
public class MyRunnable3 implements Runnable {
    boolean run = true;
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (run) {
                System.out.println(Thread.currentThread().getName() + "---->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                //终止当前程序
                return;
            }
        }
    }
}
public class ThreadTest06 {
    public static void main(String[] args) {
        MyRunnable3 r = new MyRunnable3();
        Thread t = new Thread(r);
        t.start();
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //模拟5秒后终止线程,将rn修改为false,t线程就结束了
        r.run = false;
    }
}

5.线程调度

5.1线程调度模型

  • 抢占式调度模型:那个线程的优先级比较高,抢到的CPU时间片的概率就高一些。Java采用这种模型
  • 均分式调度模型:平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样

5.2线程调度相关的方法

  • 获取与设置线程优先级
    void setPriority(int newPriority)
    int getPriority()
    最低优先级1,默认5,最高10,优先级比较高的获取CPU时间片可能会多一些
public class ThreadTest07 {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getPriority());
        Thread.currentThread().setPriority(10);
    }
}
  • 让位方法
    static void yield()
    暂停正在执行的线程对象,执行其他线程
    yield()不是阻塞方法,让当前线程从运行状态回到就绪状态(回到就绪后,可能还会再次抢到)
public class MyRunnable4 implements Runnable {
    public void run() {
        for (int i = 0; i < 1000; i++) {
            if(i%100 == 0) Thread.yield();//当前线程暂停,让给主线程
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}
  • 合并线程
    t.join();
    当前线程进入阻塞,让t线程执行,知道t线程结束,当前线程才可以执行
public class ThreadTest07 {
    public static void main(String[] args) {
        System.out.println("main begin");
        Thread t = new Thread(new MyRunnable6());
        t.start();
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main over");
    }
}
class MyRunnable6 implements Runnable {
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

6.线程安全

在以后的开发中,项目运行在服务器中,而服务器已经将线程定义了,线程对象的创建、启动等都不需要编写。需要注意程序再多线程并发环境下运行,数据是否安全
数组在多线程情况下存在安全问题的三个条件:

  • 多线程并发
  • 有数据共享
  • 共享数据有修改行为

6.1怎么解决线程安全问题

  • 第一种方案:尽量使用局部变量代替“实例变量和静态变量”
  • 第二种方案:如果必须是实例变量,可以考虑创建多个对象,这样实例变量的内存就不共享了
  • 第三种方案:若果不能使用局部变量,也不能创建多个对象,只能选择synchronized,线程同步机制。会让程序的执行效率降低,用户体验不好,系统的用户吞吐量降低,在不得已的情况下再选择线程同步机制
    线程同步机制:线程排队执行(不能并发),会牺牲一部分效率,数据安全第一位
    两个专业术语:
  • 异步编程模型:线程t1和t2各自执行各自的,谁也不需要等谁,多线程并发,效率较高
  • 同步编程模型:线程t1和t2之间发生了等待关系,需要等另一个线程执行完,自己才能执行,排队,效率较低

6.2多线程取款示例

取款时,线程需排队,不能并发
线程同步机制的语法是

sychronized(){
	//线程同步代码块
}

()中填写需要排队的线程共享的对象
这里的共享对象为账户对象,填写this

public class Account {
    private String actno;
    private double balance;
    public Account() {}
    public Account(String actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }
    public String getActno() {return actno;}
    public void setActno(String actno) {this.actno = actno;}
    public double getBalance() {return balance;}
    public void setBalance(double balance) {this.balance = balance;}
    public  void withdraw(double money) {
        synchronized (this) {
            try {
                Thread.sleep(1000 * 2);//模拟网络延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(this.getBalance() - money);
        }
    }
}
public class AccountThread extends Thread {
    private Account act;
    public AccountThread(Account act) {this.act = act;}
    public void run() {
        double money = 5000;
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName() + "对" + act.getActno() + "取款" + money + ",余额为" + act.getBalance());
    }
}
public class Test2 {
    public static void main(String[] args) {
        Account act2 = new Account("act-002",10000);
        AccountThread t1 = new AccountThread(act2);
        AccountThread t2 = new AccountThread(act2);
        t1.start();
        t2.start();
    }
}

6.3共享对象的标记“锁”

java语言中任何一个对象都有一把锁,即为标记。
在取款的例子中,t1与t2并发,坑定有先后,假设t1先执行,遇到了sychronized,自动找()中共享对象的对象锁,找到后占有,然后执行同步代码块中的程序,同步代码快中代码执行结束,锁才会释放。t1占有锁后,t2也遇到sychronized,如果共享对象的锁被t1占有,t2只能等待t1把同步代码快执行结束,t2占有锁后,才能进入同步代码块执行
注意:共享对象需要是你需要排队执行的这些线程对象所共享的

//Object obj2 = new Object2();
//synchronized (obj2) { 定义的局部变量,不是共享的
//synchronized (obj) { 可以,act的实例变量obj对象只有一个,可以共享
//synchronized ("abc") { 在方法区的字符串常量池中,导致所有的栈都排队
//synchronized (null) { 空指针异常
synchronized (this) {
	try {
   	 Thread.sleep(1000 * 2);//模拟网络延迟
	} catch (InterruptedException e) {
    	e.printStackTrace();
	}
	this.setBalance(this.getBalance() - money);
}

6.4java对象的线程安全问题

  • 实例变量:在堆中,堆只有一个
  • 静态变量:在方法区中,方法区中有一个
    堆和方法区都是多线程共享的,可能存在线程安全问题
  • 局部变量:在栈中,不共享,不会有线程安全问题
    常量也不存在线程安全问题,因为它不可被修改
非线程安全线程安全
StringBuilderStringBuffer
ArrayListVector
HashMap HashSetHashtable

6.5synchronized的用法

  • 同步代码块:灵活
  • 在实例方法上使用synchronized,表示共享对象一定是this,同步代码快是整个方法,这种方式不灵活,因为整个方法体都需要同步,可能回无故扩大同步范围,导致执行效率低。但是若果共享的对象就是this,并且需要同步整个方法体,建议使用这种方式
    public synchronized void withdraw(double miney){}
  • 在静态方法上使用synchronized,表示找类锁,类锁中有一把,100个对象100把锁

6.6synchronized面试题

(1)doOther方法的执行需要等待doSome方法结束吗
不需要,doOther方法没有被synchronized修饰,t2直接执行

public class Exam01 {
    public static void main(String[] args) throws InterruptedException {
        MyClass mc = new MyClass();
        Thread t1 = new MyThread(mc);
        Thread t2 = new MyThread(mc);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
}
class MyThread extends Thread {
    private MyClass mc;
    public MyThread(MyClass mc) {this.mc = mc;}
    public void run() {
        if (Thread.currentThread().getName().equals("t1")) {
            mc.doSome();}
        if (Thread.currentThread().getName().equals("t2")) {
            mc.doOther();}
    }
}
class MyClass {
    public synchronized void doSome() {
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public void doOther() {
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

(2)doOther方法的执行需要等待doSome方法结束吗
需要,一个mc对象一把锁,是排它锁,t2执行时需要等待t1执行完毕doSome

class MyClass {
    public synchronized void doSome() {
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized void doOther() {
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

(3)doOther方法的执行需要等待doSome方法结束吗
不需要,MyClass对象是两个,两把锁

public class Exam02 {
    public static void main(String[] args) throws InterruptedException {
        MyClass mc1 = new MyClass();
        MyClass mc2 = new MyClass();
        Thread t1 = new MyThread(mc1);
        Thread t2 = new MyThread(mc2);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
}

(4)doOther方法的执行需要等待doSome方法结束吗
需要,虽然new了两个MyClass对象,但doSome是静态方法找类锁,MyClass只有一把锁

class  MyClass {
    public synchronized static void doSome() {
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized static void doOther() {
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

7.死锁

会写死锁,这样在开发中才会注意到这个事
synchronized在开发中最好不要嵌套使用,一不小心会导致死锁现象发生
在这里插入图片描述

public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new MyThread1(o1, o2);
        Thread t2 = new MyThread2(o1, o2);
        //o1和o2数据共享
        t1.start();
        t2.start();
    }
}
class MyThread1 extends Thread {
    Object o1;
    Object o2;
    public MyThread1(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run() {
        synchronized (o1) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2) {
            }
        }
    }
}
class MyThread2 extends Thread {
    Object o1;
    Object o2;
    public MyThread2(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run() {
        synchronized (o2) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1) {
            }
        }
    }
}

8.守护线程

java中线程可分为两大类:用户线程(例如主方法main线程)与守护线程(后台线程,代表性:GC)
守护线程的特点:一般是一个死循环,所有的用户线程结束,守护线程自动结束
守护线程的使用:实现功能每天零点自动备份,将定时器设置为守护线程,所有用户想吃结束,守护线程自动退出
守护线程的实现:

public class ThreadTest08 {
    public static void main(String[] args) {
        Thread t = new BakeDateThread();
        t.setName("备份数据的线程");
        t.setDaemon(true);//启动线程前,设置为守护线程
        t.start();
        //主线程
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class BakeDateThread extends Thread {
    public void run() {
        int i = 0;
        while (true) {
            System.out.println(Thread.currentThread().getName() + "--->" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

9.定时器

定时器的作用:间隔特定的时间,执行特定的程序
java中可以采多种方式实现这种功能

  • sleep方法,设置睡眠时间(最原始)
  • java类库中java.uitl.Timer(实际开发中少用,有许多更高级的框架支持定时任务)
  • 较多使用Spring框架中的springTask框架,这个管家只要进行简单的配置,就可以完成定时器的任务
public class TimerTest {
    public static void main(String[] args) throws ParseException {
        Timer timer = new Timer();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = sdf.parse("2020-08-14 17:00:00");
        timer.schedule(new LogTimerTask(), firstTime, 1000 * 10);
    }
}
class LogTimerTask extends TimerTask {
    public void run() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String strTime = sdf.format(new Date());
        System.out.println(strTime + ":完成了数据备份");
    }
}

10.wait和notify

10.1注意点

  • wait和notify不是形成对象的方法,不能通过线程对象调用,是Object类自带的
  • wait方法的作用:让o对象上活动的线程进入等待状态,直到被再次唤醒
  • notify方法的作用:唤醒正在o对象上等待的线程;notifyAll的作用,唤醒o对象上所有处于等待状态的线程
    在这里插入图片描述

10.2生产者和消费者模式

在这里插入图片描述
模拟这样的场景:仓库采用List集合,只能存储一个元素,实现生产一个修复一个,集合中最多存储一个元素

public class ThreadTest10 {
    public static void main(String[] args) {
        List list = new ArrayList();
        Thread t1 = new Thread(new Produce(list));
        Thread t2 = new Thread(new Consumer(list));
        t1.setName("生产者线程");
        t2.setName("消费者线程");
        t1.start();
        t2.start();
    }
}
class Produce implements Runnable {
    private List list;
    public Produce(List list) {this.list = list;}
    public void run() {
        while (true) { //使用死循环模拟一直生产
            synchronized (list) {
                if (list.size() > 0) {
                    try {
                        list.wait();//当前线程进入等待,释放之前占有的list集合的锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                list.notifyAll();//唤醒所有等待的线程,可能被当前线程抢到,但有元素时进入等待
            }
        }
    }
}
class Consumer implements Runnable {
    private List list;
    public Consumer(List list) {this.list = list;}
    public void run() {
        while (true) {
            synchronized (list) {
                if (list.size() == 0) {
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                list.notifyAll();
            }
        }
    }
}

传送门

上一篇:JavaSE进阶 第十二章 IO流
下一篇:JavaSE进阶 第十四章 反射机制

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值