19-java笔记-多线程

把程序执行路径只有一条的环境称为单线程环境

把程序的执行有多条路径的环境称为多线程环境

一、线程

1.进程

学习线程要先了解进程 因为线程依赖于进程

​ 进程:正在运行的应用程序。

电脑上可以有多个进程,但在某个时间点上单核CPU只能执行一个进程。尽管人们感觉多个进程是在同时进行,但实际上只是单核CPU在多个进程间进行高速的切换,人们不能感觉出来。

多进程的意义:提高CPU的利用率

2.线程

线程依赖于进程,进程是线程的容器。进程开启后,它会执行很多个任务,每一个任务就被称为线程 人们感觉多个任务同时执行使用为线程是具有随机性的,它会抢占CPU的执行权,谁抢到,CPU在某个时刻就执行谁(但也只能执行一个)

进程是拥有资源的基本单位,线程是CPU调度的基本单位 操作系统在调度时的基本单位是进程间的多个线程而不是进程程序

二、并发和并行

1.并发:

指物理上的同时发生 同一时间间隔执行 应用程序能够交替执行不同任务 只是切换得太快 我们察觉不到 是不同实体上的多个事件 在单处理器和多处理器都存在

2.并行:

指逻辑上的同时发生 应用程序能够同时执行不同任务 如走路看手机 是同一个实体的多个事件 在多处理器存在

JVM 是多线程 至少两个线程 一个线程是主线程(main方法),还有一个垃圾回收线程

三、java实现多线程

由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。
而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。

Java不能直接调用系统功能,但可以去调用C/C++写好的程序来实现多线程程序。
由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,
然后提供一些类供我们使用。我们就可以实现多线程程序了。

1.创建多线程的方法A

​ 1.定义一个类继承Thread类

​ 2.重写Thread类中的run()方法

​ 3.创建我们写的类的对象

​ 4.开启分线程

class MyThread extends Thread{
    @override
    public void run(){
        //这个run方法就是需要分线程来执行的代码,一般耗时的操作就放到这里,让分线程执行,不会阻塞主线程的执行
    }
}
public class Test{
    public static void main(String[] args){
        MyThread mt = new MyThread();
        //开启分线程,不是调用run方法,这只是代表 你在使用一个对象调用一个方法,让这个方法执行而已,没有开启线程
        //开启分线程的正确方式是,调用start()开启线程,由分线程调用run方法
        th.start();
        //同一个线程不要多次开启 ,否则会报异常
        
        //获取线程名称  该方法获取的是默认的线程名
        th.getName();
        //设置线程名
        th.setName("a线程");
        
        //Thread类中的静态方法  获取当前线程对象  再获取主线程名称
        Thread t = Thread.currentThread();//获取当前线程对象
        t.getName();//main
        //也可以设名字
        t.setName("主线程");
    }
}




A.线程调度模型

分时调度模型:所有线程轮流使用CPU使用权,平均分配每个线程占用CPU的时间片

抢占式调度模型 让优先级高的线程有更高几率使用CPU使用权(低优先级也可以使用,只是几率更低) 如果多个线程优先级一样,线程的执行就是随机抢占

java使用的是抢占式调度模型

B.设置线程优先级
class MyThread extends Thread{
    @override
    public void run(){
        //这个run方法就是需要分线程来执行的代码,一般耗时的操作就放到这里,让分线程执行,不会阻塞主线程的执行
    }
}
public class Test{
    public static void main(String[] args){
        MyThread mt = new MyThread();
        MyThread mh = new MyThread();
        //获取线程的优先级
        int p = mt.getPriority();//默认优先级  NORM_PRIORITY  5
        int p = mh.getPriority();//默认优先级  NORM_PRIORITY  5
        
        //设置线程的优先级  Thread设有各个优先级的常量  也可以设整数 1-10 不能超过其范围
        mt.setPriority(Thread.MAX_PRIORITY);  //最大10
        mH.setPriority(Thread.MIN_PRIORITY);//最小1  
        

    }
}
C.线程休眠

(让线程处于阻塞状态)

