Java大总结(三)—— 集合框架、流、多线程

一、集合框架

1. javabean特性、作用

(1)所有的类必须放在一个包中,在WEB中没有包的是不存在的

(2)所有的类必须声明为public class,这样才能够被外部所访问;

(3)类中所有的属性都必须封装,即:使用private声明;

(4)封装的属性如果需要被外部所操作,则必须编写对应的setter、getter方法;

(5)一个JavaBean中至少存在一个无参构造方法,此为JSP中的标签所使用。

2. List、Set、Map的区别

Set接口:

无序可变的数组,不允许添加重复元素,如果视图把两个相同的元素加入到同一个集合中,add方法返回false.

set判断对象是否相同使用equals方法, 就是说返回true就表示两个对象相同 set不会接受.

List接口

一个 List 是一个元素有序的、可以重复、可以为 null 的集合(有时候我们也叫它“序列”)

Map接口

Map是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象。 Map没有继承于Collection接口 从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。

3. List的常用子类、区别

ArrayList

代表长度可以改变得数组。可以对元素进行随机的访问,向ArrayList()中插入与删除元素的速度慢。
LinkedList:在实现中采用链表数据结构。插入和删除速度快,访问速度慢。

Vector

Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。

4. Set的常用子类、区别

HashSet

不能保证元素的排列顺序,

不是同步的(线程不安全)

集合元素可以是null,但只能放入一个null

当向HashSet集合中存入一个元素时,HashSet会调用该对象的HashCode()方法来得到该对象的HashCode值,然后根据HashCode值来决定该对象在HashSet中的储存位置.

判断两个HashSet元素相等的标准是两个对象通过equals方法比较相等,并且两个对象的HashCode()方法返回值相等

如果要一个对象放入HashSet中,重写该对象对应类的equals方法,也应该重写HashCode()方法.

LinkedHashSet

LinkedHashSet集合同样是根据HashCode()方法来决定元素的存储位置,但是它同时使用链表维护元素的次序.这样使得元素看起来像是以插入顺序保存的,当遍历该集合的时候,LinkedHashSet将会以元素的添加顺序访问集合的元素.

LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍逊色于HashSet.

TreeSet

TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。

TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。
TreeSet判断两个对象不相等的方式是两个对象通过equals方法返回false,或者通过CompareTo方法比较没有返回0

5. Map的常用子类、区别

HashTable

类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值。为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。

HashMap

Map基于散列表的实现。插入和查询“键值对”的开销是固定的。可以通过构造器设置容量capacity和负载因子load factor,以调整容器的性能。

LinkedHashMap

类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点。而在迭代访问时发而更快,因为它使用链表维护内部次序。

TreeMap

基于红黑树数据结构的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparabel或Comparator决定)。TreeMap的特点在 于,你得到的结果是经过排序的。TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。

6. Collection和Collections的区别、Collections的常用方法

区别

Collection是集合类的上级接口,继承与他有关的接口主要有List和Set

Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全等操作

Collections的常用方法

static void swap(List list,int i,int j) 将指定列表中的两个索引进行位置互换

static void shuffle(List list) 随机置换

static void fill(List list,Object obj) 使用指定对象填充指定列表的所有元素

static void copy(List dest,List src) 把源列表中的数据覆盖到目标列表

static int binarySearch(List list,Object key) 使用二分查找法查找指定元素在指定列表的索引位置

7. TreeSet的对象的排序(Comparable、Compartor)

自然排序Comparable

TreeSet集合,存入整数,进行排序

import java.util.TreeSet;

/*
 * TreeSet:能够对元素按照某种规则进行排序。
 * 排序有两种方式
 * A:自然排序
 * B:比较器排序
 * 
 * TreeSet集合的特点:排序和唯一
 * 
 * 通过观察TreeSet的add()方法,我们知道最终要看TreeMap的put()方法。
 */
