多线程基础

多线程

一、线程的概述

1.1什么是进程?什么是线程?

​ 进程是一个应用程序(一个进程是一个软件)。

​ 线程是个进程中的执行场景/执行单元。

​ 一个进程可以启动多个线程。

1.2 对于java程序来说,当在DOS命令窗口中输入:

​ java HelloWorld回车之后。会先启动JVM,而JVM就是一个进程。

​ JVM再启动一个主线程调用main方法。

​ 同时再启动一个垃圾回收线程负责看护,回收垃圾。

​ 最起码,现在java程序中至少有两个线程并发,一个是垃圾回收线程,一个 是执行main方法的主线程。

1.3 进程和线程的关系?举个例子

​ 阿里巴巴:进程

​ 马云:阿里巴巴的一个线程

​ 童文红:阿里巴巴的一个线程

​ 京东:进程

​ 刘强东:京东的一个线程

​ 妹妹:京东的一个线程

​ 进程可以看作是现实生活当中的公司。线程可以看作是公司当中的某个员工。

​ 注意:

​ 进程A和进程B的内存独立不共享(阿里巴巴和京东资源不会共享!)

​ 魔兽游戏是一个进程,酷狗音乐是一个进程,这两个进程是独立的,

​ 不共享资源。

​ 线程A和线程B呢?

​ 在java语言中:线程A和线程B,堆内存和方法区内存共享。但是栈内 存独立,一个线程一个栈。

​ 假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

火车站,可以看作是一个进程。

​ 火车站的每一个售票窗口可以看作是一个线程。我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。所以多线程并发可以提高效率。

java中之所以有多线程机制,目的就是为了提高程序的处理效率。

1.4 思考问题

使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。

​ main方法结束只是主线程结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈。

1.5 分析:对于单核的cpu来说,真的可以做到真正的多线程并发吗?

对于多核的cpu电脑来说,真正的多线程并发是没问题的。

4核cpu表示同一时间点上,可以真正的有4个进程并发执行。

什么是真正的多线程并发?

t1线程执行t1的。t2线程执行t2的。t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。(实际上也就是并行)

单核的cpu表示只有一个大脑:

不能真正的做到多线程并发,但是可以做到给人一种"多线程并发"的感觉。对于单核的cpu来说,在某一个时间点上实际上只能处理一件事,但是由于cpu的处理速度极快,多个线程之间频繁的切换执行,给人的感觉是:多个事情同时在做!!!

单核CPU:只能并发

多核CPU:并行+并发

二、实现线程的三种方式

2.1 继承Thread类

​ 步骤:

​ 1.编写一个类,直接继承java.lang.Thread,重写run()方法

​ 2.调用start()方法启动线程

package com.bjpowernode.java.thread;
/*
    实现线程的第一种方式:
        编写一个类,直接继承java.lang.Thread,重写run方法
    怎么创建线程对象?new就行了
    怎么启动线程呢?调用start()方法
 */
public class ThreadTest02 {
    public static void main(String[] args) {

        //这里是main方法,这里的代码属于主线程,在主栈中运行。
        //新建一个分支线程对象
        MyThread myThread = new MyThread();
        //启动线程
        //start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
        //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了,线程就启动成功了
        //启动工程的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)
        //run方法在分支栈的栈底部,main方法在主站的栈底部。run和main是平级的。
        myThread.start();
        //这里的代码还是运行在主线程中。
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程===》" + i);
        }
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        //编写程序,这段程序运行在分支线程中(分支栈)
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程===》" + i);
        }
    }
}

2.2 实现Runnale接口

​ 步骤:

​ 1.编写一个类,实现java.lang.Runnable接口,实现run()方法。

​ 2.创建1中的类,将其作为参数传到Thread类的构造器中,调用Thread类中的start()方法启动线程。

注意:实现Runnable接口的方式比较常用,因为一个类实现了接口,他还可以继承其他的类,更灵活。

package com.bjpowernode.java.thread;

/*
    实现线程的第二种方式:编写一个类实现java.lang.Runnble接口
 */
