JavaSE——07多线程

目录

07 多线程

1、关于多线程

1.1 进程与线程

1.2 关于java程序的多线程

1.3 进程和线程的关系

1.4 关于多线程机制

1.5 关于单核的CPU

2、如何实现多线程

2.1 继承Thread

2.2 实现Runnable接口

2.3 匿名内部类

2.4 关于线程对象的生命周期

3、对线程的操作

3.1 获取当前线程对象

3.2 获取线程对象的名字

3.3 修改线程对象的名字

3.4 默认的名字的规律

3.5 关于线程的sleep方法

3.7 终止线程的睡眠

3.8 强行终止程序

3.9 *关于线程的调度

4、线程安全

4.1 多线程并发的问题

4.2 涉及到的专业术语

4.3 线程不安全的例子

4.4 解决线程安全的方法

4.5 线程锁

4.6 java中的三大变量的安全问题

4.7 扩大安全同步的范围

4.8 在实例方法上使用synchronized

4.9 synchronized总结

4.10 死锁现象

4.11 开发中如何解决线程安全问题

4.12 守护线程

4.13 定时器

4.14 实现线程的第三种方式

4.15 Object类中关于线程的方法

4.16 生产者和消费者模式


07 多线程

1、关于多线程

1.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线程执行tl的。
t2线程执行t2的。
t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。

单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种多线程并发”的感觉。
对于单核的cpu来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是:多个事情同时在做!!!!!
线程A:播放音乐
线程B:运行魔兽游戏
线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,给我们的感觉是同时并发的。

电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,人类的眼睛产生了错觉,感觉是动画的。这说明人类的反应速度很慢,就像一根钢针扎到手上,到最终感觉到疼,这个过程是需要”很长的"时间的,在这个期间计算机可以进行亿万次的循环。所以计算机的执行速度很快。

2、如何实现多线程

public class ThreadTest02 {
	public static void main(String[] args){
		//这里是main方法,这里的代码属于主线程,在主栈中运行。
		//新建一个分支线程对象
		MyThread myThread = new MyThread();
		//启动线程
		//myThread.run();//不会启动线程,不会分配新的分支栈。

		//start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
		//这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
		//启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
		// run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
		myThread.start();
		//这里的代码还是运行在主线程中。
		for(int i = 0; i < 1000;i++){
			System.out.println("主线程--->"+i);
		}
        //java代码逐行执行
	}
}
class MyThread extends Thread {
	@0verride
	public void run(){
	//编写程序,这段程序运行在分支线程中(分支栈)。
		for(int i = 0;i < 1000;i++){
			System.out.println("分支线程--->"+i);
        }
    }
}

手动调用run方法

并没有启动线程,不会分配新的分支栈,还是在主线程中进行的

在这里插入图片描述

JVM调用的run方法

在这里插入图片描述

通过start方法启动线程,然后分配新的分支栈,jvm自动调用run方法

2.1 继承Thread

第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。

//java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。
//
//定义线程类
public class MyThread extends Thread{
	public void run(){
        
	}
}
	//创建线程对象
	MyThread t = new MyThread() ;
	//启动线程。
	t.start();

2.2 实现Runnable接口

第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。

//定义一个可运行的类
public class MyRunnable implements Runnable {
	public void run(){
        
    }
}
	//创建线程对象
	Thread t = new Thread(new MyRunnable()) ;
	//启动线程
	t.start();

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

2.3 匿名内部类

public class ThreadTest04{
	public static void main(String[] args){
		//创建线程对象,采用匿名内部类方式。
		//这是通过一个没有名字的类,new出来的对象。
		Thread t = new Thread(new Runnable(){
			@Override
			public void run(){
				for(int i = 0; i < 100; i++){
					System.out.println("t线程--->"+i);
           	 	}
            }
       	});
		//启动线程
		t.start();
		for(int i =0; i < 100; i++){
			System.out.println("main线程--->"+i);
        }
    }
}

2.4 关于线程对象的生命周期

新建状态、就绪状态、运行状态、阻塞状态、死亡状态

