多线程
1.线程的创建
创建线程有两种方法:
1.继承Thread类,重写run方法实现。
2.创建Thread实例并且传入Runable实例,以任务的方式创建线程。
线程通过调用start方法调用run方法
// 通过继承Thread类实现,重写run方法实现多线程
public class Test {
public static void main(String args[]) {
MyThread t = new MyThread();
t.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程被执行");
}
}
// 以任务的方式创建线程
public class Test {
public static void main(String args[]) {
MyRunable task = new MyRunable();
Thread t = new Thread(task);
t.start();
}
}
class MyRunable implements Runable {
@Override
public void run() {
System.out.println("线程被执行");
}
}
2.线程的中断 interrupt
中断一个线程只需要在调用线程的interrupt方法,然后trycache线程的InterruptedException,线程种植的时候会抛出这个异常
public class Test {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
try {
System.out.println("线程被执行");
} catch (InterruptedException e) {
System.out.println("线程中断");
}
}
};
t.start();
t.interrupt();
}
}
3.线程的六种状态
New
:线程被创建,还未执行
Runable
:运行中的线程,正在执行run方法中的代码
Blocked
:运行中的线程因为某些操作被阻塞而刮挂起
Waiting
:运行中的线程,因为某些操作处于等待的状态
Time Waiting
:运行中的线程,因为执行sleep方法正在计时等待
Terminated
:线程被终止
4.守护线程setDaemon
守护线程是指为其他线程服务的线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。 因此,JVM退出时,不必关心守护线程是否已结束 创建守护线程只需要调用线程的setDaemon
方法。
Thread t=new Thread();
t.setDeamon(true);
t.start();
// 在守护线程中,编写代码要注意:守护线程不能持有任何需要关闭的资源,例如打开文件等,因为虚拟机退出时,守护线程没有任何机会来关闭文件,这会导致数据丢失。
锁
1.同步代码块的锁机制synchronized
,多个线程看同一把锁-隐式锁
public class Test {
public static void main(String[] args) {
Runnable run = new Ticket();
new Thread(run).start();
}
}
class Ticket implements Runnable {
private int count = 10;
private Object lock = new Object();
@Override
public void run() {
while (true) {
// 三个线程看同一把锁
synchronized (lock) {
if (this.count > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName());
} else {
break;
}
System.out.println("余票" + count);
}
}
}
}
2.同步方法锁,synchronized
修饰方法-隐式锁
public class Test {
public static void main(String[] args) {
Runnable run = new Ticket();
new Thread(run).start();
}
}
class Ticket implements Runnable {
private int count = 10;
@Override
public void run() {
while (true) {
Boolean sell = this.sell();
if (!sell) {
break;
}
}
}
// synchronized 修饰任务方法
synchronized public Boolean sell() {
boolean sell = true;
if (this.count > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName());
sell = true;
} else {
sell = false;
}
System.out.println("余票" + count);
return sell;
}
}
3.显示锁-ReentrantLock
,实例化一个ReentrantLock
,在需要加锁的地方调用lock
方法,在需要解锁的地方调用unLock
方法.
public class Test {
public static void main(String[] args) {
Runnable run = new Ticket();
new Thread(run).start();
}
}
class Ticket implements Runnable {
private int count = 10;
// 实例化锁
private Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
// 加锁
l.lock();
if (this.count > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName());
} else {
break;
}
System.out.println("余票" + count);
// 解锁
l.unlock();
}
}
}
4.公平锁和不公平锁
公平锁:排队取锁 不公平锁:不排队,抢锁,java默认为非公平锁 设置一个锁为公平锁,只需要实例化ReentrantLock
的时候传入true
public class Test {
public static void main(String[] args) {
// 传入true表示此锁为公平锁
Lock lock = new ReentrantLock(true);
}
}
5.死锁
死锁产生的条件是多线程各自持有不同的锁,并互相试图获取对方已持有的锁,导致无限等待; 避免死锁的方法是多线程获取锁的顺序要一致。
6.wait
和notify
用于多线程协调运行
在synchronized内部可以调用wait()使线程进入等待状态;
必须在已获得的锁对象上调用wait()方法;
在synchronized内部可以调用notify()或notifyAll()唤醒其他等待线程;
必须在已获得的锁对象上调用notify()或notifyAll()方法;
已唤醒的线程还需要重新获得锁后才能继续执行。
7.Callable
带返回值的线程
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Call c = new Call();
// 使用Future获取线程的返回值,线程池中提交任务后也会返回一个Future类型
FutureTask<Integer> f = new FutureTask<>(c);
Thread t = new Thread(f);
t.start();
int val = 0;
val = f.get();
System.out.println("返回值为" + val);
System.out.println("同步打印");
}
}
class Call implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
return 100;
}
}
8.线程池
线程池:存放线程的容器
线程执行过程:创建线程,创建任务,执行任务,关闭线程
耗时的步骤:创建线程,关闭线程
线程池分类:缓存线程池,定长线程池,单线程线程池,周期性任务定长线程池
public class Test {
public static void main(String[] args) throws InterruptedException {
// 1.缓存线程池
ExecutorService cacheThreadPool = Executors.newCachedThreadPool();
// 2.定长线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
// 3.单线程线程池
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
ExecutorService service = singleThreadPool;
// 线程池中添加并执行任务
for (int i = 0; i < 5; i++) {
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
// 祝线程sleep
Thread.sleep(1000);
// 下面的任务使用缓存的线程执行
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
// 4.周期性任务定长线程池
ScheduledExecutorService scheduleService = Executors.newScheduledThreadPool(2);
/**
* 1.定制执行一次
* 参数1. 定是执行的任务
* 参数2. 时长数字
* 参数3. 时长数字的时间单位,TimeUnit的常量指定
*/
scheduleService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("床前明月光");
}
}, 5, TimeUnit.SECONDS);
/**
* 周期执行的任务
* @params
* 任务
* 延迟时间数字(第一次执行在什么时间以后)
* 周期时长数字
* 时长数字的单位
*/
scheduleService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("5s种后执行,每隔1s执行一次");
}
}, 5, 1, TimeUnit.SECONDS);
}
}
9.lambda表达式
Lambda 表达式是一个匿名函数(对于 Java 而言并不很准确,但这里我们不纠结这个问题)。简单来说,这是一种没有声明的方法,即没有访问修饰符,返回值声明和名称。
在仅使用一次方法的地方特别有用,方法定义很短。它为我们节省了,如包含类声明和编写单独方法的工作。 Java 中的 Lambda 表达式通常使用语法是 (argument) -> (body),比如:
(arg1, arg2...) -> { body }
(type1 arg1, type2 arg2...) -> { body }
public class Learn0304_8 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("床前明月光");
}
});
t.start();
// lambda
Thread t2 = new Thread(() -> System.out.println("床前明月光"));
t2.start();
// custom
print(new getNumber() {
@Override
public int sum(int x, int y) {
return x + y;
}
}, 100, 200);
// lambda
print((int x, int y) -> {
return x + y;
}, 100, 200);
}
static public void print(getNumber g, int x, int y) {
System.out.println(g.sum(x, y));
}
}
interface getNumber {
int sum(int x, int y);
}
10.网络编程
1.tcp编程
在开发网络应用程序的时候,我们又会遇到Socket这个概念。Socket是一个抽象概念,一个应用程序通过一个Socket来建立一个远程连接,而Socket内部通过TCP/IP协议把数据传输到网络
使用Socket进行网络编程时,本质上就是两个进程之间的网络通信。其中一个进程必须充当服务器端,它会主动监听某个指定的端口,另一个进程必须充当客户端,它必须主动连接服务器的IP地址和指定端口,如果连接成功,服务器端和客户端就成功地建立了一个TCP连接,双方后续就可以随时发送和接收数据。
因此,当Socket连接成功地在服务器端和客户端之间建立后:
对服务器端来说,它的Socket是指定的IP地址和指定的端口号;
对客户端来说,它的Socket是它所在计算机的IP地址和一个由操作系统分配的随机端口号
// server
public class TestServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(4000);
// 等待客户端连接
/*Socket socket = server.accept();
System.out.println("客户端连接成功");
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("welcome connect.");
InputStream is = socket.getInputStream();
BufferedReader bf = new BufferedReader(new InputStreamReader(is));
String msg = bf.readLine();
System.out.println("服务器接收到客户端消息:" + msg);*/
/**
* 多线程服务器
*/
while (true) {
Socket socket = server.accept();
new Thread() {
@Override
public void run() {
try {
// 获取输入流
/*InputStream is = socket.getInputStream();
BufferedReader bf = new BufferedReader(new InputStreamReader(is));
String clientMsg = bf.readLine();*/
// 输出流
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("message from server");
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
}
// client
public class TestClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 4000);
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String msg = br.readLine();
System.out.println("客户端接收到消息:" + msg);
OutputStream os = socket.getOutputStream();
PrintStream ps = new PrintStream(os);
ps.println("message from client");
}
}