public class TreadTest03 {
    public static void main(String[] args) {
        //创建一个可运行的对象
        MyRunnable r = new MyRunnable();
        //将可运行的对象封装成一个线程对象
        //Thread t = new Thread(r);
        //合并代码
        Thread t = new Thread(new MyRunnable());
        //启动线程
        t.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程===》" + i);
        }
    }
}

//这并不是一个线程类,是一个可运行的类。它还不是一个线程
class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("分支线程===》" + i);
        }
    }
}

2.3 采用匿名内部类的方式实现多线程

package com.bjpowernode.java.thread;

/*
    采用匿名内部类实现多线程
 */
public class ThreadTest04 {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("分支线程:===》" + i);
                }
            }
        }).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程:===》" + i);
        }
    }
}

2.4 实现Callable接口(JDK8新特性)

实现Callable接口这种方式可以获取线程的返回值。
之前讲解的那两种方式是无法获取线程的返回值的,因为run()方法返回void

思考:	
	系统委派一个线程区执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么能拿到这个执行结果呢?
	使用第三种方式:实现Callable接口方式。
package com.bjpowernode.java.thread;
//JUC包下的,属于java的并发包,老JDK中没有这个包,新特性

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadTest15 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //第一步:创建一个“未来任务类对象”
        //参数非常重要,需要给一个Callable接口实现类对象
        FutureTask futureTask = new FutureTask(new Callable() {
            //call()方法就相当于run()方法。只不过这个有返回值
            @Override
            public Object call() throws Exception {
                //线程执行一个任务,执行之后可能会有一个执行结果
                //模拟执行
                System.out.println("call method begin");
                Thread.sleep(1000);
                System.out.println("call method end");
                int a = 100;
                int b = 200;
                return a + b;
            }
        });
        //第二步 创建线程对象
        Thread thread = new Thread(futureTask);
        //启动线程
        thread.start();

        //这里是main方法,这是在主线程中。
        //在主线程中,怎么获取t线程的返回结果。
        //通过FutureTask类的get()方法就可以获取线程的返回结果
        //get()方法的执行会导致当前线程的阻塞
        Object o = futureTask.get();
        System.out.println(o);
        //main方法这里的程序要想执行必须等待get()方法的结束
        //而get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果
        //另一个线程执行是需要时间的

    }
}

三、线程的生命周期

在这里插入图片描述

在这里插入图片描述

四、获取线程的名字

4.1获取线程对象的名字

​ String name = 线程对象.getName();

4.2修改线程对象的名字

​ 线程对象.setName(String name);

4.3默认线程的名字

​ Thread-0

​ Thread-1

​ Thread-2

​ Thread-3

五、获取当前线程

5.1 获取当前线程对象

​ Thread t = Thread.currentThread();

​ 注意:currentThread()是Thread类中的一个static方法,当方法出现在主线程中就获取到主线程,出现在分支线程中就获取到分支线程。(类似this)

5.2 获取当前相乘的名字

​ Thread t = Thread.currentThread();

​ String name = t.getName();

六、sleep()方法

6.1 static void sleep(long mills)

①静态方法:Thread.sleep(long millis)

②参数是毫秒

③作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有cpu时间片,让给其他线程使用。

​ (重点)

​ 这行代码出现在A线程中,A线程就会进入休眠。

​ 这行代码出现在B线程中,B线程就会进入休眠。

④Thread.sleep()方法,可以做到这种效果:

​ 间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。

6.2 面试题

package com.bjpowernode.java.thread;

public class ThreadTest07 {
    public static void main(String[] args) {
        //创建线程对象
        MyThread3 t = new MyThread3();
        t.setName("t");
        t.start();

        //调用sleep()方法
        try {
            //会让线程t进入休眠状态吗
            t.sleep(1000 * 5);//在执行的时候还是会转换成:Thread.sleep(1000*5);
                                    //这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5秒之后才会执行
        System.out.println("hello world");
    }
}

class MyThread3 extends Thread {
    @Override
    public void run() {

        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "===>" + i);
        }
    }
}

七、中止线程的睡眠

​ 线程对象.interrupt():中断线程的睡眠(这种中断睡眠的方式依靠了java的异常处理机制)

