java 中的线程(一)

1.概念:

什么是进程?
  概念:在计算机中运行的软件,是操作系统中最基础的组成部分 。进程是容器,里面装的都是线程。

什么是线程?
  概念:就是运行在进程中的一段代码,是进程中最小组织单元。

注意:
  1.一个进程中至少要有一个线程,该线程必须是主线程
  2.一个进程中可以有多个线程,至少要有一个主线程
  3.多线程之间按照自己的执行路径同时运行(在cpu中交替执行),互不影响。

为什么多线程可以同时运行?
  因为每个线程在CPU上执行的时间非常短,多线程执行的顺序高速的切换,是人类无法感受到的。

2. 创建线程 的三种方式

创建线程方式一:继承Thread类 --------重写run()方法---------- 在主线程中启动(start())
  
    注意:启动线程是调用start()而不是run(),  run()仅仅是线程的成员,不具有启动的功能。

创建线程方式二:定义 实现Runnable接口 实现类----------在类中重写run()-----------在主线程中先创建实现类的对象,传递给线程类对象的构造方法中
  
    eg MyRunnable my = new MyRunnable();

    Thread t1 = new Thread(my);
    Thread t2 = new Thread(my);

创建线程方式三:实现Callable接口和Future创建线程--------重写call()方法,并且有返回值-----------在主线程中启动(start())

Callable接口是一个带泛型的接口,泛型的类型就是线程返回值的类型。实现Callable接口中的call()方法,方法的返回类型与泛型的类型相同。

Callable<String> callable=new CallableThread();
FutureTask<String> futureTask=new FutureTask<String>(callable);
Thread thread=new Thread(futureTask);
thread.start();//开启一个线程

三种方式的区别:

  1. 实现 接口的方式可以避免java单继承带来的 局限性。
  2.数据共享问题: 使用继承的方式 只能将属性修改为static,  而实现接口的方式可以自动做到数据共享(只创建一个Runnable接口实现类对象)
  
3. Callable规定(重写)的方法是call(),有返回值,并且可以可以抛出异常。Runnable规定(重写)的方法是run(),没有返回值,不能抛出异常。

       4. 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

3. Thread类介绍

1. Thread类位于java.lang包下,常见的构造方法和字段如下:

  1. Thread():无参的构造方法
  2. Thread(Runnable target):依赖实现Runnable接口对象创建线程对象
  3. Thread(String name):指定创建线程对象的名称
  4. Thread(Runnable target,String name):指定创建线程对象的Runnable接口对象和名字

    MAX_PRIORITY    =10;
    MIN_PRIORITY    =1; 
    NORM_PRIORITY   =5; 

  这些字段给线程分配优先级,都有默认值。。

      获取当前线程:Thread.currentThread()

      获取线程的名字 getName()

      获取线程的IDgetId();

2. 线程的优先级

概念: :如果一个线程的优先级越高,获得CPU资源的机会更大,不等于高优先级的就最先执行,

线程优先级的值为1-10,默认优先级为5

  1. 如何设置优先级呢? 调用方法

    线程对象.getPriority()                   返回线程的优先级

    void setPriority(int newPriority)   更改线程的优先级

 

public static void main(String[] args) {
        Thread main = Thread.currentThread();
        System.out.println("主线程默认优先级:"+main.getPriority());//主线程默认优先级:5
        
        main.setPriority(Thread.MAX_PRIORITY);
        System.out.println("主线程修改后的优先级:"+main.getPriority());//主线程修改后的优先级:10
        
        main.setPriority(7);
        System.out.println("主线程修改后的优先级:"+main.getPriority());//主线程修改后的优先级:7
    }

