线程下、单例模式及网络

线程下、单例模式及网络

*大纲

1、线程

a、重入锁:ReentrantLock
b、闭锁,或称倒计时门栓:CountDownLatch
c、循环栅栏或称关卡:CyclicBarrier
d、信号量:Semaphore

2、单例模式

a、特点:
b.分类
1)、懒汉式单例
*1在getInstance方法上加同步
*2、双重检查锁定
*3、静态(类级)内部类

2)、饿汉式单例
3)、单例和枚举
c、饿汉式和懒汉式区别

3、网络

a、网络模型
b、网络协议
两类传输协议:TCP、UDP
c、网络编程
基于Socket的java网络编程
1)何为Socket
2)Socket通讯过程
3)Socket的创建
4)tcp实例

1. 线程(续)

juc 中的大部分类是通过无锁并发实现的(没有用synchronized)

CAS 机制 compare And swap 比较并交换

synchronized 可以称之为悲观锁
cas 体现的是乐观锁
首先不会给共享资源加锁,而是做一个尝试
先拿到旧值,查看旧值是否跟共享区域的值相等
如果不等,那么说明别的线程改动了共享区域的值,我的修改失败
如果相等,那么就让我的修改成功
如果修改失败,没关系,重新尝试

    java    10行

    int var5;
       // 修改失败,没关系,重新尝试 自旋
        do {
           // 获取共享区域的最新值
            var5 = this.getIntVolatile(var1, var2); // 10
                    // 比较并交换                                      最新值   最新值+1
        } while(! this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;

a. 重入锁 ReentrantLock

.lock() 加锁
.unlock() 解锁
例子:

    java    36行

package Lock1;

import java.util.concurrent.locks.ReentrantLock;

public class CountDownLock {
    static int i=0;
    public static void main(String[] args) throws InterruptedException {
        //建对象
        ReentrantLock down = new ReentrantLock();
        Thread s1=new Thread(()->{
            for (int j = 0; j <1000 ; j++) {
            try {
                down.lock();//加锁
                i++;
            }finally {
                down.unlock();//解锁,放在finally语句块里目的是防止前边出现异常,解锁一定能被执行。
            }
            }

        });
        new Thread(()->{
            for (int j = 0; j <1000 ; j++) {

                try {
                    down.lock();
                    i--;
                }finally {
                    down.unlock();
                }
            }
        }).start();
        s1.start();
        System.out.println(i);
    }
}

synchronized 性能上比较 ReentrantLock 在高并发下低,ReentrantLock的内存占用会高一些

b. CountDownLatch

countdown 倒计时

当希望多个线程执行完毕后,再接着做下一步操作时,
例子:

    java    43行

package Latch1;

import java.util.concurrent.CountDownLatch;

public class CountDownLatch1 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch Latch = new CountDownLatch(3);
        new Thread(()->{
            System.out.println("1开始");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("1完成准备");
            Latch.countDown();
        }).start();
        new Thread(()->{
            System.out.println("2开始");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("2完成准备");
            Latch.countDown();
        }).start();
        new Thread(()->{
            System.out.println("3开始");
            try {
                Thread.sleep(8000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("3完成准备");
            Latch.countDown();
        }).start();
//主线程等待,直至倒计时为0
        Latch.await();
        System.out.println("开始进入游戏");
    }
}

一个应用例子:模拟10个玩家加载进度

    java    28行

public static void main(String[] args) throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(10);
    String[] all = new String[10];

    for (int j = 0; j < 10; j++) {
        int x = j;
        new Thread(()->{
            Random r = new Random();
            for (int i = 0; i <= 100; i++) {
                try {
                    Thread.sleep(r.nextInt(100));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (all){
                    all[x]=(i+"%");
                    System.out.print("\r"+Arrays.toString(all));
                }
            }
            latch.countDown();

        }).start();
    }

    latch.await();
    System.out.println("\nend...");
}

c. 循环栅栏

    java    40行

// CyclicBarrier   可循环的 屏障(栅栏)
// 当满足CyclicBarrier设置的线程个数时,继续执行,没有满足则等待
package Barrier1;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrier1 {
    public static void main(String[] args) {
        CyclicBarrier Barrier = new CyclicBarrier(2);
        new Thread(()->{
            System.out.println("甲方先行");
            try {
                Barrier.await();//数量不足2,因而得在此等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("甲方继续前进");
        }).start();
        new Thread(()->{
            System.out.println("乙方开始前进");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                Barrier.await();//两秒后,线程数满足要求,继续运行
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("乙方继续前进");
        }).start();
    }
}

与倒计时锁的区别:倒计时锁只能使用一次,倒计时结束这个对象就没用了。
而循环栅栏可以重复利用。

d. 信号量

    java    27行

package Phorn1;

import java.util.concurrent.Semaphore;

public class Semaphone1 {
    public static void main(String[] args) {
        Semaphore phore = new Semaphore(3);//最大允许三个线程一起进行
        for (int i = 0; i <9 ; i++) {
            new Thread(()->{
               try {
                   try {
                       phore.acquire();//获得信号量
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   System.out.println(Thread.currentThread().getName() + "开始进行");
                   Thread.sleep(2000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               } finally {
                   phore.release();//释放锁
               }
            }).start();
        }
    }
}

2.单例模式

单例模式
a、特点:
b.分类
1)、懒汉式单例
*1在getInstance方法上加同步
*2、双重检查锁定
*3、静态(类级)内部类

2)、饿汉式单例
3)、单例和枚举
c、饿汉式和懒汉式区别

a、特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

b.分类
1)、懒汉式单例
//懒汉式单例类.在第一次调用的时候实例化自己

    java    17行