package com.bjpowernode.java.thread;
/*
    sleep睡眠太久了,如果希望半道上醒来,应该怎么办?也就是说怎么叫醒一个正在睡眠的线程
    注意:这个不是终端线程的执行,是终止线程的睡眠
 */
public class ThreadTest08 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable2());
        t.setName("t");
        t.start();

        //希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了)
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //终端t线程的睡眠(这种中断睡眠的方式依靠了java的异常处理机制)
        t.interrupt();
    }
}
class MyRunnable2 implements Runnable{
    //重点:run()当中的异常不能throws,只能try{}catch{}
    //因为run()方法在父类中没有抛出任何异常,子类不能比父类抛出更多的异常
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"==>begin");
        //睡眠一年
        try {
            Thread.sleep(1000*60*60*24*365);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"==>end");
    }
}

八、强行终止一个线程的执行

8.1 stop()方法

注意:这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了, 线程没有保存数据将会丢失。不建议使用

package com.bjpowernode.java.thread;

/*
    在java中怎么强行终止一个线程的执行
           这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了
           线程没有保存数据将会丢失。不建议使用
 */
public class ThreadTest09 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable3());
        t.setName("t");
        t.start();
        //模拟5秒
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        //5秒之后强行终止t线程
        t.stop();//已过时(不建议使用,因为此种方式容易造成数据丢失)
    }
}

class MyRunnable3 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "==>" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

8.2 合理的终止一个线程

package com.bjpowernode.java.thread;
public class ThreadTest10 {
    public static void main(String[] args) {
        MyThread4 myThread4 = new MyThread4();
        Thread thread = new Thread(myThread4);
        thread.start();
        //模拟5秒
        try {
            thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread4.run = false;
    }
}
class MyThread4 implements Runnable {
    boolean run = true;
    @Override
    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 就结束了,你在结束之前还有什么没保存的。
                //在这里可以保存
                //终止当前线程
                return;
            }
        }
    }
}

九、线程调度概述

9.1 常见的线程调度有哪些

​ 抢占式调度模型:

​ 哪个线程的优先级比较高,抢到cpu时间片的概率就高一些/多一些。java采用的就是抢占式调度模型。

​ 均分式调度模型:

​ 平均分配cpu时间片。每个线程占有的cpu时间片时间长度一样。平均分配,一切平等。有一些编程语言,线程调度模型采用的就是这种方式。

9.2 java中提供了哪些方法是和线程调度有关系的呢?

​ 实例方法:

​ void setPriority(int newPriority):设置线程的优先级

​ int getPriority():获取线程的优先级

​ 最低优先级:1

​ 默认优先级:5

​ 最高优先级:10

​ 优先级比较高的获取cpu时间片可能会多一些(但也不完全是,大概率是多的)

​ 静态方法:

​ static void yield():让位方法

​ 暂停当前正在执行的线程对象,并执行其他线程,yield()方法不是阻塞方法。让当前线程让位,让给其他线程使用。yield()方法的执行会让当前线程从"运行状态"回到"就绪状态"。

注意:在回到就绪之后,有可能还会再次抢到。

​ 实例方法:

​ void join():合并线程

class MyThread1 extends thread{
    public void doSome(){
        MyThread2 t = new MyThread2();
        //当前线程进入阻塞状态,t线程执行,直到t线程结束,当前线程才能继续执行。
        t.join();
    }
}
class MyThead2 extends Thread{
    
}

9.3 关于线程的优先级

package com.bjpowernode.java.thread;

import static java.lang.Thread.currentThread;

public class ThreadTest11 {
    public static void main(String[] args) {
        //设置当前线程的优先级是10
        currentThread().setPriority(Thread.MAX_PRIORITY);
        //获取当前线程的优先级
        System.out.println(currentThread().getName() + "线程的优先级是:" + currentThread().getPriority());
        MyRunnable4 myRunnable4 = new MyRunnable4();
        Thread thread = new Thread(myRunnable4);
        //thread.setPriority(Thread.MIN_PRIORITY);
        thread.start();
    }
}