3.守护线程

  1. 守护线程(精灵线程/后台线程)
  2. 跟普通线程(用户线程)没区别
    唯一区别是:当程序中没有普通线程时,守护线程也就没有用了,会自动死亡(死亡时间不确定),当程序中只剩下守护线程时,JVM可以关闭了
  1. 特点

    ① 一般来说后台线程是为前台线程服务的(例如gc线程);

    ② 如果所有的前台线程都死了,那么后台线程也会自动的死亡;但是前台线程死了,后台线程不一定立即死亡(可能还需要收尸...

    ③ 一个线程的默认状态和创建它的环境线程状态一致(比如:已经设置一个线程为守护线程,那么在该线程里面创建的线程都是守护线程。)

常用方法:

  setDaemon(true): 设置值为true是守护线程。
  isDaemon():   判断当前线程是否是守护线程,返回   true 和 false.
注意:当一个线程处于运行状态的时候,不可以去改变守护状态,否则发生异常

4. 线程终止 join()方法

join方法有三个重载版本:

     join()
    join(long millis)     //参数为毫秒
    join(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

  某线程调用该方法,会让其他线程处于等待状态,让其运行完毕,再执行其他线程.       

       实际上调用join方法是调用了Object的wait方法wait方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。
       对象名.join()

5. 线程礼让   yield()方法

  Thread.yield()
  调用此方法,当前线程会 让出cpu的使用权,优先执行其他线程,该线程也可能会处于被执行的对象。yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会更大而已。

  注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。

6.  sleep()方法

  sleep方法有两个重载版本:

    sleep(long millis)     //参数为毫秒

    sleep(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

  1. sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。

  2.  sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。

  3. 如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,

  因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。

day15
线程安全
线程安全:线程主体中的某段代码同时只有一个线程可以访问,其他线程处于等待状态

解决问题:为了解决多线程下访问资源共享时产生数据安全问题,


同步代码块
语法:

synchronized(参数){

}
参数可以是任意类型的,常用的是this(非静态数据),一般情况下用类的字节码对象(类名.class)(静态的数据)

同步方法
在自定义方法的返回值类型前加synchronized关键词修饰

如果数据是非静态的,普通方法即可

如果数据是静态的,普通方法改为静态方法(加static )

同步锁:Lock
在类中定义一个锁对象: ReentrantLock lock = new ReentrantLock();
在实际业务代码中 先上锁 lock.lock() 执行完业务后 释放锁 lock.unlock();
注意:
1.如果线程访问的是非静态的数据 用 非静态锁和静态锁都可以

4. 线程的同步控制synchronized和lock的区别

1. 用法:synchronized (隐式锁)在需要同步的对象中加入此控制,synchronized可以加在方法和代码块上面,括号中表示要锁的对象。

    lock(显示锁)   需要显示在指定的 起始和终止位置 。 一般使用 ReentrantLock 类作为锁。 加锁 lock(),解锁 unlock() .   finally块一定要解锁,不然会造成死锁情况。

2.性能: synchronized 是Java的关键字,在JVM层面实现了对资源的同步互斥访问 .   而 lock 是一个接口。里面有许多方法。

    比如:尝试获取锁包括立即返回是否成功的tryLock(),以及一直尝试获取的lock()方法和尝试等待指定时间长度获取的方法,比synchronized相对灵活了许多。

3. 机制:synchronized(悲观锁CUP,怕别人抢吃的), 同一时刻不管是读还是写都只能有一个线程对共享资源操作,其他线程只能等待.  当锁的对象成功完成或者抛出异常时,会自动释放锁。

    lock(乐观锁CAS)  Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待。

  • lock():获取锁,如果锁被暂用则一直等待
    
    unlock():释放锁
    
    tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true
    
    tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间
    
    lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,那么可以中断此线程,先去做别的事  

补充重点精华:ReentrentLock 和ReentrentReadWriteLock两个类可重入的锁,即当同一线程获取到锁之后,他在不释放锁的情况下,可以再次获取到当前已经拿到的锁,只需标记获取到锁的次数加一即可;

       ReentrantReadWriteLock是Lock的另一种实现方式,我们已经知道了ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。

       通过在读多,写少的高并发情况下,我们用ReentrantReadWriteLock  分别获取读锁和写锁来提高系统的性能,因为读锁是共享锁,即可以同时有多个线程读取共享资源,而写锁则保证了对共享资源的修改只能是单线程的。

原文地址:https://www.cnblogs.com/gshao/p/10073892.html