class MyThread extends Thread{
    @override
    public void run(){
        //这个run方法就是需要分线程来执行的代码,一般耗时的操作就放到这里,让分线程执行,不会阻塞主线程的执行
        Thread.sleep(3000);//让当前线程休眠3000毫秒  异常只能抓取,不能抛出 父类没有抛出
        
    }
    需要一个有参构造
    
}
public class Test{
    public static void main(String[] args){
        MyThread mt = new MyThread("分线程");//通过构造给线程设名字
        mt.start();//3秒后执行分线程
        Thread.sleep(3000);//3秒后执行主线程
        
    }
}


D.线程加入
class MyThread extends Thread{
    @override
    public void run(){
        //这个run方法就是需要分线程来执行的代码,一般耗时的操作就放到这里,让分线程执行,不会阻塞主线程的执行
    }
}
public class Test{
    public static void main(String[] args){
        MyThread mt = new MyThread("分线程1");
        MyThread mh = new MyThread("分线程2");
        MyThread mr = new MyThread("分线程3");
        //现在三个线程是并发执行
        mt.start();
        mh.start();
        mr.start();
        
        //串行  让多个线程挨个顺序执行
        线程启动之后  这个线程执行完后其它线程再执行
        mt.start();
        mt.join();//分线程1执行完后其它线程再执行
        mh.start();
        mh.join();//分线程2执行完后其它线程再执行
        mr.start();
        mr.join();//分线程3执行完后其它线程再执行
        
        
    }
}



E.礼让线程 效果相当不明显
class MyThread extends Thread{
    @override
    public void run(){
        //这个run方法就是需要分线程来执行的代码,一般耗时的操作就放到这里,让分线程执行,不会阻塞主线程的执行
        //每个线程执行一次
        Thread.yield();
    }
}
public class Test{
    public static void main(String[] args){
        MyThread mt = new MyThread();
        MyThread mh = new MyThread();
        
    }
}


F.守护线程

正常主线程执行完毕后必须分线程也执行完毕才会结束程序

将分线程设为守护线程后 主线程执行完毕 分线程必须结束,无论有没有执行完毕

守护线程适合做一些辅助性工作如垃圾回收 资源管理等

在Daemon线程中产生的新线程也是Daemon的。

class MyThread extends Thread{
    @override
    public void run(){
        //这个run方法就是需要分线程来执行的代码,一般耗时的操作就放到这里,让分线程执行,不会阻塞主线程的执行
    }
}
public class Test{
    public static void main(String[] args){
        MyThread mt = new MyThread();
        MyThread mh = new MyThread();
        mt.setDaemon(true);//设为守护线程
        mh.setDaemon(true);//设为守护线程
        mt.start();
        mh.start();
        System.out.println("主线程先执行完了");
        
        
    }
}
G.打断阻塞线程interrupt

当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞

class MyThread extends Thread{
    @override
    public void run(){
        //这个run方法就是需要分线程来执行的代码,一般耗时的操作就放到这里,让分线程执行,不会阻塞主线程的执行
        mt.sleep(5000);//5秒后执行该线程
    }
}
public class Test{
    public static void main(String[] args){
        MyThread mt = new MyThread();
        MyThread mh = new MyThread();
        mt.interrupt();//打断阻塞线程  不是5秒后执行
        
    }
}

2.开启多线程的方式2

1.创建一个类,实现Runnable接口,重写该接口中的run方法

2.创建Thread对象,将Runnable接口的子类对象传递进来

​ Runnable 任务 该接口应该由那些打算通过某一线程执行其实例的类来实现

3.调用start()方法开启进程

class MyRunnable implements Runnable{
    @Override
    public void run(){
        Thread.currentThread().getName();
        System.out.println("分线程执行");
    }
}
public class Test{
    Public static void main(String[] args){
        MyRunnable mr = new MyRunnable();
        Thread th = new Thread(mr);
        th.setName("分线程1");
        th.start();        
    }
}



方式2 更加灵活 实现接口后还可以继承其它类