class MyRunnable4 implements Runnable {
    @Override
    public void run() {
        //获取当前线程的优先级
        System.out.println(currentThread().getName() + "线程优先级是:" + currentThread().getPriority());
    }
}

9.4 线程让位 Thread.yield()

​ 让位:当前线程暂停回到就绪状态,让给其他线程。

​ 静态方法:Thread.yield()

package com.bjpowernode.java.thread;

public class ThreadTest12 {
    public static void main(String[] args) {
        //开启一个分支线程
        Thread thread = new Thread(new MyRunnable6());
        thread.setName("t");
        thread.start();
        //主线程
        for (int i = 1; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }

    }
}

class MyRunnable6 implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i < 10000; i++) {
            //每100个让位一次
            if(i%100==0){
                Thread.yield();//静态方法让位 当前线程暂停一下让给主线程
            }
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

9.5 线程合并 Thread.join()

Thread.join():静态方法 将当前线程合并到另一个线程中,另一个线程受阻,直到当前线程结束。(也可以理解为强行插队)

package com.bjpowernode.java.thread;

public class ThreadTest13 {
    public static void main(String[] args) {
        System.out.println("main begin");

        //开启一个分支线程
        Thread thread = new Thread(new MyRunnable7());
        thread.setName("t");
        thread.start();
        //合并线程
        try {
            thread.join();//t线程合并到当前线程中,当前线程受阻塞,t线程执行直到结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main over");
    }
}

class MyRunnable7 implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "==>" + i);
        }
    }
}

十、线程安全问题(synchronized)

10.1为什么这个是重点?

以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。

最重要的是:你要知道 ,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的(重点)

10.2 什么时候数据在多线程并发的环境下会存在安全问题?

在这里插入图片描述

三个条件(重点):

​ 条件1:多线程并发

​ 条件2:有共享数据

​ 条件3:共享数据有修改的行为。

​ 满足以上三个条件之后,就会存在线程安全问题。

10.3 怎么解决线程问题

当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?

​ 线程排队执行(不能并发)

​ 用排队执行解决线程安全问题。

​ 这种机制被称为:线程同步机制

专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。

线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事。

10.4 异步编程模型和同步编程模型

异步编程模型:

​ 线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是多线程并发(效率较高,但线程不安全)

异步就是并发

同步编程模型:

线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。

效率较低(但是线程安全)。

同步就是排队。

10.5 模拟线程安全问题

//银行账户类
package com.bjpowernode.java.threadsafe;

/*
    银行账户类:
 */
public class Account {
    private int actno;//账号
    private double balance;//余额

    public Account() {
    }