public class TreeSetDemo {
    public static void main(String[] args) {
        // 创建集合对象
        // 自然顺序进行排序
        TreeSet<Integer> ts = new TreeSet<Integer>();

        // 创建元素并添加
        // 20,18,23,22,17,24,19,18,24
        ts.add(20);
        ts.add(18);
        ts.add(23);
        ts.add(22);
        ts.add(17);
        ts.add(24);
        ts.add(19);
        ts.add(18);
        ts.add(24);

        // 遍历
        for (Integer i : ts) {
            System.out.println(i);
        }
    }
}

在TreeSet中,存入学生对象的数据,并根据学生的年龄从小到大进行排序

  • 创建一个学生类,实现自然排序Comparable接口,传入的泛型为Student
/*
 * 如果一个类的元素要想能够进行自然排序,就必须实现自然排序接口
 */
public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student() {
        super();
    }

    public Student(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Student s) {
        // return 0;
        // return 1;
        // return -1;

        // 这里返回什么,其实应该根据我的排序规则来做
        // 按照年龄排序,主要条件
        int num = this.age - s.age;
        // 次要条件
        // 年龄相同的时候,还得去看姓名是否也相同
        // 如果年龄和姓名都相同,才是同一个元素
        int num2 = num == 0 ? this.name.compareTo(s.name) : num;
        return num2;
    }
}
  • 编写测试类
import java.util.TreeSet;

/*
 * TreeSet存储自定义对象并保证排序和唯一。
 * 
 * A:你没有告诉我们怎么排序
 *         自然排序,按照年龄从小到大排序
 * B:元素什么情况算唯一你也没告诉我
 *         成员变量值都相同即为同一个元素
 */
public class TreeSetDemo2 {
    public static void main(String[] args) {
        // 创建集合对象
        TreeSet<Student> ts = new TreeSet<Student>();

        // 创建元素
        Student s1 = new Student("linqingxia", 27);
        Student s2 = new Student("zhangguorong", 29);
        Student s3 = new Student("wanglihong", 23);
        Student s4 = new Student("linqingxia", 27);
        Student s5 = new Student("liushishi", 22);
        Student s6 = new Student("wuqilong", 40);
        Student s7 = new Student("fengqingy", 22);

        // 添加元素
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
        ts.add(s6);
        ts.add(s7);

        // 遍历
        for (Student s : ts) {
            System.out.println(s.getName() + "---" + s.getAge());
        }
    }
}

再更改需求添加学生数据,并根据学生的姓名长度,从小到大排序

public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student() {
        super();
    }

    public Student(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Student s) {
        // 主要条件 姓名的长度
        int num = this.name.length() - s.name.length();
        // 姓名的长度相同,不代表姓名的内容相同
        int num2 = num == 0 ? this.name.compareTo(s.name) : num;
        // 姓名的长度和内容相同,不代表年龄相同,所以还得继续判断年龄
        int num3 = num2 == 0 ? this.age - s.age : num2;
        return num3;
    }
}

比较器排序Compartor

添加学生数据,并根据学生的姓名长度,从小到大排序.改为比较器排序做.

  • 创建学生类
public class Student {
    private String name;
    private int age;

    public Student() {
        super();
    }