在这里插入图片描述

3、对线程的操作

3.1 获取当前线程对象

Thread t = Thread.currentThread();

可以使用**this/super.**的形式进行访问,但有一定的局限性,必须在类当中

返回值t就是当前线程。

3.2 获取线程对象的名字

String name = 线程对象:getNane();

3.3 修改线程对象的名字

线程对象.setName(“线程名字”);

3.4 默认的名字的规律

当线程没有设置名字的时候,默认的名字有什么规律?(了解一下)
Thread-0
Thread-1
Thread-2
Thread-3

3.5 关于线程的sleep方法

static void sleep(long millis)
1)静态方法:Thread.sleep(1000);
2)参数是毫秒
3)作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
4)Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。|

public class ThreadTest06 {
	public static void main(String[] args){
		//让当前线程进入休眠,睡眠5秒
		//当前线程是主线程!!!
		/*try {
			Thread.sleep(1000 * 5);
		}catch (InterruptedException e){
			e.printStackTrace();
		}*/
		//5秒之后执行这里的代码
		//System.out.println("hello world!");
		for(int i = 0; i < 10; i++){
			System.out.println(Thread.currentThread().getName() + "--->" + i);
			try {
				Thread.sleep( millis: 1000);
			} catch (InterruptedException e){
				e.printStackTrace();
       	 	}
        }
    }
}

关于sleep的面试题

public class ThreadTest07 {
	public static void main(String[] args){
		//创建线程对象
		Thread t = new MyThread3();
		t.setName("t");
		t.start();
		//调用sleep方法
		try {
		//问题:这行代码会让线程t进入休眠状态吗?
			t.sleep(millis:1000*5);//在执行的时候还是会转换成:Thread.sleep(1000*5);
		//这行代码的作用是:让当前线程进入休眠,也就是说main线程进入休眠。
		//这样代码出现在main方法中,main线程睡眠。|
		} catch (InterruptedException e) {
			e.printStackTrace();
        }
		System.out.println("hello World!");
    }
}

class MyThread3 extends Thread{
	public void run(){
		for(int i =0;i < 10000; i++){
			System.out.println(Thread.currentThread().getName() + "--->" + i);
		}
	}
}

3.7 终止线程的睡眠

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(1000 * 5);
		} 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( millis: 1000 * 60 * 60 * 24 * 365);//睡眠1年
        } catch (InterruptedException e) {
			e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "---> end");
    }
}

3.8 强行终止程序

public class ThreadTest08 {
	public static void main(String[] args){
		Thread t = new Thread(new MyRunnable2());
		t.setName("t");
		t.start();
		try{
			Thread.sleep(1000 * 5);
		} catch (InterruptedException e){
			e.printStackTrace();
        }
        
        
		t.stop();//强行终止程序
        
        
    }
}

class MyRunnable2 implements Runnable {
	@Override
	public void run(){
		System.out.println(Thread.currentThread().getName() + "---> begin");
		try {
			Thread.sleep( millis: 1000 * 60 * 60 * 24 * 365);//睡眠1年
        } catch (InterruptedException e) {
			e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "---> end");
    }
}

在java中怎么强行终止一个线程的执行。
这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了,
线程没有保存的数据将会丢失。不建议使用。

怎么合理的终止一个线程的执行?(打一个布尔标记)


public class ThreadTest10 {
	public static void main(String[] args){
		MyRunable4 r = new MyRunable4();
		Thread t =new Thread(r);
		t.setName("t");
		t.start();
		//模拟5秒
		try {
			Thread.sleep( millis: 5000);
		} catch (InterruptedException e){
			e.printStackTrace();
        }
		//终止线程
		//你想要什么时候终止t的执行,那么你把标记修改为false,就结束了。
		r.pun = false;
    }
}

class MyRunable4 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( millis: 1000);
                } catch (InterruptedException e) {
					e.printStackTrace();
                }
            }else{
            	//终止当前线程
                return;
            }
        }
    }
}

3.9 *关于线程的调度