    public Account(int actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public int getActno() {
        return actno;
    }

    public void setActno(int actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    //取款的方法
    public void withdraw(double monkey){
        //取款之前的余额
        double before = this.getBalance();
        //取款之后的余额
        double after = before - monkey;

        //模拟一下网络延迟
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //更新余额
        this.setBalance(after);
    }

    @Override
    public String toString() {
        return "Account{" +
                "actno=" + actno +
                ", balance=" + balance +
                '}';
    }
}

//账户线程类,run()方法执行取款操作
package com.bjpowernode.java.threadsafe;

public class AccountThread extends Thread {
    //两个线程必须共享同一个账户对象
    private Account act;
    //通过构造方法传递过来账户对象

    public AccountThread(Account act) {
        this.act = act;
    }

    @Override
    public void run() {
        //run()方法的执行表示取款操作
        //假设取款5000
        double monkey = 5000;
        act.withdraw(monkey);
        System.out.println("账户"+act.getActno()+"取款成功,余额"+act.getBalance());
    }
}

//测试类
package com.bjpowernode.java.threadsafe;
public class Test {
    public static void main(String[] args) {
        //创建账户对象(只创建一个)
        Account account = new Account(110, 10000);
        //创建两个分支线程
        AccountThread t1 = new AccountThread(account);
        AccountThread t2 = new AccountThread(account);
        //给两个线程命名
        t1.setName("t1");
        t2.setName("t2");
        //启动两个线程
        t1.start();
        t2.start();
    }
}
结果:
账户110取款成功,余额5000.0
账户110取款成功,余额5000.0
出现了线程安全问题

10.6 使用同步代码块的方式解决线程安全

​ 语法:

​ synchronized(同步监视器){
​ //将需要同步的代码全部卸载代码块中

​ }

​ 注意:

​ synchronized后面的小括号中传的这个“数据”是相当关键的。这个数据必须是一个对象,这个对象必须是多线程共享的对象。才能达到多线程排队
​ ()中写什么?
​ 那要看你想让哪些线程同步。
​ 假设t1、t2 t3 t4 t5有五个线程,你只希望t1 t2 t3排队,t4 t5不排队怎么办?你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说是不共享的。

package com.bjpowernode.java.threadsafe2;

/*
    银行账户类:
        使用同步机制来解决线程安全问题
 */
public class Account {
    private int actno;//账号
    private double balance;//余额

    public Account() {
    }

    public Account(int actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public int getActno() {
        return actno;
    }

    public void setActno(int actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    //取款的方法
    public void withdraw(double monkey) {
        //一以下这几行代码必须是线程排队的,不能并发
        //一个线程把这里的代码全部执行结束之后,另一个线程才能进来
        /*
            线程同步机制的语法是:
            synchronized(){
                //线程同步代码块
            }
            synchronized后面的小括号中传的这个“数据”是相当关键的。
          这个数据必须是多线程共享的数据。才能达到多线程排队(重点)
            ()中写什么?
            那要看你想让哪些线程同步。
            假设t1、t2 t3 t4 t5有五个线程,你只希望t1 t2 t3排队,t4 t5不排队怎么办?
            你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说是不共享的。

            这里的共享对象是:账户对象。
            账户对象是共享的,那么this就是账户对象
            不一定是this,这里只要是多线程共享的那个对象就行。
         */
        synchronized (this) {
            double before = this.getBalance();
            double after = before - monkey;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }
    }

    @Override
    public String toString() {
        return "Account{" +
                "actno=" + actno +
                ", balance=" + balance +
                '}';
    }
}

10.7 对synchronized的理解

synchronized(同步监视器){}:
	1.同步监视器一般情况下,是需要使用同步的多个线程之间共享的对象。
	这样的话不同的同步线程之间是不需要等待的。使用各自的共享对象就可以进入同步代码块之中(建议使用)
	2.当然也可以是所有线程共享的对象。
		如果是这样的话,即使是不同的同步线程之间也是需要等待的,换而言之就是所有线程都需要等待。

10.8 哪些变量有线程安全问题?

java中有三大变量
	成员变量:在堆中
	静态变量:在方法区中
	局部变量:在栈中
	
	以上三大变量中:
		局部变量和常量永远都不会存在线程安全问题。
			因为局部变量不共享(一个线程一个栈)
		实例变量在堆中,堆只有一个。
		静态变量在方法区中,方法区只有一个。
		堆和方法区都是多线程共享的,所以可能存在线程安全问题。

10.9 同步方法(实例方法)

​ synchronized使用在实例方法上时,锁只能是this,不能是其他对象。

package com.bjpowernode.java.threadsafe3;


public class Account {
    private int actno;//账号
    private double balance;//余额

    public Account() {
    }

    public Account(int actno, double balance) {
        this.actno = actno;
        this.balance = balance;
    }

    public int getActno() {
        return actno;
    }

    public void setActno(int actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    //取款的方法
    /*
        在实例方法上可以使用synchronized吗?可以的
        synchronized出现在实例方法上,一定锁的是this
        没的挑。只能是this.不能是其他对象了。
        所以这种方式不灵活

        另外还有一个缺点:synchronized出现在实例方法上,
        表示整个方法体都需要同步,可能会无故的扩大同步的范围,
        导致程序执行效率的降低。所以这种方式不灵活

        synchronized使用在实例方法上有什么优点?
        代码写少了。节俭了
        如果共享的对象就是this,并且需要同步的代码块是整个方法体
        建议使用这种方式
     */
    public synchronized void withdraw(double money){

        double before = this.getBalance();
        double after = before - money;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setBalance(after);
    }

    @Override
    public String toString() {
        return "Account{" +
                "actno=" + actno +
                ", balance=" + balance +
                '}';
    }
}

10.10 面试题

package com.bjpowernode.java.exam;

//面试题:doOther()方法的执行需不需要等待doSome()方法的结束
//不需要,因为doOther()方法没有synchronized的,也就意味着不需要锁。
//就可以直接执行了
public class Exam01 {
    public static void main(String[] args) {
        //创建一个MyClass对象
        MyClass mc = new MyClass();
        //创建两个线程
        Thread t1 = new MyThread(mc);
        Thread t2 = new MyThread(mc);
        //给t1 t2线程设置名字
        t1.setName("t1");
        t2.setName("t2");
        //开启两个线程
        t1.start();
        try {
            Thread.sleep(1000);//这个线程的作用是保证t1线程先执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class MyThread extends Thread {
    private MyClass mc;

    public MyThread(MyClass mc) {
        this.mc = mc;
    }

    @Override
    public void run() {

        if(Thread.currentThread().getName().equals("t1"){
            mc.doSome();
        }
        if(Thread.currentThread().getName().equals("t2"){
            mc.doOther();
        }
    }
}

class MyClass {

    //synchronized出现在实例方法上,表示锁是this
    public synchronized void doSome() {
        System.out.println("doSome begin");
        //睡眠十秒
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");

    }

    public void doOther() {
        System.out.println("doOther begin");

        System.out.println("doOther over");
    }
}
package com.bjpowernode.java.exam2;

//面试题:doOther()方法的执行是否需要等待doSome()方法的结束
//需要;因为doSome()方法和doOther()都是synchronized方法
//使用的是同一个this监视器,一个方法占用锁资源,另一个方法
//就需要等待锁资源

public class Exam01 {
    public static void main(String[] args) {
        //创建一个MyClass对象
        MyClass mc = new MyClass();
        //创建两个线程
        Thread t1 = new MyThread(mc);
        Thread t2 = new MyThread(mc);
        //给t1 t2线程设置名字
        t1.setName("t1");
        t2.setName("t2");
        //开启两个线程
        t1.start();
        try {
            Thread.sleep(1000);//这个线程的作用是保证t1线程先执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class MyThread extends Thread {
    private MyClass mc;

    public MyThread(MyClass mc) {
        this.mc = mc;
    }

    @Override
    public void run() {

       if(Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
       if(Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}

class MyClass {

    //synchronized出现在实例方法上,表示锁是this
    public synchronized void doSome() {
        System.out.println("doSome begin");
        //睡眠十秒
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");

    }

    public synchronized void doOther() {
        System.out.println("doOther begin");

        System.out.println("doOther over");
    }
}
package com.bjpowernode.java.exam3;

//面试题:doOther()方法的执行是否需要等待doSome()方法的结束
/*
    不需要;因为现在MyClass 对象有两个是两把锁,调用方法时this实际上表示的是不同的对象,那么不同锁资源之间实际上是异步操作
 */

public class Exam01 {
    public static void main(String[] args) {
        //创建一个MyClass对象
        MyClass mc1 = new MyClass();
        MyClass mc2 = new MyClass();
        //创建两个线程
        Thread t1 = new MyThread(mc1);
        Thread t2 = new MyThread(mc2);
        //给t1 t2线程设置名字
        t1.setName("t1");
        t2.setName("t2");
        //开启两个线程
        t1.start();
        try {
            Thread.sleep(1000);//这个线程的作用是保证t1线程先执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class MyThread extends Thread {
    private MyClass mc;

    public MyThread(MyClass mc) {
        this.mc = mc;
    }

    @Override
    public void run() {

        if(Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if(Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}

class MyClass {

    //synchronized出现在实例方法上,表示锁是this
    public synchronized void doSome() {
        System.out.println("doSome begin");
        //睡眠十秒
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");

    }

    public synchronized void doOther() {
        System.out.println("doOther begin");

        System.out.println("doOther over");
    }
}
package com.bjpowernode.java.exam4;

//面试题:doOther()方法的执行是否需要等待doSome()方法的结束
/*
    需要;因为此时的doOther()和doSome()是静态的同步方法
     也就是说此时的锁资源是运行是类,所以此时无论两个线程
     是否操作共享数据,都需要同步
 */

public class Exam01 {
    public static void main(String[] args) {
        //创建一个MyClass对象
        MyClass mc1 = new MyClass();
        MyClass mc2 = new MyClass();
        //创建两个线程
        Thread t1 = new MyThread(mc1);
        Thread t2 = new MyThread(mc2);
        //给t1 t2线程设置名字
        t1.setName("t1");
        t2.setName("t2");
        //开启两个线程
        t1.start();
        try {
            Thread.sleep(1000);//这个线程的作用是保证t1线程先执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

class MyThread extends Thread {
    private MyClass mc;

    public MyThread(MyClass mc) {
        this.mc = mc;
    }

    @Override
    public void run() {

        if(Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if(Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}

class MyClass {

    //synchronized出现在静态方法上时,表示锁是运行时类
    public static synchronized void doSome() {
        System.out.println("doSome begin");
        //睡眠十秒
        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");

    }

    public static synchronized void doOther() {
        System.out.println("doOther begin");

        System.out.println("doOther over");
    }
}

10.11 在开发中应该怎么解决线程安全问题

是一上来就选择线程同步吗?synchronized
	不是,synchronized会让程序的执行效率降低,用户体验不好。
	系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步	机制。
第一种方案:尽量使用局部变量代替“实例变量和静态变量”
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

十一、死锁

package com.bjpowernode.java.deadlock;
/*
    死锁:代码要求会写
    一般面试官要求会写。只有会写的,才会在以后的开发中注意这个事儿
    因为死锁很难调试

    synchronized在开发中最好不要嵌套使用,一不小心就可能导致死锁情况的发生
 */
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        //创建两个线程,连个线程共享o1 o2;
        MyThread1 t1 = new MyThread1(o1, o2);
        MyThread1 t2 = new MyThread1(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;
    }
    @Override
    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;
    }
    @Override
    public void run() {
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){

            }
        }
    }
}

十二、 守护线程

java语言中线程分为两大类:
	一类是:用户线程
	一类是:守护线程(后台线程)
	其中具有代表性的就是:垃圾回收线程(守护线程)
守护线程的特点:
	一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
	注意:主线程main方法是一个用户线程
守护线程用在什么地方法呢?
每天00:00的时候系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程。一直在那里看着,每到00:00的时候就备份一次。所有的用户线程如果都结束了,守护线程自动退出,没有必要进行数据备份了。
package com.bjpowernode.java.thread;

/*
    守护线程
 */
public class ThreadTest14 {
    public static void main(String[] args) {
        BakThread bakThread = new BakThread();
        bakThread.setName("备份数据的线程");

        //启动线程之前,将线程设置为守护线程
        bakThread.setDaemon(true);//这一句代码必须放在启动线程之前
        bakThread.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 BakThread extends Thread {
    @Override
    public void run() {
        int i = 0;
        //即使是死循环,但由于该线程是守护线程,当用户线程结束,守护线程自动终止
        while (true) {
            System.out.println(Thread.currentThread().getName() + "-->" + (++i));

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

十三、定时器

定时器的作用:
	间隔特定的时间,执行特定的程序。
	例如:每周要进行银行账户的总账操作。
		 每天要进行数据的备份操作。
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:
	可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
	在Java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用,不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
	在实际开发中,目前使用较多的是spring框架中的springTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
package com.bjpowernode.java.thread;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/*
    使用定时器指定定时任务
 */
public class TimerTest {
    public static void main(String[] args) throws ParseException {
        //创建定时器对象
        Timer timer = new Timer();
 //Timer timer1 = new Timer(true);//这种方式是指创建一个定时器对象,并指定该对象是一个守护线程
//指定定时任务timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date parse = simpleDateFormat.parse("2021-2-4 13:10:00");

        //timer.schedule(new LogTimerTask(), parse, 1000 * 10);

        //使用匿名内部类的方式
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                SimpleDateFormat simpleDateFormat = new 				 SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String format = 											simpleDateFormat.format(new Date());
                System.out.println(format+"成功完成了一次数据备份");
            }
        }, parse, 1000 * 10);

    }
}
//编写一个定时任务类
//假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask{

    @Override
    public void run() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = simpleDateFormat.format(new Date());
        System.out.println(format+"成功完成了一次数据备份");
    }
}

十四、wait()、notify()、notifyAll()

第一:wait()和notify()方法不是线程对象的方法,是Java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。
	wait()、notify()都不是通过线程对象调用的。
第二:wait()方法作用?
	Object o = new Object();
	o.wait();
	表示:
		让正在o对象上活动的线程进入等待状态,无期限的等待,直到被唤醒为止。o.wait()方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态。
第三:notify()方法作用?
Object o = new Object();
o.notify();
	表示:
		唤醒正在o对象上等待的线程。
	还有一个notifyAll()方法:
		这个方法是唤醒o对象上处于等待的所有线程。

重点:o.wait()方法会让正在o对象上活动的当前线程进入等待状态,并且释放之前占有的o对象的锁。
	o.notify()方法只会通知,不会释放之前占有的o对象的锁。
	
	
	注意:(重点)
	被notify唤醒的线程并不会立马执行,直到当前线程放弃锁资源的时候,唤醒的线程才能继续执行。
		

14.1 生产者和消费者问题

​ 生产一个消费一个

package com.bjpowernode.java.thread;

import java.util.ArrayList;
import java.util.List;

/*
   1. 使用wait()、notify()方法实现“生产者和消费者模式”
   2.什么是“生产者和消费者模式”?
   生产者线程负责生产,消费者线程负责消费
   生产线程和消费线程要达到均衡。
   这是一种特殊的业务需求,在这种情况下需要使用wait方法和notify方法
   3.wait和notify方法不是线程对象的方法,是普通java对象都有的方法。
   4.wait方法和notify方法是建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题
   5.wait方法的作用:o.wait让正在o对象上活动的线程t进入等待状态,并且释放调t线程之前占有的o对象的锁。
   6.notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
   7.模拟这样一个需求:
   仓库我们采用List集合
   List集合中假设只能存储1个元素。
   1个元素就表示仓库满了。
   如果List结合中元素个数是0,就表示仓库空了。
   保证List集合中永远都是最多存储1个元素
   必须做到这种效果:生产一个消费一个。
 */
public class ThreadTest16 {
    public static void main(String[] args) {
        //创建一个仓库对象,共享的
        List list = new ArrayList();
        //创建两个线程对象
        //生产者线程
        Thread t1 = new Thread(new Producer(list));
        //消费者线程
        Thread t2 = new Thread(new Consumer(list));
        t1.setName("生产者线程");
        t2.setName("消费者线程");
        //启动线程
        t1.start();
        t2.start();

    }
}


//生产线程
class Producer implements Runnable {
    //仓库
    private List list;

    public Producer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        //一直生产(使用死循环来模拟一直生产)
        while (true) {
            //给仓库对象list加锁
            synchronized (list) {

                if (list.size() > 0) {//大于0,说明仓库中已经有1个元素了
                    try {
                        //当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //程序能够执行到这里说明仓库是空的,可以生产
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                //唤醒消费者进行消费
                list.notify();
            }

        }
    }
}


//消费线程
class Consumer implements Runnable {
    private List list;

    public Consumer(List list) {
        this.list = list;
    }

    @Override
    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.notify();

        }

        }
    }
}

ublic Producer(List list) {
this.list = list;
}

@Override
public void run() {
    //一直生产(使用死循环来模拟一直生产)
    while (true) {
        //给仓库对象list加锁
        synchronized (list) {

            if (list.size() > 0) {//大于0,说明仓库中已经有1个元素了
                try {
                    //当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //程序能够执行到这里说明仓库是空的,可以生产
            Object obj = new Object();
            list.add(obj);
            System.out.println(Thread.currentThread().getName() + "--->" + obj);
            //唤醒消费者进行消费
            list.notify();
        }

    }
}

}

//消费线程
class Consumer implements Runnable {
private List list;

public Consumer(List list) {
    this.list = list;
}

@Override
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.notify();

    }

    }
}

}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值