    public Student(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
  • 创建一个比较器接口的子类对象
 import java.util.Comparator;
 
 public class MyComparator implements Comparator<Student> {
 
     @Override
     public int compare(Student s1, Student s2) {
         // int num = this.name.length() - s.name.length();
         // this -- s1
         // s -- s2
         // 姓名长度
         int num = s1.getName().length() - s2.getName().length();
         // 姓名内容
         int num2 = num == 0 ? s1.getName().compareTo(s2.getName()) : num;
         // 年龄
         int num3 = num2 == 0 ? s1.getAge() - s2.getAge() : num2;
         return num3;
     }
 
 }
  • 编写测试类
import java.util.TreeSet;

/*
 * 需求:请按照姓名的长度排序
 * 
 * TreeSet集合保证元素排序和唯一性的原理
 * 唯一性:是根据比较的返回是否是0来决定。
 * 排序:
 *         A:自然排序(元素具备比较性)
 *             让元素所属的类实现自然排序接口 Comparable
 *         B:比较器排序(集合具备比较性)
 *             让集合的构造方法接收一个比较器接口的子类对象 Comparator
 */
public class TreeSetDemo {
    public static void main(String[] args) {
        // 创建集合对象
        //TreeSet<Student> ts = new TreeSet<Student>(); //自然排序
        // public TreeSet(Comparator comparator) //比较器排序
        TreeSet<Student> ts = new TreeSet<Student>(new MyComparator());



        // 创建元素
        Student s1 = new Student("linqingxia", 27);
        Student s2 = new Student("zhangguorong", 29);
        Student s3 = new Student("wanglihong", 23);
        Student s4 = new Student("linqingxia", 27);
        Student s5 = new Student("liushishi", 22);
        Student s6 = new Student("wuqilong", 40);
        Student s7 = new Student("fengqingy", 22);
        Student s8 = new Student("linqingxia", 29);

        // 添加元素
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
        ts.add(s6);
        ts.add(s7);
        ts.add(s8);

        // 遍历
        for (Student s : ts) {
            System.out.println(s.getName() + "---" + s.getAge());
        }
    }
}

8. 堆、栈、队列、链表、二叉树

堆(heap),是一类特殊的数据结构的统称。它通常被看作一棵树的数组对象。在队列中,调度程序反复提取队列中的第一个作业并运行,因为实际情况中某些时间较短的任务却可能需要等待很长时间才能开始执行,或者某些不短小、但很重要的作业,同样应当拥有优先权。而堆就是为了解决此类问题而设计的数据结构。

栈是具有一定操作约束的线性表,只在一端(栈顶Top)做插入删除,后进先出

队列

队列是具有一定操作约束的线性表,只能在一端插入,在另一端删除

队列是先进先出(FIFO)的线性结构

链表

定义节点类

public class Node {
    public int data;//数据
    public Node next;//指向下一个结点的指针

    public Node(int data) {
        this.data = data;
    }
}

定义操作类

public class NodeList {
    private Node head;//头结点

    public NodeList(Node head) {
        this.head = head;
    }
    //添加结点
    public void addNode(int data){
        Node newNode = new Node(data);
        if (head == null){
            head = newNode;
            return;
        }
        Node tmp = head;
        while(tmp.next != null){
            tmp = tmp.next;
        }
        tmp.next = newNode;
    }
    //删除结点
    public void delNode(Node node){
        if (node == null){
            return;
        }
        node.data = node.next.data;
        node.next = node.next.next;
    }
}
二叉树

二叉树是一个每个最结最多只能有两个分支的树,左边的分支称之为左子树,右边的分支称之为右子树。

二、流

1. 流的概念、分类

概念

数据的流向,流动称之为流

分类

按方向分:

输入流(InputStream、Reader)

输出流(OutputStream、Writer)

按节点分:

字节流(***Stream)

字符流(Reader/Writer,不能处理音频、视频等)

按功能分:

处理流(以节点流为参数的流)

节点流

2. 常用流的特性、API

输å¥è¾“出流的层次ç"“æž„

Readerå’ŒWriter

1)对文件进行操作:FileInputStream(字节输入流),FileOutputStream(字节输出流),FileReader(字符输入流),FileWriter(字符输出流)

2)对管道进行操作:PipedInputStream(字节输入流),PipedOutStream(字节输出流),PipedReader(字符输入流),PipedWriter(字符输出流)

PipedInputStream的一个实例要和PipedOutputStream的一个实例共同使用,共同完成管道的读取写入操作。主要用于线程操作。

3)字节/字符数组:ByteArrayInputStream,ByteArrayOutputStream,CharArrayReader,CharArrayWriter是在内存中开辟了一个字节或字符数组。

4)Buffered缓冲流:BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter,是带缓冲区的处理流,缓冲区的作用的主要目的是:避免每次和硬盘打交道,提高数据访问的效率。

