当前位置: 代码迷 >> 综合 >> SE02 Unit05 多线程基础 、 TCP通信
  详细解决方案

SE02 Unit05 多线程基础 、 TCP通信

热度:45   发布时间:2023-12-11 15:08:31.0

当一个方法被synchronized修饰后,那么 该方法称为同步方法,即:多个线程不能同时进入到方法内部执行。

package day10;
/*** 当多线程并发操作同一资源时,由于线程切换的不确定* 性,可能导致执行顺序的混乱,严重时可能导致系统* 瘫痪。* @author adminitartor**/
public class SyncDemo1 {
    public static void main(String[] args) {final 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();Thread.yield();//模拟线程切换System.out.println(getName()+":"+bean);}}};t1.start();t2.start();}
}class Table{private int beans = 20;/*** 当一个方法被synchronized修饰后,那么* 该方法称为同步方法,即:多个线程不能同时* 进入到方法内部执行。* 在方法上使用synchronized那么锁对象为* 当前方法所属对象,即:this* @return*/public synchronized int getBean(){if(beans==0){throw new RuntimeException("没有豆子了!");}Thread.yield();//模拟线程切换return beans--;}
}

有效的缩小同步范围可以在保证并发安全的前提下提高并发效率。

package day10;
/*** 有效的缩小同步范围可以在保证并发安全的前提下* 提高并发效率。* @author adminitartor**/
public class SyncDemo2 {
    public static void main(String[] args) {final 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(){try {Thread t = Thread.currentThread();System.out.println(t.getName()+":正在挑选衣服...");Thread.sleep(5000);/** 多个线程要保证同步执行代码的前提是* 这里看到的"同步监视器"即:上锁的对象* 是同一个。*/synchronized(this){System.out.println(t.getName()+":正在试衣服...");Thread.sleep(5000);}System.out.println(t.getName()+":结账离开");} catch (Exception e) {e.printStackTrace();}}
}

每个类在被JVM加载时,JVM都会创建一个且只创建一个Class类型的实例来表示它。所以,每个类在JVM内部都有唯一的一个Class类型的实例对应,而静态方法就是将该Class的实例上锁的。

package day10;
/*** 静态方法若使用synchronized修饰后,那么该方法* 一定具有同步效果。* 静态方法的同步监视器对象为当前类的类对象。* 类对象:Class类型的实例。* 每个类在被JVM加载时,JVM都会创建一个且只创建* 一个Class类型的实例来表示它。所以,每个类在* JVM内部都有唯一的一个Class类型的实例对应,而* 静态方法就是将该Class的实例上锁的。* @author adminitartor**/
public class SyncDemo3 {
    public static void main(String[] args) {Thread t1 = new Thread(){public void run(){Foo.dosome();}};Thread t2 = new Thread(){public void run(){Foo.dosome();}};t1.start();t2.start();}
}class Foo{public synchronized static void dosome(){try {Thread t = Thread.currentThread();System.out.println(t.getName()+":正在运行dosome方法...");Thread.sleep(5000);            System.out.println(t.getName()+":运行dosome方法完毕!");} catch (Exception e) {e.printStackTrace();}}
}

互斥锁

package day10;

/*** 互斥锁* 当使用Synchronized修饰多段不同代码,但是同步* 监视器对象是同一个的时候,那么这些代码间就具有* 了互斥性,同一时间不能同时执行这些代码。* @author adminitartor**/
public class SyncDemo4 {
    public static void main(String[] args) {final Boo boo = new Boo();Thread t1 = new Thread(){public void run(){boo.methodA();}};Thread t2 = new Thread(){public void run(){boo.methodB();}};t1.start();t2.start();}
}class Boo{public void methodA(){synchronized(this){try {Thread t = Thread.currentThread();System.out.println(t.getName()+":正在执行A方法");Thread.sleep(5000);System.out.println(t.getName()+":执行A方法完毕");} catch (Exception e) {e.printStackTrace();}}}public void methodB(){synchronized(this){try {Thread t = Thread.currentThread();System.out.println(t.getName()+":正在执行B方法");Thread.sleep(5000);System.out.println(t.getName()+":执行B方法完毕");} catch (Exception e) {e.printStackTrace();}}}
}

死锁

package day10;
/*** 死锁* 双方都持有自己的锁,但都要求对方先释放锁时* 出现死锁现象。* @author adminitartor**/
public class SyncDemo5 {
    public static void main(String[] args) {final Coo coo = new Coo();Thread t1 = new Thread(){public void run(){coo.methodA();}};Thread t2 = new Thread(){public void run(){coo.methodB();}};t1.start();t2.start();}
}class Coo{private Object lockA = new Object();private Object lockB = new Object();public void methodA(){try {Thread t = Thread.currentThread();synchronized (lockA) {System.out.println(t.getName()+"正在运行A方法");Thread.sleep(5000);methodB();System.out.println(t.getName()+"运行A方法完毕");}} catch (Exception e) {}}public void methodB(){try {Thread t = Thread.currentThread();synchronized (lockB) {System.out.println(t.getName()+"正在运行B方法");Thread.sleep(5000);methodA();System.out.println(t.getName()+"运行B方法完毕");}} catch (Exception e) {}}
}

使用Collections的静态方法可以将现有的集合 或Map转换为线程安全的

package day10;import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;/*** 使用Collections的静态方法可以将现有的集合* 或Map转换为线程安全的* @author adminitartor**/
public class Sync_API {public static void main(String[] args) {/** 线程安全的集合自身的add,remove等方法* 都是同步的,并且之间也有互斥。* 但是并不与迭代器遍历互斥。所以若并发* 同时遍历和增删元素,迭代器依然会抛出* 异常。* 所以,迭代器与集合元素操作间要自行维护* 互斥关系。* * ArrarList,LinkedList都不是线程安全的*/List<String> list = new ArrayList<String>(); list.add("one");list.add("two");list.add("three"); list.add("four");System.out.println(list);//将给定的集合转换为一个线程安全的集合list = Collections.synchronizedList(list);System.out.println(list);Set<String> set = new HashSet<String>(list);set = Collections.synchronizedSet(set);System.out.println(set);Map<String,Integer> map= new HashMap<String,Integer>();map.put("语文", 100);map.put("数学", 99);map.put("英语", 98);map = Collections.synchronizedMap(map);System.out.println(map);}
}

线程池

package day10;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** 线程池* 线程池有两个主要作用:* 1:控制线程数量* 2:重用线程* @author adminitartor**/
public class ThreadPool_Demo {
    public static void main(String[] args) {ExecutorService threadPool= Executors.newFixedThreadPool(2);for(int i=0;i<5;i++){Runnable runn = new Runnable(){public void run(){Thread t = Thread.currentThread();System.out.println(t.getName()+":正在运行任务...");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(t.getName()+":运行任务完毕!");}};threadPool.execute(runn);}//结束线程池threadPool.shutdown();System.out.println("线程池结束了!");}
}

案例:

聊天室客户端

 package chat;import java.net.Socket;/*** 聊天室客户端* @author adminitartor**/
public class Client {
    /** 封装了TCP通讯的Socket* 使用它可以与服务端建立连接,并进行通讯* */private Socket socket;/*** 构造方法,用来初始化客户端*/public Client() throws Exception{/** 实例化Socket的过程就是连接服务端的* 过程。若连接失败,这里会抛出异常* * 构造方法的两个参数:* 1:服务端计算机的IP地址* 2:服务端应用程序的端口*/socket = new Socket("localhost",8088    );}/*** 客户端开始工作的方法*/public void start(){}public static void main(String[] args) {try {Client client = new Client();client.start();} catch (Exception e) {e.printStackTrace();System.out.println("客户端运行失败!");}}
}

聊天室服务端

package chat;import java.net.ServerSocket;
import java.net.Socket;/*** 聊天室服务端* @author adminitartor**/
public class Server {
    /** 运行在服务端的ServerSocket,主要作用:* 1:向操作系统申请服务端口,客户端就是通过* 这个端口与服务端程序建立连接的* 2:监听服务端口,一旦客户端连接了,就会创建* 一个Socket以便与该客户端交互 */private ServerSocket server;/*** 构造方法,用来初始化服务端* @throws Exception*/public Server() throws Exception{/** 初始化,并申请服务端口,若该端口被* 其他应用程序占用,这里会抛出异常*/server = new ServerSocket(8088);}public void start(){try {/** ServerSocket提供方法:* Socket accept() * 该方法是一个阻塞方法,直到一个客户端* 通过申请的端口连接上,这里才会返回* 返回的是一个Socket实例,通过该实例* 即可与刚连接的客户端交互。*/System.out.println("等待客户端连接...");Socket socket = server.accept();System.out.println("一个客户端连接了!");} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) {try {Server server = new Server();server.start();} catch (Exception e) {e.printStackTrace();System.out.println("服务端运行失败!");}}
}