JAVASE多线程知识梳理

1、什么是进程,什么是线程

1、进程的狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
2,线程:线程是程序中执行的线程,Java虚拟机允许程序同时运行多个执行线程。
3、进程和线程的关系:一个进程可以有多个线程
4、进程A和进程B内存独立不共享
5、线程A和线程B,方法区和堆内存共享,栈内存不共享,一个线程一个栈内

2、实现线程的两种方式

1、继承java.lang.Thread类,重写如何方法

public class test1 {
    public static void main(String[] args) {
        ThreadTest threadTest=new ThreadTest();
        threadTest.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程----》"+i);
        }

    }
}
class ThreadTest extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程--》"+i);
        }

    }
}

2、实现在java.lang.Runable接口,实现run方法

package com.zzuli.thread;

public class test2 {
    public static void main(String[] args) {
        ThreadTest2 threadTest=new ThreadTest2();
        Thread thread=new Thread(threadTest);
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程----》"+i);
        }

    }
}
class ThreadTest2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程----》"+i);
        }
    }
}

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

3、线程的生命周期

1,线程的生命周期有五种状态,分别是,新建状态,就绪状态,运行状态,阻塞状态,死亡状态。
2、图片展示
在这里插入图片描述

4、获取线程的基本信息

1、获取线程的名字:线程对象.getName()
2、设置线程的名字:线程对象.setName()
3、获取当前线程对象:static Thread currentThread() ;
   3.1该方法是一个静态的方法,在哪个线程调用返回的就是哪个线程的对象

5、线程的睡眠和唤醒

1、sleep()方法
参数和方法类型:该方法是一个静态的方法,参数是毫秒值
作用:导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
2、interrupt()
  2.1、作用:终止此线程的睡眠。这个方法是采用java异常处理机制来唤醒此线程的
例如:

//当把子线程沉睡一年,然后就可以用interrupt()方法唤醒
public class test3 {
    public static void main(String[] args) {
        ThreadTest3 threadTest=new ThreadTest3();
        Thread thread=new Thread(threadTest);
        thread.start();
    }
}
class ThreadTest3 implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(1000*60*60*60*365);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程----》"+i);
        }
    }
}

这是可以看到控制台什么也没有打印出
在这里插入图片描述
2.2、这时可以调用interrupt方法唤醒此线程

public class test3 {
    public static void main(String[] args) {
        ThreadTest3 threadTest=new ThreadTest3();
        Thread thread=new Thread(threadTest);
        thread.start();
        thread.interrupt();
    }
}
class ThreadTest3 implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(10000*60*60*365);
        } catch (InterruptedException e) {
            e.printStackTrace();
           System.out.println("我被唤醒了---》");
        }
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程----》"+i);
        }
    }
}

运行结果为:
在这里插入图片描述

6、线程的调度

1、常见的线程调度模型有哪些
      1.1 抢占式调度:那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。java采用的就是抢占式调度模型。
      1.2 均分式调度:平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。平均分配,一切平等。有一些编程语言,线程调度模型采用的是这种方式。
2、java中提供了哪些方法是和线程调度有关系的呢?
   2.1实例方法:
      void setPriority(int newPriority) 设置线程的优先级
      int getPriority() 获取线程优先级
            最低优先级1,默认优先级是5,最高优先级10,优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
   2.2静态方法:
      static void yield() 让位方法
      注意:暂停当前正在执行的线程对象,并执行其他线程,yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用,yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。在回到就绪之后,有可能还会再次抢到。
   2.3实例方法:
      void join() //合并线程

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

			class MyThread2 extends Thread{
				
			}

7、关于多线程并发环境下,数据的安全问题。

1、为什么要说这个: 以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。 最重要的是:你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点:*****)
2、什么时候数据在多线程并发的环境下会存在安全问题呢?
  三个条件:条件1:多线程并发。条件2:有共享数据。条件3:共享数据有修改的行为。满足以上3个条件之后,就会存在线程安全问题。
3、怎么解决线程安全问题呢?
    使用“线程同步机制”。线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。
4、说到线程同步这块,涉及到这两个专业术语:
    4.1异步编程模型:
        线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是:多线程并发(效率较高。)
        异步就是并发。
    4.2同步编程模型:
        线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低。线程排队执行。
        同步就是排队。
5、Java中有三大变量
实例变量:在堆中。
静态变量:在方法区。
局部变量:在栈中。
以上三大变量中:
   5.1局部变量永远都不会存在线程安全问题。因为局部变量不共享。(一个线程一个栈。)局部变量在栈中。所以局部变量永远都不会共享。
   5.2实例变量在堆中,堆只有1个。
   5.3静态变量在方法区中,方法区只有1个。
   5.4堆和方法区都是多线程共享的,所以可能存在线程安全问题。局部变量+常量:不会有线程安全问题。成员变量:可能会有线程安全问题。
   5.5如果使用局部变量的话:建议使用:StringBuilder。因为局部变量不存在线程安全问题。选择StringBuilder。StringBuffer效率比较低。