5)转化流:InputStreamReader:在读入数据的时候将字节转换成字符。

 OutputStreamWriter:在写出数据的时候将字符转换成字节。

6)数据流:DataInputStream,DataOutputStream。

因为平时若是我们输出一个8个字节的long类型或4个字节的float类型,那怎么办呢?可以一个字节一个字节输出,也可以把转换成字符串输出,但是这样转换费时间,若是直接输出该多好啊,因此这个数据流就解决了我们输出数据类型的困难。数据流可以直接输出float类型或long类型,提高了数据读写的效率。

7)打印流:printStream,printWriter,一般是打印到控制台,可以进行控制打印的地方和格式,其中的 print方法不会抛出异常,可以通过checkError方法来查看异常。

8)对象流:ObjectInputStream,ObjectOutputStream,把封装的对象直接输出,而不是一个个在转换成字符串再输出。

9)RandomAccessFile随机访问文件

10)ZipInputStream、ZipOutputStream读取zip文档 getNextEntry、putNextEntry 得到或创建ZipEntry对象。

三、多线程

1. 线程和进程的概念

线程

在Java中,“线程”指两件不同的事情:

  • java.lang.Thread类的一个实例
  • 线程的执行
进程

进程是拥有一个执行流,或多个执行流的线程组。

进程是一个能独立运行的基本单位,同时也是系统分配资源基本单位

2. 如何创建多线程

方法一:继承Thread类创建线程类
    package com.thread;  
      
    public class FirstThreadTest extends Thread{  
        int i = 0;  
        //重写run方法,run方法的方法体就是现场执行体  
        public void run()  
        {  
            for(;i<100;i++){  
            System.out.println(getName()+"  "+i);  
              
            }  
        }  
        public static void main(String[] args)  
        {  
            for(int i = 0;i< 100;i++)  
            {  
                System.out.println(Thread.currentThread().getName()+"  : "+i);  
                if(i==20)  
                {  
                    new FirstThreadTest().run();  
                    new FirstThreadTest().run();  
                }  
            }  
        }  
      
    }  
方法二:通过Runable接口创建线程类
    package com.thread;  
      
    public class RunnableThreadTest implements Runnable  
    {  
      
        private int i;  
        public void run()  
        {  
            for(i = 0;i <100;i++)  
            {  
                System.out.println(Thread.currentThread().getName()+" "+i);  
            }  
        }  
        public static void main(String[] args)  
        {  
            for(int i = 0;i < 100;i++)  
            {  
                System.out.println(Thread.currentThread().getName()+" "+i);  
                if(i==20)  
                {  
                    RunnableThreadTest rtt = new RunnableThreadTest();  
                    new Thread(rtt,"新线程1").start();  
                    new Thread(rtt,"新线程2").start();  
                }  
            }  
      
        }  
      
    }  
方法三:通过Callable和FutureTask创建线程
package com.demo;
 
import java.util.concurrent.Callable;  
import java.util.concurrent.ExecutionException;  
import java.util.concurrent.FutureTask;  
  
public class CallableThreadTest implements Callable<Integer>  
{  
  
    public static void main(String[] args)  
    {  
        CallableThreadTest ctt = new CallableThreadTest();  
        FutureTask<Integer> ft = new FutureTask<Integer>(ctt);  
//        Thread thread = new Thread(ft,"有返回值的线程");
//        thread.start();
        for(int i = 0;i < 100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i);  
            if(i==20)  
            {  
                new Thread(ft,"有返回值的线程").start();  
            }  
        }  
        try  
        {  
            System.out.println("子线程的返回值:"+ft.get());  
        } catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        } catch (ExecutionException e)  
        {  
            e.printStackTrace();  
        }  
  
    }  
  
    @Override  
    public Integer call() throws Exception  
    {  
        int i = 0;  
        for(;i<100;i++)  
        {  
            System.out.println(Thread.currentThread().getName()+" "+i);  
        }  
        return i;  
    }  
  
}  
方法四:通过线程池创建线程
/**
 * 
 */
