其他分享
首页 > 其他分享> > day06 Socket_线程API_线程并发安全

day06 Socket_线程API_线程并发安全

作者:互联网

使用多线程实现多客户端连接服务端

流程图

image-20210430152730779
服务端代码改造:

package socket;

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

/**
 * 聊天室服务端
 */
public class Server {
    /**
     * 运行在服务端的ServerSocket主要完成两个工作:
     * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
     * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
     *   就可以和该客户端交互了
     *
     * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
     * 电话使得服务端与你沟通。
     */
    private ServerSocket serverSocket;

    /**
     * 服务端构造方法,用来初始化
     */
    public Server(){
        try {
            System.out.println("正在启动服务端...");
            /*
                实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
                应用程序占用的端口相同,否则会抛出异常:
                java.net.BindException:address already in use

                端口是一个数字,取值范围:0-65535之间。
                6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服务端开始工作的方法
     */
    public void start(){
        try {
            while(true) {
                System.out.println("等待客户端链接...");
                /*
                    ServerSocket提供了接受客户端链接的方法:
                    Socket accept()
                    这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                    的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                    通过这个Socket就可以与客户端进行交互了。

                    可以理解为此操作是接电话,电话没响时就一直等。
                 */
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                //启动一个线程与该客户端交互
                ClientHandler clientHandler = new ClientHandler(socket);
                Thread t = new Thread(clientHandler);
                t.start();

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 定义线程任务
     * 目的是让一个线程完成与特定客户端的交互工作
     */
    private class ClientHandler implements Runnable{
        private Socket socket;
        public ClientHandler(Socket socket){
            this.socket = socket;
        }
        public void run(){
            try{
                 /*
                    Socket提供的方法:
                    InputStream getInputStream()
                    获取的字节输入流读取的是对方计算机发送过来的字节
                 */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(isr);

                String message = null;
                while ((message = br.readLine()) != null) {
                    System.out.println("客户端说:" + message);
                }
            }catch(IOException e){
                e.printStackTrace();
            }
        }
    }


}

线程API

获取线程相关信息的方法

package thread;

/**
 * 获取线程相关信息的一组方法
 */
public class ThreadInfoDemo {
    public static void main(String[] args) {
        Thread main = Thread.currentThread();//获取主线程

        String name = main.getName();//获取线程的名字
        System.out.println("名字:"+name);

        long id = main.getId();//获取该线程的唯一标识
        System.out.println("id:"+id);

        int priority = main.getPriority();//获取该线程的优先级
        System.out.println("优先级:"+priority);

        boolean isAlive = main.isAlive();//该线程是否活着
        System.out.println("是否活着:"+isAlive);

        boolean isDaemon = main.isDaemon();//是否为守护线程
        System.out.println("是否为守护线程:"+isDaemon);

        boolean isInterrupted = main.isInterrupted();//是否被中断了
        System.out.println("是否被中断了:"+isInterrupted);

    }
}

线程优先级

线程start后会纳入到线程调度器中统一管理,线程只能被动的被分配时间片并发运行,而无法主动索取时间片.线程调度器尽可能均匀的将时间片分配给每个线程.

线程有10个优先级,使用整数1-10表示

package thread;

public class PriorityDemo {
    public static void main(String[] args) {
        Thread max = new Thread(){
            public void run(){
                for(int i=0;i<10000;i++){
                    System.out.println("max");
                }
            }
        };
        Thread min = new Thread(){
            public void run(){
                for(int i=0;i<10000;i++){
                    System.out.println("min");
                }
            }
        };
        Thread norm = new Thread(){
            public void run(){
                for(int i=0;i<10000;i++){
                    System.out.println("nor");
                }
            }
        };
        min.setPriority(Thread.MIN_PRIORITY);
        max.setPriority(Thread.MAX_PRIORITY);
        min.start();
        norm.start();
        max.start();
    }
}

sleep阻塞

线程提供了一个静态方法:

package thread;

public class SleepDemo {
    public static void main(String[] args) {
        System.out.println("程序开始了!");
        try {
            Thread.sleep(5000);//主线程阻塞5秒钟
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("程序结束了!");
    }
}

线程API

sleep阻塞(续)

sleep方法处理异常:InterruptedException.

当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的interrupt()方法被调用时,sleep方法会抛出该异常从而打断睡眠阻塞.

package thread;

/**
 * sleep方法要求必须处理中断异常:InterruptedException
 * 当一个线程调用sleep方法处于睡眠阻塞的过程中,它的interrupt()方法被调用时
 * 会中断该阻塞,此时sleep方法会抛出该异常。
 */
public class SleepDemo2 {
    public static void main(String[] args) {
        Thread lin = new Thread(){
            public void run(){
                System.out.println("林:刚美完容,睡一会吧~");
                try {
                    Thread.sleep(9999999);
                } catch (InterruptedException e) {
                    System.out.println("林:干嘛呢!干嘛呢!干嘛呢!都破了像了!");
                }
                System.out.println("林:醒了");
            }
        };

        Thread huang = new Thread(){
            public void run(){
                System.out.println("黄:大锤80!小锤40!开始砸墙!");
                for(int i=0;i<5;i++){
                    System.out.println("黄:80!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
                System.out.println("咣当!");
                System.out.println("黄:大哥,搞定!");
                lin.interrupt();//中断lin的睡眠阻塞
            }
        };
        lin.start();
        huang.start();
    }
}

守护线程

守护线程也称为:后台线程

package thread;

/**
 * 守护线程
 * 守护线程是通过普通线程调用setDaemon(true)设置而转变的。因此守护线程创建上
 * 与普通线程无异。
 * 但是结束时机上有一点不同:进程结束。
 * 当一个java进程中的所有普通线程都结束时,该进程就会结束,此时会强制杀死所有正在
 * 运行的守护线程。
 */
public class DaemonThreadDemo {
    public static void main(String[] args) {
        Thread rose = new Thread(){
            public void run(){
                for(int i=0;i<5;i++){
                    System.out.println("rose:let me go!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
                System.out.println("rose:啊啊啊啊啊啊AAAAAAAaaaaa....");
                System.out.println("噗通");
            }
        };

        Thread jack = new Thread(){
            public void run(){
                while(true){
                    System.out.println("jack:you jump!i jump!");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
            }
        };
        rose.start();
        jack.setDaemon(true);//设置守护线程必须在线程启动前进行
        jack.start();

    }
}

通常当我们不关心某个线程的任务什么时候停下来,它可以一直运行,但是程序主要的工作都结束时它应当跟着结束时,这样的任务就适合放在守护线程上执行.比如GC就是在守护线程上运行的.

多线程并发安全问题

当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能导致系统瘫痪.
临界资源:操作该资源的全过程同时只能被单个线程完成.
package thread;

/**
 * 多线程并发安全问题
 * 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现
 * 混乱,严重时可能导致系统瘫痪。
 * 临界资源:同时只能被单一线程访问操作过程的资源。
 */
public class SyncDemo {
    public static void main(String[] args) {
        Table table = new Table();
        Thread t1 = new Thread(){
            public void run(){
                while(true){
                    int bean = table.getBean();
                    Thread.yield();
                    System.out.println(getName()+":"+bean);
                }
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                while(true){
                    int bean = table.getBean();
                    /*
                        static void yield()
                        线程提供的这个静态方法作用是让执行该方法的线程
                        主动放弃本次时间片。
                        这里使用它的目的是模拟执行到这里CPU没有时间了,发生
                        线程切换,来看并发安全问题的产生。
                     */
                    Thread.yield();
                    System.out.println(getName()+":"+bean);
                }
            }
        };
        t1.start();
        t2.start();
    }
}

class Table{
    private int beans = 20;//桌子上有20个豆子

    public int getBean(){
        if(beans==0){
            throw new RuntimeException("没有豆子了!");
        }
        Thread.yield();
        return beans--;
    }
}

synchronized关键字

synchronized有两种使用方式

同步方法

当一个方法使用synchronized修饰后,这个方法称为"同步方法",即:多个线程不能同时 在方法内部执行.只能有先后顺序的一个一个进行. 将并发操作同一临界资源的过程改为同步执行就可以有效的解决并发安全问题.

package thread;

/**
 * 多线程并发安全问题
 * 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现
 * 混乱,严重时可能导致系统瘫痪。
 * 临界资源:同时只能被单一线程访问操作过程的资源。
 */
public class SyncDemo {
    public static void main(String[] args) {
        Table table = new Table();
        Thread t1 = new Thread(){
            public void run(){
                while(true){
                    int bean = table.getBean();
                    Thread.yield();
                    System.out.println(getName()+":"+bean);
                }
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                while(true){
                    int bean = table.getBean();
                    /*
                        static void yield()
                        线程提供的这个静态方法作用是让执行该方法的线程
                        主动放弃本次时间片。
                        这里使用它的目的是模拟执行到这里CPU没有时间了,发生
                        线程切换,来看并发安全问题的产生。
                     */
                    Thread.yield();
                    System.out.println(getName()+":"+bean);
                }
            }
        };
        t1.start();
        t2.start();
    }
}

class Table{
    private int beans = 20;//桌子上有20个豆子

    /**
     * 当一个方法使用synchronized修饰后,这个方法称为同步方法,多个线程不能
     * 同时执行该方法。
     * 将多个线程并发操作临界资源的过程改为同步操作就可以有效的解决多线程并发
     * 安全问题。
     * 相当于让多个线程从原来的抢着操作改为排队操作。
     */
    public synchronized int getBean(){
        if(beans==0){
            throw new RuntimeException("没有豆子了!");
        }
        Thread.yield();
        return beans--;
    }
}

同步块

有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率.同步块可以更准确的控制需要多个线程排队执行的代码片段.

语法:

synchronized(同步监视器对象){
   需要多线程同步执行的代码片段
}

同步监视器对象即上锁的对象,要想保证同步块中的代码被多个线程同步运行,则要求多个线程看到的同步监视器对象是同一个.

package thread;

/**
 * 有效的缩小同步范围可以在保证并发安全的前提下尽可能提高并发效率。
 *
 * 同步块
 * 语法:
 * synchronized(同步监视器对象){
 *     需要多个线程同步执行的代码片段
 * }
 * 同步块可以更准确的锁定需要多个线程同步执行的代码片段来有效缩小排队范围。
 */
public class SyncDemo2 {
    public static void main(String[] args) {
        Shop shop = new Shop();
        Thread t1 = new Thread(){
            public void run(){
                shop.buy();
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                shop.buy();
            }
        };
        t1.start();
        t2.start();
    }
}

class Shop{
    public void buy(){
        /*
            在方法上使用synchronized,那么同步监视器对象就是this。
         */
//    public synchronized void buy(){
        Thread t = Thread.currentThread();//获取运行该方法的线程
        try {
            System.out.println(t.getName()+":正在挑衣服...");
            Thread.sleep(5000);
            /*
                使用同步块需要指定同步监视器对象,即:上锁的对象
                这个对象可以是java中任何引用类型的实例,只要保证多个需要排队
                执行该同步块中代码的线程看到的该对象是"同一个"即可
             */
            synchronized (this) {
//            synchronized (new Object()) {//没有效果!
                System.out.println(t.getName() + ":正在试衣服...");
                Thread.sleep(5000);
            }

            System.out.println(t.getName()+":结账离开");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

在静态方法上使用synchronized

当在静态方法上使用synchronized后,该方法是一个同步方法.由于静态方法所属类,所以一定具有同步效果.

静态方法使用的同步监视器对象为当前类的类对象(Class的实例).

注:类对象会在后期反射知识点介绍.

package thread;

/**
 * 静态方法上如果使用synchronized,则该方法一定具有同步效果。
 */
public class SyncDemo3 {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            public void run(){
                Boo.dosome();
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                Boo.dosome();
            }
        };
        t1.start();
        t2.start();
    }
}
class Boo{
    /**
     * synchronized在静态方法上使用是,指定的同步监视器对象为当前类的类对象。
     * 即:Class实例。
     * 在JVM中,每个被加载的类都有且只有一个Class的实例与之对应,后面讲反射
     * 知识点的时候会介绍类对象。
     */
    public synchronized static void dosome(){
            Thread t = Thread.currentThread();
            try {
                System.out.println(t.getName() + ":正在执行dosome方法...");
                Thread.sleep(5000);
                System.out.println(t.getName() + ":执行dosome方法完毕!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

静态方法中使用同步块时,指定的锁对象通常也是当前类的类对象

class Boo{
    public static void dosome(){
        /*
            静态方法中使用同步块时,指定同步监视器对象通常还是用当前类的类对象
            获取方式为:类名.class
         */
        synchronized (Boo.class) {
            Thread t = Thread.currentThread();
            try {
                System.out.println(t.getName() + ":正在执行dosome方法...");
                Thread.sleep(5000);
                System.out.println(t.getName() + ":执行dosome方法完毕!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

互斥锁

当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的.

使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的.

package thread;

/**
 * 互斥锁
 * 当使用synchronized锁定多个不同的代码片段,并且指定的同步监视器对象相同时,
 * 这些代码片段之间就是互斥的,即:多个线程不能同时访问这些方法。
 */
public class SyncDemo4 {
    public static void main(String[] args) {
        Foo foo = new Foo();
        Thread t1 = new Thread(){
            public void run(){
                foo.methodA();
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                foo.methodB();
            }
        };
        t1.start();
        t2.start();
    }
}
class Foo{
    public synchronized void methodA(){
        Thread t = Thread.currentThread();
        try {
            System.out.println(t.getName()+":正在执行A方法...");
            Thread.sleep(5000);
            System.out.println(t.getName()+":执行A方法完毕!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized void methodB(){
        Thread t = Thread.currentThread();
        try {
            System.out.println(t.getName()+":正在执行B方法...");
            Thread.sleep(5000);
            System.out.println(t.getName()+":执行B方法完毕!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

死锁

死锁的产生:

两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。这个现象就是死锁。

package thread;

/**
 * 死锁
 * 死锁的产生:
 * 两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。
 * 这个现象就是死锁。
 */
public class DeadLockDemo {
    //定义两个锁对象,"筷子"和"勺"
    public static Object chopsticks = new Object();
    public static Object spoon = new Object();

    public static void main(String[] args) {
        Thread np = new Thread(){
            public void run(){
                System.out.println("北方人开始吃饭.");
                System.out.println("北方人去拿筷子...");
                synchronized (chopsticks){
                    System.out.println("北方人拿起了筷子开始吃饭...");
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                    }
                    System.out.println("北方人吃完了饭,去拿勺...");
                    synchronized (spoon){
                        System.out.println("北方人拿起了勺子开始喝汤...");
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                        }
                        System.out.println("北方人喝完了汤");
                    }
                    System.out.println("北方人放下了勺");
                }
                System.out.println("北方人放下了筷子,吃饭完毕!");
            }
        };


        Thread sp = new Thread(){
            public void run(){
                System.out.println("南方人开始吃饭.");
                System.out.println("南方人去拿勺...");
                synchronized (spoon){
                    System.out.println("南方人拿起了勺开始喝汤...");
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                    }
                    System.out.println("南方人喝完了汤,去拿筷子...");
                    synchronized (chopsticks){
                        System.out.println("南方人拿起了筷子开始吃饭...");
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                        }
                        System.out.println("南方人吃完了饭");
                    }
                    System.out.println("南方人放下了筷子");
                }
                System.out.println("南方人放下了勺,吃饭完毕!");
            }
        };

        np.start();
        sp.start();



    }
}
package thread;

/**
 * 解决死锁:
 * 1:尽量避免在持有一个锁的同时去等待持有另一个锁(避免synchronized嵌套)
 * 2:当无法避免synchronized嵌套时,就必须保证多个线程锁对象的持有顺序必须一致。
 *   即:A线程在持有锁1的过程中去持有锁2时,B线程也要以这样的持有顺序进行。
 */
public class DeadLockDemo2 {
    //筷子
    private static Object chopsticks = new Object();
    //勺
    private static Object spoon = new Object();

    public static void main(String[] args) {
        //北方人
        Thread np = new Thread(){
            public void run(){
                try {
                    System.out.println("北方人:开始吃饭");
                    System.out.println("北方人去拿筷子...");
                    synchronized (chopsticks) {
                        System.out.println("北方人拿起了筷子,开始吃饭...");
                        Thread.sleep(5000);
                    }
                    System.out.println("北方人吃完了饭,放下了筷子");
                    System.out.println("北方人去拿勺子...");
                    synchronized (spoon){
                            System.out.println("北方人拿起了勺子,开始喝汤...");
                            Thread.sleep(5000);
                    }
                    System.out.println("北方人喝完了汤,北方人放下了勺子");
                    System.out.println("吃饭完毕。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        //南方人
        Thread sp = new Thread(){
            public void run(){
                try {
                    System.out.println("南方人:开始吃饭");
                    System.out.println("南方人去拿勺...");
                    synchronized (spoon) {
                        System.out.println("南方人拿起了勺,开始喝汤...");
                        Thread.sleep(5000);
                    }
                    System.out.println("南方人喝完了汤,放下勺子...");
                    System.out.println("南方人去拿筷子...");
                    synchronized (chopsticks){
                        System.out.println("南方人拿起了筷子,开始吃饭...");
                        Thread.sleep(5000);
                    }
                    System.out.println("南方人吃完了饭,南方人放下了筷子");
                    System.out.println("吃饭完毕。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        np.start();
        sp.start();
    }
}

标签:Socket,Thread,day06,System,线程,println,public,out
来源: https://www.cnblogs.com/yzoran/p/16483131.html