知识梳理:线程并发问题&线程安全&线程同步&基于TCP/IP的Socket通信&JSON数据格式&注解

目录:线程并发问题&线程安全&线程同步&基于TCP/IP的Socket通信&JSON数据格式&注解

线程并发问题(线程安全)

线程安全

多线程并发时,多个线程同时操作同一个内存区域(变量),可能会导致的结果不一致的问题;所谓线程安全,指的是在多线程并发操作的时候能保证共享的变量数据一致

并发编程三要素

线程并发时需要保证线程安全,需要满足三大条件:

  • 原子性
  • 可见性
  • 有序性

原子性(Atomicity)

对于一条线程执行来说要保证,要么都成功,要么都失败;对于原子性操作可以通过加锁的方式实现;Synchronized和ReentrantLock保证线程的原子性

可见性(Visibility)

多条线程操作同一个变量时,需要保证其中一条线程对变量的修改,对其他线程也是可见的

有序性(Ordering)

对于每一条线程的执行,内部应该是有序的:

  1. 代码顺序执行
  2. 锁的释放应该在获得之后
  3. 变量读取操作,在修改操作之后

线程同步解决方案

Synchronized

synchronized的关键字使用包含三种方式:

  1. synchronized块:对象锁
  2. synchronized方法:在方法声明的时候使用
  3. 类锁:在静态方法或者同步块中使用类的Class对象

被synchronized鎖住的区域称之为临界区

死锁

​ 由于线程并发,会出现共享变量的情况,如果使用同步锁,对象会被锁住,如果存在多个线程争抢资源时陷入僵局(多个线程在等待被对方线程占有的资源释放时陷入的无限等待状态),这种情况称之为死锁。死锁无法解决,只能尽量避免

public class DeathLock implements Runnable{
    /**
    * 使用静态修饰的原因是希望意向两个对象永远只存在一个实例(不论有多少Runnable对象)
    */
    private static Object obj1 = new Object();
    private static Object obj2 = new Object();
    private int flag;

    public DeathLock(int flag) {
        this.flag = flag;
    }

    @Override
    public void run() {	
        if(flag == 1) {
            synchronized (obj1) {
                System.out.println("线程1锁住了obj1");
                synchronized (obj2) {
                    System.out.println("线程1锁住了obj2,结束。。。。");
                }
            }
        }else if(flag == 2){
            synchronized (obj2) {
                System.out.println("线程2锁住了obj2");
                synchronized (obj1) {
                    System.out.println("线程2锁住了obj1,结束。。。。");
                }
            }
        }
    }

    public static void main(String[] args) {
        DeathLock d1 = new DeathLock(1);
        DeathLock d2 = new DeathLock(2);

        new Thread(d1).start();
        new Thread(d2).start();
    }

}

Lock

提供了跟synchronized相同的功能,也可以对于的对象实现加锁,ReentrantLock粒度控制方面比synchronized更细,同时支持公平锁和非公平锁(默认),synchronized只支持非公平锁

注意事项:

  1. 锁的获取位置一般在try语句块之前
  2. 锁的释放一般放到finally语句块中

ReentrantLock和Synchronized的区别:

  1. ReentrantLock是一个实现类,后者是一个关键字
  2. ReentracntLock需要手动获取锁以及释放锁(粒度控制更细);后者自动加锁,临界区(synchronized锁住的区域)的代码执行完之后自动释放
  3. ReentrantLock支持公平锁以及非公平锁;sychronized只支持非公平锁
  4. synchronized一般用于少量代码同步,ReentrantLock一般用于大量的同步代码块

volatile

在前面的线程并发中,对于原子性的解决方案使用synchronized或lock实现同步;但是对于数据的可见性来说,我们还需要另外处理,关于可见性,实时的将全局变量的更新立即同步给其他线程。

Wait¬ify

线程运行时若需要限时等待,则可以通过sleep()方法设置休眠时间,但是对于一些不定时的等待需求sleep则无法实现;对于这种需求,java.lang.Object类中提供了用于实现等待和唤醒的方法:wait和notify;

  • wait()
  • notify()

在使用wait和notify的时候一定要求确保当前线程持有对象的监视器(对象锁)

wait()和sleep()区别?

  • sleep是来自Thread类中的一个方法;wait是属于Object类的方法
  • sleep是限时等待,在等待的时限到达时自动恢复;而wait是无限等待,必须要等到其他线程调用该对象上的notify方法(或notifyAll)才能继续执行
  • sleep使用时不需要持有对象锁,而wait使用时必须确保当前线程持有该对象的对象锁,否则会有异常(java.lang.IllegalMonitorStateException)