package com.demo;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/**
 * @author Maggie
 *
 */
public class ThreadPool 
{
	/* POOL_NUM */
	private static int POOL_NUM = 10;
	
	/**
	 * Main function
	 */
	public static void main(String[] args)
	{
		ExecutorService executorService = Executors.newFixedThreadPool(5);
		for(int i = 0; i<POOL_NUM; i++)
		{
			RunnableThread thread = new RunnableThread();
			executorService.execute(thread);
		}
	}
}
 
class RunnableThread implements Runnable
{
	private int THREAD_NUM = 10;
	public void run()
	{
		for(int i = 0; i<THREAD_NUM; i++)
		{
			System.out.println("线程" + Thread.currentThread() + " " + i);
		} 
	}
}

3. 线程的生命周期

img

4. 线程的交互

线程交互中用到的三个基本函数:

void notify();唤醒在此对象监视器上等待的单个线程。

void notifyAll();唤醒在此对象监视器上等待的所有线程。

void wait();导致当前的线程等待,直到其他线程调用此对象的notify()或者notifyAll()方法。

void wait(long timeout);wait()的重载版本,同样导致当前线程等待,直到其他线程调用此对象的notify()或者notifyAll()方法,或者等待超过指定的时间后不再等待。

void wait(long timeout,int nanos);wait()的重载版本,同样导致当前线程等待,直到其他线程调用此对象的notify()或者notifyAll()方法,或者等待超过某个实际的时间后不再等待,或者其他某个线程中断当前线程。

在线程交互中需要注意的是:

wait()、notify()、notifyAll()方法必须从同步环境中调用,线程不能调用对象上等待或通知的方法,除非此线程拥有对象的锁。

线程通过执行对象上的wait()方法获得等待列表,此后线程不再执行任何其他指令,直到调用对象的notify()方法为止。当多个线程在同一个对象上等待的时候,则notify()将随机的唤醒其中的一个线程来使其运行。若没有线程等待,则不做任何处理。

要执行wait()方法,前提是当前线程拥有此对象的锁,当执行wait()方法之后,此线程即释放此对象的锁,以便于其他线程中调用notify()或者notifyAll()来唤醒此线程。然而调用notify()时,不意味着线程会释放对象的锁。如果线程依然在完成同步代码,那么线程在完成同步代码块时不会释放对象的锁。所以,notify()并不意味着锁变得可用。

注意的问题:

wait(),notify(),notifyAll()不属于Thread类,而属于Object类,也就是说每个对象都有着三个方法。因为每个对象都有自己的锁,锁是每个对象的基础,操作锁的方法也是每个对象的基础。

wait()导致的是当前的线程的等待,而不是调用对象所在的线程类等待,也就是哪个线程拥有此对象的锁才能调用wait()方法。

notify()唤醒当前对象监视器上等待的单个线程,如果在此对象上有多个线程在等待,则随机的唤醒期中一个线程,直到当前线程放弃此对象上的锁,才能继续执行被唤醒的线程。notify()也和wait()一样,必须由持有此对象的锁的线程来调用,notifyAll()也一样,但是notifyAll()会唤醒在这个对象等待的所有线程,并从这个线程中随机的选择一个。

这三个方法都需要和synchronized配合使用,也就是必须先获取对象的锁,才能调用者三个方法。

5. 线程的调度

(1)定义

线程调度是指系统为线程分配处理器使用权的过程。主要调度方式有:协同式线程调度 和 抢占式线程调度

(2)协同式线程调度(Cooperative Threads-Scheduling)

定义

在此多线程系统中,线程的执行时间由线程本身控制,线程把自己的工作执行完之后,要主动通知系统切换到另外一个线程上。

优劣势

好处:实现简单,由于线程要把自己事情干完才会进行线程切换,切换操作对线程自己是可知的,所以没什么线程同步问题。
坏处:线程执行时间不可控,如果一个线程编写有问题,一直不告知系统进行线程切换,那么程序就会一直阻塞在那里。