package demo1;
//懒汉式,在第一次调用的时候才进行实例化
public class Singleton {
    //构造方法私有化
     private Singleton() {
    }
    private static Singleton single =null;
     //静态工厂方法
    public static Singleton getInstance(){
        if (single==null) {
            single = new Singleton();
        }
        return single;
    }
}

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)
但 是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都 是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看 饿汉式单例,等看完后面再回头考虑线程安全的问题:

*1、在getInstance方法上加同步

    java    17行

package demo1;
//懒汉式,在getInstance上加同步,保证线程安全,方法一
public class Singleton1 {
    //构造方法适用于化
    private Singleton1(){
    }
    private static Singleton1 single =null;
    //静态工厂方法阿
    public static synchronized Singleton1 getInstance(){
        if (single==null){
            single= new Singleton1();
        }
        return single;
    }
}

*2、双重检查锁定

可以使用“双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受很大的影响。那么什么是“双重检查加锁”机制呢?
所谓“双重检查加锁”机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如 果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样 一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
“双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
注意:在java1.4及以前版本中,很多JVM对于volatile关键字的实现的问题,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只只能用在java5及以上的版本。

    java    21行

package demo1;
//懒汉式,保证线程安全方法二:双重检查锁定
public class Singleton2 {
    //构造方法私有化
    private Singleton2(){}
    private  volatile static Singleton2 single =null;
    public static Singleton2 getInstance(){
        //先检查实例是否存在,不存在才会进入下面的同步代码块
        if (single==null){
            //同步块,线程安全的创建实例
            synchronized (Singleton2.class){
                //再次检查
                if (single==null){
                    single=new Singleton2();
                }
            }
        }
        return single;
    }
}

这种实现方式既可以实现线程安全地创建实例,而又不会对性能造成太大的影响。它只是第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。
(摘自网络)提示:由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。
*3、静态(类级)内部类

    java    18行

package demo1;
//懒汉式,保证线程安全方法三:静态(类级)内部类,
public class Singleton3 {
    //私有化
    private Singleton3(){
    }
     //类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
    // 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。

    private static class MM {
       // 静态初始化器,由JVM来保证线程安全
        private static Singleton3 single = new Singleton3();
    }
    public static Singleton3 getInstance(){
        return MM.single;
    }
}

这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。 当getInstance方法第一次被调用的时候,它第一次读取 SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静 态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。
这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

2)、饿汉式单例
//饿汉式单例类.在类初始化时,已经自行实例化

    java    12行

package demo1;
//饿汉式单例类,在类初始化时,已经自行实例化
public class Singletonehan {
    private static Singletonehan single=new Singletonehan();
    //构造方法私有化
    private Singletonehan(){}
    //静态工厂方法
    public static Singletonehan getInstance(){
        return single;
    }
}

饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

3)、单例和枚举
用枚举来实现单例非常简单,只需要编写一个包含单个元素的枚举类型即可。

    java    49行

package demo1;