网络协议

网络协议就是计算器网络设备相互之间通信的标准(暗号),在互联网标准组织的推动下,将网络协议分为两种:

  • TCP/IP : 传输控制协议/ip协议
  • UDP:用户数据报协议
TCP/IP协议(打电话)

传输控制协议,是一个安全可靠的互联网协议,需要通信的主机之间需要先建立正确的链接,才能够进行通信,并且改协议能够保证数据传输稳定性(必须的保证信息发送到一台主机,由该主机确认之后才能发送下一条信息),另外该协议也能保证数据传输的有序性(先发送的信息一定先到达)。一般基于C/S架构,存在服务器客户端模式。

应用领域: 远程登录

UDP协议(发快递)

User Diagram Protocol(用户数据报协议),是一个不安全的网络协议,不需要双方之间建立联系,也不保证信息传输的有序性(有可能后发消息先到),传输效率比TCP/IP更高.没有专门服务器和客户端,只有发送端和接收端

应用领域: 广播,视频会议

InetAddress类

IndetAddress是位于java.net包中提供的用于表示ip地址和主机的类,常用方法:

  • getLocalhost() 获取本地主机
  • getByName() 根据提供的主机名或者ip获取InetAddress对象
  • getHostAddress() 获取主机地址
  • getHostName() 获取主机名称

基于TCP/IP的Socket通信

​ Socket(套接字),实际上就是由IP地址跟端口号的结合,通过Socket对象可以实现两台主机之间的通信;Socket分为服务端Socket(java.net.ServerSocket),以及客户端Socket(java.net.Socket)

综合案例:基于TCP/IP的文件服务器实现

服务端

/**
 * 服务器
 * @author mrchai
 *
 */
public class FileServer {