(3)抢占式线程调度(Preemptive Threads-Scheduling)

定义

在此多线程系统中,每个线程将由系统来分配时间,线程的切换不由线程本身决定(Java中,Thread.yield() 可以让出执行时间,但是线程无法获取执行时间)。

优势

线程执行时间是系统可控的,也不会出现一个线程导致整个进程阻塞的问题。Java使用的线程调度方法就是这个。

(4)Java线程优先级

虽然Java 线程调度是系统自动完成的, 但我们还是可以建议系统给某些线程多分配一点执行时间,另外一些线程则可以少分配一点——这项操作可以通过设置线程优先级来完成。Java语言一共设置了10个级别的优先级,在两个线程同时处于 Ready状态,优先级越高的线程越容易被系统选择执行。

不过线程优先级并不是太靠谱,原因是因为Java的线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于 操作系统,虽然现在很多os 都提供了线程优先级,但不见得和 能与 java线程的优先级一一对应。如 Solaris中有 2^32 种优先级,而windows只有7种 。

(5)Java 线程优先级与Windows线程优先级对应关系

下表显示了 Java线程优先级 与 Windows 线程优先级之间的对应关系:

这里写图片描述

(6)优先级可能会被系统自行改变

上文说到的“Java线程优先级并不是太靠谱”,不仅仅是在说一些平台上不同的优先级实际会变得相同这一点,还有其他情况让我们不能太依赖优先级:优先级可能会被系统自行改变。

例如:在Windows 中存在一个称为 “优先级推进器”的功能,作用是 当系统发现一个线程执行得特别勤奋的话,可能会越过线程优先级去为它分配执行时间。因此,我们不能在程序中通过优先级来完全准备判断一组状态都为Ready的线程将会先执行哪个。

6. 线程的同步与锁(对象锁),死锁的概念