3.开启多线程的方式3

  1. 创建一个实现Callable接口的类 MyCallable(泛型根据返回值设置)

    ​ Callable 返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法 Callable 接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是Runnable 不会返回结果,并且无法抛出经过检查的异常。

  2. 创建该类对象mc

  3. 创建FutureTask类的对象ft并将mc作为参数传给FutureTask类的对象

  4. 创建Thread对象th,并将ft作为参数传进来

  5. th.start();

class MyCallable implements Callable<Integer>{
    //call方法即使线程要执行的方法
    @Override
    public Integer call()throws Exception{
        int sum = 100+5;
        return sum;
    }
}

public class MyTest {
    public static void main(String[] args) throws Exception{
        MyCallable mc = new MyCallable();
		FutureTask<Integer> ft = new FutureTask<Integer>(mc);
		Thread th = new Thread(ft);
		th.start();
		ft.get();//获取分线程运行完成后的运行结果

    }
}

方式3可以获取分线程运行完成后的运行结果,可以抛出异常

四.线程安全

我们在实际网上访问多线程程序时,因为网络延迟,会出现重复票、0票、负数票

重复票 由于原子性导致 不是一个原子性(不可分割)的操作

0票、负数票 由线程的随机性导致的

1.出现数据安全问题需要三个前提条件

​ 1.是多线程环境

​ 2.存在共享数据

​ 3.有多条语句在操作共享数据

前两个要求都是必须出现的,要避免数据安全问题只能避免第三个条件,可以使用同步代码块解决线程安全问题

2.synchronized(){}

//需要一个任意对象(多个线程共享同一个对象),当该对象在执行其中的代码块时,其它对象不能执行必须等候前一个对象执行完毕 如同一把锁将代码块锁住

将可能出现的代码块放到其中 这里面的代码块运行环境就是单线程环境 这里的代码块不能是一个死循环,否则一个线程进入此处代码块后一直不能结束,而其它进程也必须阻塞等待该线程

public class Text02 extends Thread {
    static int piao = 4800;
    static Object obj = new Object();
    @Override
    public void run() {
        try {
            this.sleep(50);
        } catch (InterruptedException e) {
            System.out.println("InterruptedException");
        }
        for (int i = 0;i<=5000;i++){
            synchronized (obj){
                if(piao>=1){
                    System.out.println(Thread.currentThread().getName()+piao--);
                }else {
                    System.out.println("票已售空");
                }
            }

        }
    }

    public Text02(String name) {
        super(name);
    }

    public Text02() {
    }
}

同步代码块比较耗费资源 效率低

方法上加上synchronized关键字我们叫做同步方法

当同步代码块与同步方法同时存在时,又会出现异常票

同步代码块使用任意对象作为锁对象

同步方法使用的锁对象不是任意对象,它用的锁是this,因为同步代码块的锁对象对同步方法没有用