ArrayList是非线程安全的。Vector是线程安全的。HashMap HashSet是非线程安全的。Hashtable是线程安全的。
6、synchronized有三种写法:

	第一种:同步代码块
		灵活
		synchronized(线程共享对象){
			同步代码块;
		}

	第二种:在实例方法上使用synchronized
		表示共享对象一定是this
		并且同步代码块是整个方法体。
	
	第三种:在静态方法上使用synchronized
		表示找类锁。
		类锁永远只有1把。
		就算创建了100个对象,那类锁也只有一把。
	
	对象锁:1个对象1把锁,100个对象100把锁。
	类锁:100个对象,也可能只是1把类锁。

7、死锁
1、java中导致死锁的原因:Java中死锁最简单的情况是,一个线程T1持有锁L1并且申请获得锁L2,而另一个线程T2持有锁L2并且申请获得锁L1,因为默认的锁申请操作都是阻塞的,所以线程T1和T2永远被阻塞了。导致了死锁。这是最容易理解也是最简单的死锁的形式。但是实际环境中的死锁往往比这个复杂的多。可能会有多个线程形成了一个死锁的环路,比如:线程T1持有锁L1并且申请获得锁L2,而线程T2持有锁L2并且申请获得锁L3,而线程T3持有锁L3并且申请获得锁L1,这样导致了一个锁依赖的环路:T1依赖T2的锁L2,T2依赖T3的锁L3,而T3依赖T1的锁L1。从而导致了死锁。

从这两个例子,我们可以得出结论,产生死锁可能性的最根本原因是:线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,也就是说在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2,这个是产生死锁的最根本原因。另一个原因是默认的锁申请操作是阻塞的。
2、死锁的代码演示

//synchronized中最好不要嵌套使用,很容易造成死锁
public class DeadThread6 {
    public static void main(String[] args) {
        Object o1=new Object();
        Object o2=new Object();
        //t1和t2线程共享o1,o2
        DeadThread1 t1=new DeadThread1(o1,o2);
        DeadThread2 t2=new DeadThread2(o1,o2);
        t1.start();
        t2.start();
    }
}
class DeadThread1 extends Thread {
    Object obj1;
    Object obj2;

    public DeadThread1(Object obj1, Object obj2) {
        this.obj1 = obj1;
        this.obj2 = obj2;
    }
    @Override
    public void run() {
        synchronized (obj1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (obj2){
                System.out.println("线程1");
            }
        }
    }
}
class DeadThread2 extends Thread {
    Object obj1;
    Object obj2;
    public DeadThread2(Object obj1, Object obj2) {
        this.obj1 = obj1;
        this.obj2 = obj2;
    }
    @Override
    public void run() {
        synchronized (obj2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (obj1){
                System.out.println("线程2");
            }
        }
    }
}

8、线程的扩展

1,守护线程
java语言中线程分为两大类:
一类是:用户线程
一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)。
设置线程变成守护线程:线程对象.setDaemon(true)

	守护线程的特点:
		一般守护线程是一个死循环,所有的用户线程只要结束,
		守护线程自动结束。
	
	注意:主线程main方法是一个用户线程。

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

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

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

			在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,
			那么在java中其实可以采用多种方式实现:
				
				可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行
				任务。这种方式是最原始的定时器。(比较low)

				在java的类库中已经写好了一个定时器:java.util.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 date= sdf.parse("2021-08-29 0:23:30:00");
        timer.schedule(new LogTimerTask(), date, 1000 * 5);
    }
}
//编写一个定时任务类
class LogTimerTask extends TimerTask{

    @Override
    public void run() {
        //在这里你可以指定你要执行的任务
        System.out.println("五秒执行一次");
    }
}

3、实现线程的第三种方式:实现Callable接口。(JDK8新特性。)
这种方式实现的线程可以获取线程的返回值。之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。
优点:可以获取到线程的执行结果
缺点:效率比较低,在获取t线程的执行结果的时候,当前线程手阻塞,效率较低

public class testThread5 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1,创建一个未来任务类对象
        //参数非常重要,需要给一个callback接口实现类对象
        FutureTask task=new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {//call方法相当于run方法
                Thread.sleep(1000*10);
                System.out.println("线程的第三种方法");
                int a=100;
                int b=200;
                return a+b;
            }
        });
        //创建线程
        Thread t=new Thread(task);
        //启动线程
        t.start();
        //这里是main方法,这是在主线程中,在主线程中,怎么获取t线程的返回结果
        Object obj=task.get();//这里会导致当前线程阻塞
        System.out.println("hello world");
    }
}

4、关于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对象上处于等待的所有线程。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值