3.9.1 常见的线程调度模型

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

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

3.9.2 和线程调度有关的方法

实例方法:
void setPriority(int newPriority)设置线程的优先级
int getPriority()获取线程优先级
最低优先级1
默认优先级是5
最高优先级10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)

静态方法:
static void yield() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态"回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。

实例方法:
void join()合并线程

class MyThreadl extends Thread {
	public void dosome(){
		MyThread2 t = new MyThread2 () ;
		t.join();//当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以执行
    }
}
class MyThread2 extends Thread{
    
}

4、线程安全

​ 在以后的开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等都已经实现完了。这些代码我们都不需要编写。最重要的是,编写的代码需要放到一个多线程的环境下运行,更需要关注的是这些数据在多线程并发的环境下是否是安全的

4.1 多线程并发的问题

在这里插入图片描述

4.1.2 什么时候出现线程安全问题

三个条件:

  • 条件1:多线程并发。
  • 条件2:有共享数据。
  • 条件3:共享数据有修改的行为。

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

4.1.3 解决线程安全问题

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

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

  • 这种排队执行解决线程安全问题

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

解决线程安全问题:使用线程同步机制

线程同步就是线程排队了,线程排队就会牺牲一部分效率,数据安全第一位,只有数据安全了,才可以提高效率

4.2 涉及到的专业术语

4.2.1 异步编程模型

线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型

  • 多线程并发,效率高

  • 异步就是并发

4.2.2 同步编程模型

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

  • 效率低,线程排队执行
  • 同步就是排队

4.3 线程不安全的例子

不使用线程同步机制,多线程对同一个账户进行取款,出现了线程安全问题。同时对同一个账号取款

package TgreadSAFE;

public class Account {
    private String acton;//账户
    private double balance;//余额
    public Account() {
    }	

    public Account(String acton, double balance) {
        this.acton = acton;
        this.balance = balance;
    }

    public String getActon() {
        return acton;
    }

    public double getBalance() {
        return balance;
    }

    public void setActon(String acton) {
        this.acton = acton;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    //取款方法
    public void withdraw(double money){
        //取款之前的余额
        double before = this.balance;
        //取款之后的余额
        double after = before - money;
        //模拟一下网络延迟,100%出问题
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //更新余额
        this.setBalance(after);
    }
}
package TgreadSAFE;

public class AccountThread extends Thread {
    //两个线程必须共享一个账户对象
    private Account account;
    //通过构造方法来传递账户对象
    public AccountThread(Account account){
        this.account = account;
    }
    @Override
    public void run() {
        //run方法的执行表示取款
        double money = 5000;
        //多线程并发执行这个方法(t1和t2两个栈)
        account.withdraw(money);
        System.out.println(Thread.currentThread().getName() + "余额:" + account.getBalance());

    }
}
package TgreadSAFE;

public class Test01 {
    public static void main(String[] args) {
        //创建账户对象(只有一个)
        Account account = new Account("001",10000);
        //创建了两个线程,共享一个账户
        AccountThread accountThread1 = new AccountThread(account);
        AccountThread accountThread2 = new AccountThread(account);
        //设置name
        accountThread1.setName("t1");
        accountThread2.setName("t2");
        //启动取款
        accountThread1.start();
        accountThread2.start();
    }
}

4.4 解决线程安全的方法

线程同步机制的语法:

 synchronized (){
    //线程同步机制代码块
}
//() 传的数据必须是多线程共享的数据,才能达到多线程排队

() 中写什么?要看你想要哪些线程同步
假设有t1,t2,t3,t4,t5,有5个线程。你只希望t1 t2 t3排队,t4 t5不需要排队,怎么办?
你一定要在()中传入t1 t2 t3共享的对象,这个对象对于t4 t5不是共享的

package ThreadSafe2;
/*
使用线程同步机制,多线程对同一个账户进行取款,解决线程安全问题
 */
public class Account {
    private String acton;//账户
    private double balance;//余额
    public Account() {
    }

    public Account(String acton, double balance) {
        this.acton = acton;
        this.balance = balance;
    }