public enum  enmuSingleton {
    //定义一个枚举的元素,就代表一个enmuSingleton的实例
    FF,HH;//定义枚举的两个类型
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
class Text{
    public static void main(String[] args) {
        enmuSingleton s1=enmuSingleton.FF;
        s1.setName("池昌旭");
        System.out.println(s1.getName());

        enmuSingleton s2=enmuSingleton.FF;
        System.out.println(s2.getName());

        System.out.println("-----------------");

        enmuSingleton s3=enmuSingleton.HH;
        s3.setName("朴信惠");
        System.out.println(s3.getName());

        enmuSingleton S4=enmuSingleton.HH;
        S4.setName("全智贤");
        System.out.println(S4.getName());

        System.out.println("-----------------");

        System.out.println(s1==s2);

        System.out.println(s3==S4);

        System.out.println("-----------------");

        System.out.println(s1.hashCode()+"\t"+s2.hashCode());

        System.out.println(s3.hashCode()+"\t"+ S4.hashCode());

    }
}

使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。
c、饿汉式和懒汉式区别
从名字上来说,饿汉和懒汉,
饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,
而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。

3. 网络

a. 网络模型

OSI 七层模式 : 应用层,表示层,会话层,传输层,网络层,链路层,物理层

五层模型: 应用层, 传输层,网络层,链路层,物理层
四层模型 : 应用层, 传输层,网络层,链路层

应用层:http(超文本传输协议) ftp(文件传输协议) stmp (邮件发送协议) pop3(邮件接收协议), ssh ( 安全shell,用于远程登录)

传输层: tcp(安全可靠的协议) udp(不可靠)

网络层:ip

windows下可以使用 ipconfig来查看ip地址
linux 下可以使用 ifconfig来查看ip地址

b. 网络协议

两类传输协议:TCP;UDP
TCP是Tranfer Control Protocol的 简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建 立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送 或接收操作。
UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
比较:
UDP:1,每个数据报中都给出了完整的地址信息,因此无需要建立发送方和接收方的连接。
2,UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。
3,UDP是一个不可靠的协议,发送方所发送的数据报并不一定以相同的次序到达接收方
TCP:1,面向连接的协议,在socket之间进行数据传输之前必然要建立连接,所以在TCP中需要连接
时间。
2,TCP传输数据大小限制,一旦连接建立起来,双方的socket就可以按统一的格式传输大的
数据。
3,TCP是一个可靠的协议,它确保接收方完全正确地获取发送方所发送的全部数据。
应用:
1,TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输。但是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP高。
2,UDP操作简单,而且仅需要较少的监护,因此通常用于局域网高可靠性的分散系统中client/server应用程序。例如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。

c. java中的网络编程

基于Socket的java网络编程
1),什么是Socket
网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket。Socket通常用来实现客户方和服务方的连接。Socket是TCP/IP协议的一个十分流行的编程界面,一个Socket由一个IP地址和一个端口号唯一确定。
但是,Socket所支持的协议种类也不光TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。
2),Socket通讯的过程
Server端Listen(监听)某个端口是否有连接请求,Client端向Server 端发出Connect(连接)请求,Server端向Client端发回Accept(接受)消息。一个连接就建立起来了。Server端和Client 端都可以通过Send,Write等方法与对方通信。
对于一个功能齐全的Socket,都要包含以下基本结构,其工作过程包含以下四个基本的步骤:
  (1) 创建Socket;
  (2) 打开连接到Socket的输入/出流;
  (3) 按照一定的协议对Socket进行读/写操作;
  (4) 关闭Socket.(在实际应用中,并未使用到显示的close,虽然很多文章都推荐如此,不过在我的程序中,可能因为程序本身比较简单,要求不高,所以并未造成什么影响。)
3),创建Socket
创建Socket
java在包java.net中提供了两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务端。这是两个封装得非常好的类,使用很方便。其构造方法如下:
  Socket(InetAddress address, int port);
  Socket(InetAddress address, int port, boolean stream);
  Socket(String host, int prot);
  Socket(String host, int prot, boolean stream);
  Socket(SocketImpl impl)
  Socket(String host, int port, InetAddress localAddr, int localPort)
  Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
  ServerSocket(int port);
  ServerSocket(int port, int backlog);
  ServerSocket(int port, int backlog, InetAddress bindAddr)
  其中address、host和port分别是双向连接中另一方的IP地址、主机名和端 口号,stream指明socket是流socket还是数据报socket,localPort表示本地主机的端口号,localAddr和 bindAddr是本地机器的地址(ServerSocket的主机地址),impl是socket的父类,既可以用来创建serverSocket又可 以用来创建Socket。count则表示服务端所能支持的最大连接数。
  Socket client = new Socket("127.0.01.", 80);
  ServerSocket server = new ServerSocket(80);
  注意,在选择端口时,必须小心。每一个端口提供一种特定的服务,只有给出正确的端口,才 能获得相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23, 所以我们在选择端口号时,最好选择一个大于1023的数以防止发生冲突。
  在创建socket时如果发生错误,将产生IOException,在程序中必须对之作出处理。所以在创建Socket或ServerSocket是必须捕获或抛出例外。
4)、tcp的例子

建立连接
服务器端:

    java    26行

package Socket.chuanshu;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Fuwu {
    public static void main(String[] args) throws IOException {
        //建立连接,并设置端口号
        ServerSocket server = new ServerSocket(5555);
        System.out.println("等待客户端进行连接...");
        Socket accept = server.accept();//等待客户端连接我的服务器方法,直到有客户连接,否则一直处于等待接受状态
        InputStream socket = accept.getInputStream();
        byte[] bytes = new byte[1024];
        while (true){
            int read = socket.read(bytes);
            if (read==-1){
                break;
            }
            System.out.println(new String(bytes,0,read));
        }
        accept.close();
    }
}

客户端:

    java    13行

package Socket.chuanshu;

import java.io.IOException;
import java.net.Socket;

public class Kehu {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1", 5555);
        socket.getOutputStream().write("你好,现在在干嘛?".getBytes());
        socket.close();
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值