栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java查漏补缺(2)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java查漏补缺(2)

多线程 进程&线程

启动一个程序,是一个进程
一个进程内并行执行的是多个线程

创建多线程三种方式

这里以把同一个数循环50次加1为例。结果和预想有出入,请看线程同步部分。
想操作同一个数请把该数设为static!!!

extends Thread

设计一个类,继承Thread,并且重写run方法
启动线程办法:调用其start方法

public class AddThread1 extends Thread {
    private static Integer num = 0;

    public AddThread1(Integer num) {
        this.num = num;
    }

    public void add() throws InterruptedException {
        Thread.sleep(50);
        num = num + 1;
        System.out.println(currentThread().getName() + ":" + num);
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            try {
                add();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行线程:

		AddThread1 addThread1 = new AddThread1(num);
        AddThread1 addThread11 = new AddThread1(num);
        addThread1.start();
        addThread11.start();
implements Runnable

创建类,实现Runnable接口
启动的时候,首先创建一个对象,然后再根据该对象创建一个线程对象,并启动

public class AddThread2 implements Runnable {
    private static Integer num;
    public AddThread2(Integer num) {
        this.num = num;
    }
    public void add() throws InterruptedException {
        Thread.sleep(50);
        num = num + 1;
        System.out.println(Thread.currentThread().getName()+ ":" + num);
    }
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            try {
                add();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行线程:

		AddThread2 addThread2 = new AddThread2(num);
        new Thread(addThread2).start();
        new Thread(addThread2).start();
匿名类

使用匿名类,继承Thread,重写run方法,直接在run方法中写业务代码
匿名类的一个好处是可以很方便的访问外部的局部变量。
运行线程:

public class ThreadTest {
    private static Integer num = 0;
    public static void main(String[] args) {
        Thread t1=new Thread(){
            @Override
            public void run() {
                add50();
            }
        };
        t1.start();
        Thread t2=new Thread(){
            @Override
            public void run() {
                add50();
            }
        };
        t2.start();
    }
    public static void add50(){
        for (int i=0;i<50;i++){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num = num + 1;
            System.out.println(Thread.currentThread().getName()+ ":" + num);
        }
    }
}
常见线程方法


sleep:表示当前线程暂停 ,其他线程不受影响

//休眠1000ms
Thread.sleep(1000);

join:即表明在主线程中加入该线程,主线程会等待该线程结束完毕, 才会往下运行。

public class TestThread {
    public static void main(String[] args) {
        Thread t1= new Thread(){
            public void run(){
                 //todo     
            }
        };
        t1.start();
        //代码执行到这里,一直是main线程在运行
        try {
            //t1线程加入到main线程中来,只有t1线程运行结束,才会继续往下走
            t1.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }     
    } 
}

setPriority:优先级,当线程处于竞争关系的时候,优先级高的线程会有更大的几率获得CPU资源

		Thread t2= new Thread(){
            public void run(){
              //todo            
            }
        };
        t2.setPriority(Thread.MAX_PRIORITY);

yield:当前线程,临时暂停,使得其他线程可以有更多的机会占用CPU资源

		Thread t2= new Thread(){
            public void run(){
                //临时暂停,使得t1可以占用CPU资源
                Thread.yield();
         		//todo
            }
        };

setDaemon:当一个进程里,所有的线程都是守护线程的时候,结束当前进程。

		Thread t1= new Thread(){
            public void run(){
              	//todo             
            }
        };
        t1.setDaemon(true);
        t1.start();
线程安全的解决方案

上面三种创建线程的方式都是把同一个数0用两个线程循环50次加1,但最后结果都达不到100。
是因为其中一个线程加1的时候,另一个线程不是用的第一个线程加1以后的数据再加1,而是当前的数据,这个数据很可能还没加1。
解决办法有:使用synchronized、使用线程安全的类

synchronized

所有需要修改数据的地方,有要建立在占有someObject的基础上。
而对象 someObject在同一时间,只能被一个线程占有。 间接地,导致同一时间,数据只能被一个线程修改。
这个 someObject就是个锁,要多个线程共用一个锁线程同步才会生效。

  1. extends Thread 形式的线程同步:
  2. 实现Runnable接口的由于是同一个AddThread2类的对象生成了两个线程,所以可以用本对象当锁。
    synchronized (this) {
    }
    表示当前对象为同步对象,即是addThread2为同步对象。

  3. 匿名内部类用个obj就行
注:为什么不用static的Integer对象num做同步的对象呢?

java中这种包装类的自动拆箱装箱特性导致的,在拆装箱的过程中实际上是new了一个新对象代替了原num对象,导致锁变了,如果没有装箱拆箱过程就可以用。
可使用线程安全的AtomicInteger。

线程安全的类

在集合中,线程安全的有:HashTable,Vector
在包装类中,线程安全的有:StringBuffer,AtomicInteger等
非线程安全的类转换为线程安全的类:

ArrayList arrayList = new ArrayList<>();
List objects = Collections.synchronizedList(arrayList);
 

线程交互:

wait()的意思是, 让占用了这个同步对象的线程,临时释放当前的占用,并且等待。 所以调用wait是有前提条件的,一定是在synchronized块里,否则就会出错。
notify() 的意思是,通知一个等待在这个同步对象上的线程,你可以苏醒过来了,有机会重新占用当前对象了。
notifyAll() 的意思是,通知所有的等待在这个同步对象上的线程,你们可以苏醒过来了,有机会重新占用当前对象了。

线程交互案例

需求:设计一个类,里面一个加1和一个减1方法,创建三个线程,对同一个数进行两个减1和一个加1,当数据到0,不能减1,等待数据大于0才能减1。
分析:减1方法用while循环的时候最好不要一直判断数据是否=0,可以wait()临时释放当前的占用,让加1加完再执行线程,而不是不停的循环判断,占用cpu资源。

public class AddAndSubstractThread {
    private static int num;

    public AddAndSubstractThread (int num) {
        this.num = num;
    }

    //同步锁是this
    public synchronized void add() {
        num++;
        System.out.println(Thread.currentThread().getName() + ":" + num);
        //唤醒其他所有等待在this对象上的线程
        this.notifyAll();
    }

    public synchronized void substract() {
        //这里不能用if
        //当用if时,num=0只会判断一次,在进入判断条件内部就不会再进行判断了,wait之后只要拿到锁就算num还是0也会减1.
        while (num == 0) {
            //暂时释放对this的占有,并等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num--;
        System.out.println(Thread.currentThread().getName() + ":" + num);
    }
}
	public static void main(String[] args) {
        AddAndSubstractThread addAndSubstractThread = new AddAndSubstractThread(5);
        Thread add = new Thread() {
            @Override
            public void run() {
                while (true) {
                    addAndSubstractThread.add();
                    try {
                        sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread substract1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    addAndSubstractThread.substract();
                    try {
                        sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread substract2 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    addAndSubstractThread.substract();
                    try {
                        sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        add.start();
        substract1.start();
        substract2.start();
    }
线程池

每一个线程的启动和结束都是比较消耗时间和占用资源的。
如果在系统中用到了很多的线程,大量的启动和结束动作会导致系统的性能变卡,响应变慢。
为了解决这个问题,引入线程池这种设计思想。
线程池类ThreadPoolExecutor在包java.util.concurrent下

Lock锁 lock与synchronized区别
  1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,Lock是代码层面的实现。
  2. Lock可以选择性的获取锁,如果一段时间获取不到,可以放弃。synchronized不行,会一根筋一直获取下去。 借助Lock的这个特性,就能够规避死锁,synchronized必须通过谨慎和良好的设计,才能减少死锁的发生。
  3. synchronized在发生异常和同步块结束的时候,会自动释放锁。而Lock必须手动释放, 所以如果忘记了释放锁,一样会造成死锁。
原子访问

所谓的原子性操作即不可中断的操作,比如赋值操作。原子性操作本身是线程安全的

可以使用原子类操作。
JDK6 以后,新增加了一个包java.util.concurrent.atomic,里面有各种原子类,比如AtomicInteger。

网络编程 单向通信

先启动server,再启动client

import java.io.*;
import java.net.Socket;

public class Client {
    public static void main(String[] args) {
        try {
            Socket s = new Socket("127.0.0.1", 8888);
            OutputStream os = s.getOutputStream();
            
            DataOutputStream dos = new DataOutputStream(os);
            dos.writeUTF("中英文混合alkjds");
            dos.close();
            
            s.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8888);
            Socket s = ss.accept();
            InputStream is = s.getInputStream();

            DataInputStream dis = new DataInputStream(is);
            String msg = dis.readUTF();
            System.out.println(msg);
            dis.close();

            s.close();
            ss.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
双向通信

双向通信要用到多线程,这里可以把发送消息和接收消息抽取出来做成单独的两个类。

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

public class ReceiveThread extends Thread {
    private Socket s;

    public ReceiveThread(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
        try (DataInputStream dis = new DataInputStream(s.getInputStream())) {
            while (true) {
                String msg = dis.readUTF();
                System.out.println(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

public class SendThread extends Thread {
    private Socket s;

    public SendThread(Socket s) {
        this.s = s;
    }

    @Override
    public void run() {
        try (DataOutputStream dos = new DataOutputStream(s.getOutputStream())) {
            Scanner scanner = new Scanner(System.in);
            while (true) {
                String next = scanner.next();
                dos.writeUTF(next);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.*;
import java.net.Socket;

public class Client {
    public static void main(String[] args) {
        try {
            Socket s = new Socket("127.0.0.1", 8888);
            new SendThread(s).start();
            new ReceiveThread(s).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) {
        try {
            ServerSocket ss = new ServerSocket(8888);
            Socket s = ss.accept();
            new SendThread(s).start();
            new ReceiveThread(s).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号