    public String getActon() {
        return acton;
    }

    public double getBalance() {
        return balance;
    }

    public void setActon(String acton) {
        this.acton = acton;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    //取款方法
    public void withdraw(double money){
        //以下这几行代码必须是线程排队的不能并发
        //一个线程把这里的代码全部执行结束之后,另一个线程才能进来
        /*
        线程同步机制的语法:
            synchronized (){
            //线程同步机制代码块
            }
            传的数据必须是多线程共享的数据,才能达到多线程排队
            ()中写什么?
                那要看你想要哪些线程同步
                假设有t1,t2,t3,t4,t5,有5个线程
                你只希望t1 t2 t3排队,t4 t5不需要排队,怎么办?
                你一定要在()中传入t1 t2 t3共享的对象,这个对象对于t4 t5不是共享的
         */
        //账户对象是共享的,this就是账户对象
        synchronized (this){
            //取款之前的余额
            double before = this.balance;
            //取款之后的余额
            double after = before - money;
            //模拟一下网络延迟,100%初出问题
            try {
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //更新余额
            this.setBalance(after);
        }
    }
}

4.5 线程锁

在java语言中,任何一个对象都有一把“锁”,其实这把锁就是标记(只是把它叫做锁)100个对象100把锁

执行原理

  1. 假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先有一个后。
  2. 假设t1先执行了,遇到 synchronized,这时候自动找后面共享对象的对象锁.找到之后,并占有这把锁,然后执行同步代码块中的程序,在执行过程中一直都是占有这把锁的,直到同步代码块代码结束,这把锁才会释放
  3. 假设t1已经占有这把锁,此时t2也遇到了 synchronized关键字,也会去占有后面共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,直到t1把同步代码块执行结束,t1会归还这把锁,此时t2等到这把锁,t2占有这把锁之后,进入同步代码块执行程序

这样就达到了线程排队执行

注意:共享的对象一定是要选好了。这个共享的对象一定是你需要排队执行的这些线程对象共享的。

synchronized (this){
    //取款之前的余额
    double before = this.balance;
    //取款之后的余额
    double after = before - money;
    //模拟一下网络延迟,100%初出问题
    try {
    	Thread.sleep(1000*5);
    } catch (InterruptedException e) {
    	e.printStackTrace();
    }
    //更新余额
    this.setBalance(after);
}

4.6 java中的三大变量的安全问题

  • 实例变量:堆

  • 局部变量:栈

  • 静态变量:方法区

  1. 以上三大变量中,局部变量永远都不存在安全问题,因为局部变量不共享。常量也不会有线程安全问题

  2. 实例变量在堆中,静态变量在方法区中,堆和方法区只有一个,

  3. 堆和方法区都是多线程共享的,所以可能存在线程安全问题

4.7 扩大安全同步的范围

在run中把withdraw方法进行同步,扩大了同步范围,效率更低

package ThreadSafe2;

public class AccountThread extends Thread {
    //两个线程必须共享一个账户对象
    private Account account;
    //通过构造方法来传递账户对象
    public AccountThread(Account account){
        this.account = account;
    }
    @Override
    public void run() {
        //run方法的执行表示取款
        double money = 5000;
        //多线程并发执行这个方法(t1和t2两个栈)
        //扩大线程同步范围
        synchronized (account){
            account.withdraw(money);
        }
        	System.out.println(Thread.currentThread().getName() + account.getBalance());
        }
}

4.8 在实例方法上使用synchronized

synchronized出现在实例方法上。

  • 表示整个方法都需要同步,可能会无故扩大同步的范围,可能会导致程序的效率降低,所以这种方式不常用
  • 好处: 代码写的比较少,节俭了。
  • 使用场景:如果共享的对象就是this,并且需要同步的代码块是整个方法体,这种方式不灵活
package ThreadSafe3;

public class Account {
    private String acton;//账户
    private double balance;//余额
    Object object = new Object();//实例变量(Account是多线程共享的,Account中的实例变量object也是共享的)
    public Account() {
    }

    public Account(String acton, double balance) {
        this.acton = acton;
        this.balance = balance;
    }

    public String getActon() {
        return acton;
    }

    public double getBalance() {
        return balance;
    }

    public void setActon(String acton) {
        this.acton = acton;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
    //取款方法
    //在实例方法中加入synchronized
    //synchronized 出现在实例方法上。一定锁的是this,所以这种方式不灵活,
    //还有一个缺点:synchronized出现在实例方法上,表示整个方法都需要同步,可能会无故扩大同步的范围,可能会导致程序的效率降低,所以这种方式不常用
    //synchronized 出现在实例方法中的好处: 代码写的比较少,节俭了
    //如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式
    public synchronized void withdraw(double money){
        double before = this.balance;
        double after = before - money;
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setBalance(after);
	}
}
  • 如果使用局部变量的话,建议使用StringBuilder,因为局部变量不存在线程安全问题,选择StringBuffer效率较低。
  • ArrayList是非线程安全的,Vector是线程安全的
  • HshMap、HashSet是非线程安全的 ,HashTable是线程安全的。

4.9 synchronized总结

synchronized有三种写法:

  • 第一种:同步代码块,灵活

    synchronized(线程共享对象){
    	//同步代码块;
    }
    
  • 第二种:在实例方法上使用synchronized,表示共享对象一定是this,并且同步代码块是整个方法体。

  • 第三种:在静态方法上使用synchronized,表示找类锁,类锁永远只有1把。
    就算创建了100个对象,那类锁也只有一把。

对象锁:1个对象1把锁,100个对象100把锁。

类锁:100个对象,也可能只是1把类锁。

面试题

package ThreadExam1;
/*
t2不需要等到t1执行,只有doSome有锁
*/
public class Test01 {
    public static void main(String[] args) {
        M m = new M();
        Thread t1 = new MyThread(m);
        Thread t2 = new MyThread(m);
        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 M m;
    public MyThread(M m){
        this.m = m;
    }
    public void run(){
        if(Thread.currentThread().getName() == "t1"){
            m.doSome();
        }
        if(Thread.currentThread().getName() == "t2"){
            m.doOther();
        }
    }
}
class M {
    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 ThreadExam2;
/*
t2需要等t1执行结束,doSome和doOther都是this这把锁
*/
public class Test01 {
    public static void main(String[] args) {
        M m = new M();
        Thread t1 = new MyThread(m);
        Thread t2 = new MyThread(m);
        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 M m;
    public MyThread(M m){
        this.m = m;
    }
    public void run(){
        if(Thread.currentThread().getName() == "t1"){
            m.doSome();
        }
        if(Thread.currentThread().getName() == "t2"){
            m.doOther();
        }
    }
}
class M {
    public synchronized void doSome(){//锁 是 this
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized void doOther(){//锁 是 this
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}
package ThreadExam3;
/*
两个线程对象,所以t2不需要等t1执行
*/
public class Test01 {
    public static void main(String[] args) {
        M m = new M();
        M m1 = new M();
        Thread t1 = new MyThread(m);
        Thread t2 = new MyThread(m1);
        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 M m;
    public MyThread(M m){
        this.m = m;
    }
    public void run(){
        if(Thread.currentThread().getName() == "t1"){
            m.doSome();
        }
        if(Thread.currentThread().getName() == "t2"){
            m.doOther();
        }
    }
}
class M {
    public synchronized void doSome(){//锁 是 this
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized void doOther(){//锁 是 this
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}
package ThreadExam4;
/*
静态方法是类锁,所以t2需要等t1执行
*/
public class Test01 {
    public static void main(String[] args) {
        M m = new M();
        M m1 = new M();
        Thread t1 = new MyThread(m);
        Thread t2 = new MyThread(m1);
        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 M m;
    public MyThread(M m){
        this.m = m;
    }
    public void run(){
        if(Thread.currentThread().getName() == "t1"){
            m.doSome();
        }
        if(Thread.currentThread().getName() == "t2"){
            m.doOther();
        }
    }
}
class M {
    public synchronized static void doSome(){//锁 是 this
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome over");
    }
    public synchronized static void doOther(){//锁 是 this
        System.out.println("doOther begin");
        System.out.println("doOther over");
    }
}

4.10 死锁现象

在这里插入图片描述

package DeadLock;
import lei.T;
/*
死锁代码要会写
只有会写的,才会在以后的开发中注意这个事情
因为死锁很难调试
 */
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        //t1和t2共享o1 和 o2
        Thread t1 = new Mythread(o1,o2);
        Thread t2 = new Mythread2(o1,o2);
        t1.start();
        t2.start();
    }
}

class Mythread extends Thread{
    Object o1;
    Object o2;
    public Mythread(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){
            }
        }
    }
}

4.11 开发中如何解决线程安全问题

​ 不能一上来就选择线程同步synchronized,synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量(并发量)降低。用户体验差。在不得已的情况下再选择线程同步机制。

  • 第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
  • 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)
  • 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

4.12 守护线程

java语言中线程分为两大类:

  • 一类是:用户线程
  • 一类是:守护线程(后台线程)
  • 其中具有代表性的就是:垃圾回收线程(守护线程)。

守护线程的特点:

  • 一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。

注意:主线程main方法是一个用户线程。

守护线程用在什么地方呢?

  • 每天00:00的时候系统数据自动备份。

这个需要使用到定时器,并且我们可以将定时器设置为守护线程。一直在那里看着,每到00:00的时候就备份一次,所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。

t.setDaemon(true);
package thread;
/*
守护线程
 */
public class Test14 {
    public static void main(String[] args) {
        Thread t= new BakDtaThread();
        t.setName("beifeng");
        //启动线程之前,将线程设置为守护线程
        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 BakDtaThread 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();
            }
        }
    }
}

4.13 定时器

定时器的作用:间隔特定的时间,执行特定的程序。

  • 每周要进行银行账户的总账操作。
  • 每天要进行数据的备份操作。

在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的
那么在java中其实可以采用多种方式实现:

  • 可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)

  • 在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。

  • 在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

4.14 实现线程的第三种方式

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

​ 系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果。使用第三种方式:实现Callable接口方式 拿到这个执行结果。

4.15 Object类中关于线程的方法

关于Object类中的wait和notify方法

  • wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。
  • wait方法和notify方法不是通过线程对象调用,不是这样的:t.wait(),也不是这样的:t.notify()…不对。

wait()方法作用

Object o = new Object();
o.wait();

表示:让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。
o.wait();方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态。

notify()方法的作用

Object o = new Object();
o.notify();

表示:唤醒正在o对象上等待的线程。

notifyAll()方法的作用

这个方法是唤醒o对象上处于等待的所有线程。

在这里插入图片描述

4.16 生产者和消费者模式

在这里插入图片描述

生产一个消费一个

package thread;

import lei.T;

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个元素就代表仓库满了
    如果list集合中的元素为0,就表示仓库空了
    保证list集合永远都是最多存储一个元素
    必须做到这种效果:生成一个消费一个
 */
public class ThreadTest16 {
    public static void main(String[] args) {
        //创建仓库对象
        List list  = new ArrayList();
        //创建两个线程对象
        //一个是生产者线程
        Thread thread1 = new Thread(new Procuder(list));
        //一个是消费者线程
        Thread thread2 = new Thread(new Consumer(list));

        thread1.setName("t1");
        thread2.setName("t2");
        thread1.start();
        thread2.start();
    }

}
//生产线程
class Procuder implements Runnable{
    //共享的仓库
    private List list;

    public Procuder() {
    }
    
    public Procuder(List list) {
        this.list = list;
    }
    
    @Override
    public void run() {
        //一直生产
        while(true){
            synchronized (list){
                if(list.size() > 0){
                //当前线程进入等待状态
                     try {
                        list.wait();//当前线程进入等待状态,并且释放procuder之前占有list集合的锁
                     } 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() {
    }
    
    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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖虎不秃头

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值