    public static void main(String[] args) throws IOException {

        //准备需要传输的文件对象
        File target = new File("D:\\集团资料\\宣讲\\video\\云计算&大数据\\2017云栖.mp4");

        try(
            //创建并开启文件服务
            ServerSocket ss = new ServerSocket(6789);
            Socket s = ss.accept();
            //创建文件的输入流并包装为缓冲流
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(target));
            //获取基于Socket的输出流
            BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());
        ){
            System.out.println("客户端连接:"+s.getInetAddress().getHostAddress()); 
            System.out.println("开始传输....");
            //每次读取的字节内容
            int b = 0;
            while((b = bis.read()) != -1) {
                bos.write(b);
            }
            System.out.println("传输完成");
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客戶端

/**
 * 客户端
 * @author mrchai
 *
 */
public class FileClient {

    public static void main(String[] args) throws UnknownHostException, IOException {

        File f = new File("C:\\Users\\Administrator\\Desktop\\a.mp4");

        try(
            //连接指定ip指定端口服务
            Socket s = new Socket("192.168.7.141",6666);
            //获取基于Socket的输入流并包装为缓冲流
            BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
            //获取目标文件的输出流
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(f));
        ){
            System.out.println("开始接收...");
            int b = 0;
            while((b = bis.read()) != -1) {
                bos.write(b);
            }
            System.out.println("接收完成!");
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

}

HttpURLConnection

HttpURLConnection用于获取一个http连接,是一个http客户端工具类,HttpURLConnection从URLConnection继承而来,通过该类可以读取服务端响应的数据(文本,视频,音频,图片等资源)。

封装通用HttpUtils工具

对于大多数请求,主要是获取服务端文本数据,因此封装以下工具类用户获取网络资源:

/**
 * HTTP工具类,可以通过该工具类轻松访问在线API地址
 * 并获取响应的数据
 * @author mrchai
 */
public class HttpUtils {


    static class CallRemoteData implements Callable<String>{

        private String url;

        public CallRemoteData(String url) {
            this.url = url;
        }

        @Override
        public String call() throws Exception {
            //根据提供的url地址构建一个URL对象
            URL urlConn = new URL(url);
            //打开连接
            HttpURLConnection conn = (HttpURLConnection)urlConn.openConnection();
            //设置请求方式
            conn.setRequestMethod("GET");
            //获取响应的状态码
            int code = conn.getResponseCode();
            if(code == HttpURLConnection.HTTP_NOT_FOUND) {
                throw new Exception("请求的资源不存在!");
            }
            if(code == HttpURLConnection.HTTP_INTERNAL_ERROR) {
                throw new Exception("服务器内部错误");
            }
            if(code != HttpURLConnection.HTTP_OK) {
                throw new Exception("未知错误!");
            }
            BufferedReader br = null;
            try {
                //获取连接的输入流
                InputStream is = conn.getInputStream();
                InputStreamReader isr = new InputStreamReader(is,"utf-8");
                br = new BufferedReader(isr);
                //创建一个可变长字符串
                StringBuffer sbf = new StringBuffer();
                String str = "";
                while((str = br.readLine()) != null) {
                    sbf.append(str).append("\r\n");
                }
                return sbf.toString();
            }finally{
                if(br != null)br.close();
                //断开连接
                conn.disconnect();
            }
        }
    }

    /**
	 * 根据提供的url地址字符串获取服务端响应回来的字符串数据
	 * @param url
	 * @return
	 */
    public static String getData(String url) {
        try {
            //创建Callable对象
            Callable<String> call = new CallRemoteData(url);
            FutureTask<String> task = new FutureTask<>(call); //Runnable
            //创建并启动线程
            new Thread(task).start();
            //获取执行结果并返回
            return task.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {

        String url = "http://music.softeem.top/list";
        String json = HttpUtils.getData(url);
        System.out.println(json);
    }
}

JSON数据格式

​ JSON(JavaScript Object Notation)是一种与语言平台无关的数据交换格式,有着比XML更为轻量级的数据表现形式,是目前大多数互联网应用程序之间的数据交换格式的首选。

FastJSON使用

fastJSON是由阿里巴巴开源的JSON库,号称全世界最快的json插件,常用方法:

  • toJSONString(Object) 将一个Java对象转换为Json字符串
  • parseObject(String json,Class clazz) 将一个json字符串转换为Java对象
  • parseArray(String json,Class clazz) 将一个json字符串转换为Java集合
public static void main(String[] args) {

    User u = new User();
    u.setId(10);
    u.setUsername("softeem");
    u.setPassword("123456");
    u.setVip(true);

    //1.将Java对象转换为json字符串
    String json  =JSON.toJSONString(u);
    System.out.println(json);

    //2.将json字符串转化为Java对象
    json = "{\"id\":20,\"password\":\"666\",\"username\":\"admin\",\"vip\":false}"; 
    u = JSON.parseObject(json, User.class);
    System.out.println(u);

    //3.如何将集合转换为json字符串
    List<User> list = new ArrayList<>();
    list.add(new User(1, "softeem", "123456", true));
    list.add(new User(2, "james", "000123", false));
    list.add(new User(3, "kobe", "888123", false));
    list.add(new User(4, "面筋哥", "666123", true));
    json = JSON.toJSONString(list);
    System.out.println(json);

    //4.如何将一个json数组转换为一个Java集合
    list = JSON.parseArray(json,User.class);
    System.out.println(list);

    //5.若解析的json字符串找不到对应的Java类时?
    String json2 = "{\"sid\":10,\"name\":\"孙悟空\"}";
    //将json字符串解析为Java对象;JSONObject实际上就是一个Map集合
    JSONObject obj = JSON.parseObject(json2);
    int sid = obj.getInteger("sid");
    String name = obj.getString("name");
    System.out.println(sid+"---"+name);

}

注解概述

​ 注解(Annotation),是jdk5之后新增一项技术,可以通过在Java类,方法,属性等元素上加上注解,实现一些特定功能:编译检查,丰富文档化注释的内容,实现项目特定程序配置。注解只需要少量代码的存在即可;注释即解释;注解通常不会影响程序的正常逻辑,只是一种标记,Java中的注解通常是给编译器进行识别的

内置注解

jdk中包含一些内置的注解类型:

  • @Override:检查方法是否属于重写
  • @Depracted : 标记类,属性,方法为过时
  • @SupressWarning:抑制编译抛出的警告信息
  • @FunctionalInterface:java8新增函数式接口的检查注解

自定义注解

java中除了包含一些内置注解以外,还允许开发者自定义注解来解决程序配置问题,如果需要使用自定义注解,只需要使用@interface创建即可:

public @interface MyAnno { }

你可能感兴趣的:(知识梳理:线程并发问题&线程安全&线程同步&基于TCP/IP的Socket通信&JSON数据格式&注解)