实例:交替打印ABABAB
public class Test {
    static final Object object = new Object();
    public static void main(String[] args){
        //线程1
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0;i<5;i++){
                    synchronized (object){
                        System.out.println("A");
                        object.notify(); //唤醒线程2
                        try {
                            object.wait();//线程1进入等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();
        //线程2
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 0;i<5;i++){
                    synchronized (object){
                        System.out.println("B");
                        object.notify();//唤醒线程1
                        try {
                            object.wait();//线程2进入等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();
    }
}

在计算机科学中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。锁旨在强制实施互斥排他、并发控制策略。

锁通常需要硬件支持才能有效实施。这种支持通常采取一个或多个原子指令的形式,如"test-and-set", “fetch-and-add” or “compare-and-swap””。这些指令允许单个进程测试锁是否空闲,如果空闲,则通过单个原子操作获取锁。

死锁

至少两个任务中的每一个都等待另一个任务持有的锁的情况锁粒度是衡量锁保护的数据量大小,通常选择粗粒度的锁(锁的数量少,每个锁保护大量的数据),在当单进程访问受保护的数据时锁开销小,但是当多个进程同时访问时性能很差。因为增大了锁的竞争。相反,使用细粒度的锁(锁数量多,每个锁保护少量的数据)增加了锁的开销但是减少了锁竞争。例如数据库中,锁的粒度有表锁、页锁、行锁、字段锁、字段的一部分锁

7. 线程的资源共享

1.如果每一个线程执行的代码相同,可以使用同一个runnable对象,这个对象中有那个共享数据(买票系统)
2.如果每一个线程执行的代码不相同,这时候需要不同的Runnable对象,有以下两种方式来实现这些Runnable对戏之间的数据共享。
(1)、将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。
(2)、将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。
(3)、上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。
(4)、总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。
3.极端且简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享
在线程操作中由于其操作的不确定性,所以提供了一个方法,可以取得当前操作线程:
public static Thread currentThread();

8. 线程组、守护线程(了解)

线程组(ThreadGroup)

​ 在一个系统中,如果线程数量很多,而且功能分配比较明确,就可以将相同功能的线程放置在同一个线程组里。下面通过示例来说明。

守护线程(Daemon)

​ 守护线程是一种特殊的线程,就像它的名字一样,它是系统的守护者。在后台默默的完成一些系统性的服务,比如垃圾回收线程、JIT线程就可以理解为守护线程。与之相应的是用户线程,用户线程可以认为是系统的工作线程,它会完成这个程序应该要完成的业务操作。如果用户线程全部结束,则意味着这个程序实际上无事可做了。守护线程要守护的对象已经不存在了,那么整个应用程序就应该结束。因此一个Java应用内只有守护线程时,Java虚拟机就会自然退出。

9. 线程池的原理,API

为什么使用线程池

在一些需要使用线程去处理任务的业务场景中,如果每一个任务都创建一个线程去处理,任务处理完过后,把这个线程销毁,这样会产生大量的线程创建,销毁的资源开销。使用线程池能有效的控制这种线程的创建和销毁,而且能够对创建的线程进行有效的管理。

相关API介绍

Executor接口

主要是用来执行提交的任务。下面是接口定义:

public interface Executor {
    void execute(Runnable command);
}

ExecutorService接口

ExecutorService接口是Executor接口的一个子接口,它在Executor接口的基础上增加了一些方法,用来支持对任务的终止管理以及对异步任务的支持。

public interface ExecutorService extends Executor {
    void shutdown();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

AbstractExecutorService 抽象类

AbstractExecutorService实现了ExecutorService,并基于模板方法模式对一些方法给出了实现。是我们接下来要提到的线程池类ThreadPoolExecutor的直接父类。

ThreadPoolExecutor类

ThreadPoolExecutor通常就是我们所说的线程池类,Java的线程池就是用过这个类进行创建的。下面分析的线程池的运行原理,也是基于这个类来进行分析的。

ScheduledExecutorService接口

ScheduledExecutorService接口是ExecutorService子接口,定义了线程池基于任务调度的一些方法

public interface ScheduledExecutorService extends ExecutorService {

    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                          long delay, TimeUnit unit);
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
}

ScheduledThreadPoolExecutor类

ScheduledThreadPoolExecutor继承了ThreadPoolExecutor类,并且实现了ScheduledExecutorService接口,对任务调度的功能进行了实现。

Executors类

Executors可以认为是线程池的工厂类,里面提供了静态方法对线程池进行创建。
下面列出常用的几种线程池创建方法:

//固定线程大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}
//单个线程线程池
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}
//无上限线程线程池
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}
//基于任务调度的线程池(还有其他类型的任务调度线程池,这里不列举了)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}

10. 生产者与消费者模式(原理)

生产者-消费者模式在系统交互方面,有几个特点:
1、系统解耦
2、解决并发问题
3、不需要关心对方系统何时处理数据,处理结果如何

生产者-消费者模式通过引入一个阻塞队列这个第三方组件来做到解耦和处理并发。
生产者只需要往队列里面塞数据,消费者只需要从队列中读取数据,生产者再也无需关注消费者处理数据的速度是如何了。生产者和消费者已经是完全独立的了。

利用某些队列特性,当生产者速度太快的话,数据超过了队列的最大阀值,那么可以自动阻塞住生产者(当然也可以设置线程阻塞的超时时间,防止消费者挂掉了,一直不处理队列中的数据,生产者),一直等到消费者先消费一些数据。

11. 单例模式

定义

确保对象的唯一性,比如说买火车票,票源只有一个,购票方式可以有多个

分类

饿汉式

private static Ticket ticket = new Ticket();
	public static Ticket getInstance() {
    	return ticket;
}

懒汉式

public synchronized static Ticket getInstance() {//synchronized 加锁
    if(ticket == null) {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ticket = new Ticket();
    }
    return  ticket;
}
如何确保对象唯一

将构造方法私有化

提供公共的静态的接口来获取该类型的对象

在接口中返回对象的实例

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值