静态同步方法使用的锁对象不是任意对象,它用的是类锁(当前类的字节码类型 MyRunnable.class

我们使用上述三种锁之一,或将同时存在的锁的对象统一为一个就可以解决异常票、

我们之前说的那些容器有的是线程安全的有的是不安全的 线程安全是因为那些容器使用的是同步方法

3.lock(锁)类 ReentrantLock

在可能出现问题的代码块之前加上锁(lock.lock()) ,在这个代码块之后解锁(lock.unlock())

一般在try语句中上锁,finally语句中解锁 ,防止代码块出现异常后无法解锁

死锁 两个或两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待的现象

五、线程池

不同种类的线程间的通信

生产线程生产资源通知消费线程消费,消费线程消费资源后通知生产者生产

线程 间的等待唤醒机制

​ 1.定义资源

​ 2.要有生产线程

​ 3.要有消费线程

​ 4.测试类测试

发生线程安全 使用同步锁

1.线程间通信

等待唤醒

​ 生产者生产资源后等待并通知消费线程消费

​ 消费线程消费资源后等待并通知生产线程来生产

Object类中的方法

wait();
wait(long timeout);
wait();方法//一旦等待就必须释放锁   从哪里等待,被唤醒后就从哪里醒来

notify();//唤醒等待的线程,线程重新开始抢CPU执行权
notifyAll();

sleep和wait的区别

​ 共同点:都可以让线程处于阻塞状态

​ 不同点:1. sleep必须设置休眠量,wait可设可不设(没有唤醒就一直等)

​ 2.sleep一旦休眠不会释放锁,wait阻塞会释放锁

2.内存可见性

JVM内存模型

​ synchronized 也可以但效率慢

​ volatile 修饰变量 可以解决内存可见性(共享变量在工作内存更新后能立刻刷回主内存)问题,无法解决原子性

3.原子性

CAS算法 比较并交换

是一种硬件对CPU并发访问的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。

 CAS 是一种无锁的非阻塞算法的实现。

 CAS 包含了 3 个操作数:

​  需要读写的内存值 V

​  进行比较的值 A

​  拟写入的新值 B
当且仅当 V 的值等于 A 时, CAS 通过原子方式用新值 B 来更新 V 的
值,否则不会执行任何操作。

JDK1.5后java提供了原子性变量

 AtomicBoolean 、 AtomicInteger 、 AtomicLong、AtomicReference
 AtomicIntegerArray 、 AtomicLongArray
 AtomicMarkableReference
 AtomicReferenceArray

4.线程状态

新建:线程被创建出来

就绪:具有CPU的执行资格,但是不具有CPU的执行权

运行:具有CPU的执行资格,也具有CPU的执行权

阻塞:不具有CPU的执行资格,也不具有CPU的执行权

死亡:不具有CPU的执行资格,也不具有CPU的执行权

5.线程池

​ 装有一定线程对象的容器

A.作用

​ 程序启动新线程成本教高,而启用新线程可以很高地提高性能

​ 如果我们创建一定数量的线程放入一个容器中,如果有任务就直接从该容器中调用线程,在线程执行完任务后不是立刻死亡,而是回到容器中等待调用。

​ Blocking Queue(阻塞队列)

​ JDK1.5之后,java支持内置线程池,不用手动实现

B.创建线程池方法

​ JDK1.5之后新增了一个Executors工厂来生产线程池,有如下几个方法

​ ExecutorService 线程池

1.根据任务数量来创建线程对应的线程个数
public static ExecutorService  newCachedThreadPool (); 
ExecutorService es = Executors.newCachedThreadPool();
MyRunnable mr = new MyRunnable();
es.submit(mr);
es.submit(new MyRunnable());
es.submit(new MyRunnable());



2.在这个线程池里面,提前创建指定个线程对象  
ExecutorService es = Executors.newFixedThreadPool(3);
es.submit(new MyRunnable());
es.submit(new MyRunnable());
es.submit(new MyRunnable());


3.线程池中只有一个线程对象
ExecutorService es = Executors.newSingleThreadPool();
es.submit(new MyRunnable());

上面使用Runnable接口

下面使用Callable接口 获取线程执行之后的返回结果

ExecutorService es = Executors.newFixedThreadPool(3);
MyCallable mc = new MyCallable(10);//计算1-10整数的和
Future<Integer> f = es.submit(mc);
Integer it = f.get();


C、定时器

Timer工具类(util包下) TimerTask(定时器要执行的任务)

线程用其安排以后在后台线程中执行的任务,可安排任务执行一次,或者定期执行

Timer t= new Timer();
t.scheedule(new MyTimerTask(t),0);//立刻执行定时任务
t.scheedule(new MyTimerTask(t),2000);//2000毫秒后执行定时任务

在定时任务中t.cancel();//取消定时器
---------------------------------------------------------------------------
反复执行
MyTimerTask tt = new MyTimerTask();
t.schedule(tt,1000,2000);//1000毫秒后执行任务,每隔2000毫秒执行一次
tt.cancel();
t.cancel();


在指定日期执行定时任务
String date = "2019-08-30 14:36:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
Date d = sdf.parse(date);
t.schedule(new TimerTask(){
    @Override
    public void run(){
    	System.out.println("删除数据库,跑路")
    }
},date);//指定日期2019-08-30 14:36:00指定该任务"删除数据库,跑路"


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值