##day01 笔记:
一、多线程
1.线程:程序中一个单一的顺序执行流程,即:线性流程。代码一句一句的有先后顺序的执行。
2.多线程:多个线性流程"一起"执行。
3.线程是并发运行的。
1)并发:线程间的代码在微观世界都是走走停停的,宏观上给我们的感受是在一起执行
2)用途:
- 当出现多个代码片段执行顺序有冲突时,希望它们各干各的时就应当放在不同线程上"同时"运行
- 一个线程可以运行,但是多个线程可以更快时,可以使用多线程运行
二、线程的创建
第一种方式:继承Thread并重写run方法
* 第一种创建线程的方式的优点:结构简单,利于匿名内部类创建
* 缺点:
* 1:存在继承冲突问题,由于java是单继承的,这导致如果我们继承了Thread就无法再 继承其他类去复用方法。这在实际开发中是极其不方便的
* 2:当我们继承Thread并重写run方法,在run方法中定义线程要执行的任务。这会导致 线程与任务存在一个必然的耦合关系,不利于线程的重用。
main{
Thread t1 = new MyThread1();
Thread t2 = new MyThread2();
//启动线程要调用start方法,而不是直接调用run方法!!
/* 当我们调用线程的start方法后,线程会纳入到线程调度器中被统一管理。
当线程第一次分配时间片后会自动调用它的run方法开始执行。 */
t1.start();
t2.start();
}
class myThread extends Thread{
public void run(){
for (int i = 0; i <1000 ; i++) {
System.out.println("汤蠢蠢");
}
}
}
class myThread1 extends Thread{
public void run(){
for (int i = 0; i <1000 ; i++) {
System.out.println("平笨笨");
}
第二种创建线程的方式:单独定义线程任务
* 定义任务:
* 1:实现Runnable接口
* 2:实现Callable接口(如果线程执行完毕后需要返回值时使用,多用于线程池)
main{
//1创建线程要执行的任务
Runnable r1 = new MyRunnable1();
Runnable r2 = new MyRunnable2();
//2创建线程
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
//3启动
t1.start();
t2.start();
}
class MyRunnable1 implements Runnable{
public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println("你是谁啊?");
}
class MyRunnable2 implements Runnable{
public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println("查水表的!");
}
三、主线程:
1. java中的代码都是靠线程运行的,执行main方法的线程称为"主线程"。
2.线程提供了一个方法:- static Thread currentThread()
-该方法可以获取运行这个方法的线程
public class CurrentThreadDemo {
public static void main(String[] args) {
//让主线程执行该方法,此时该方法返回的就是主线程
Thread main = Thread.currentThread();
System.out.println(main);
dosome();//主线程执行dosome方法
Thread myThread = new Thread(){
public void run(){
dosome();//自定义线程执行dosome
}
};
myThread.start();
}
public static void dosome(){
//获取执行dosome方法的线程
Thread t = Thread.currentThread();
System.out.println("执行dosome方法的线程是:"+t);
}
}
四、使用多线程实现多客户端连接服务端
// 服务端开始工作的方法
public void start(){
try {
/* ServerSocket的一个重要方法: Socket accept()
该方法用于接受客户端的连接。这是一个阻塞方法,调用后会"卡住",直到
一个客户端与ServerSocket连接,此时该方法会立即返回一个Socket实例
通过这个Socket实例与该客户端对等连接并进行通讯。
相当于"接电话"的动作 */
while(true) {
System.out.println("等待客户端连接...");
Socket socket = serverSocket.accept();
System.out.println("一个客户端连接了!");
//启动一个线程负责与该客户端交互
ClientHandler handler = new ClientHandler(socket);
//创建线程
Thread t = new Thread(handler);
//启动线程
t.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server_V2 server = new Server_V2();
server.start();
}
/* 第二种创建线程的方式:实现Runnable接口单独定义线程任务
* 这个线程任务就是让一个线程与指定的客户端进行交互 */
private class ClientHandler implements Runnable{
private Socket socket;
private String host; //客户端地址
public ClientHandler(Socket socket){
this.socket = socket;
host = socket.getInetAddress().getHostAddress();
}
public void run(){
try {
InputStream in = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(host+"说:" + line);
}
} catch (IOException e) {
e.printStackTrace();
}
五、线程API
1.获取线程相关信息的方法
1)void run():线程本身有run方法,可以在第一种创建线程时重写该方法来定义线程任务。
2)void start():启动线程的方法。调用后线程被纳入到线程调度器中统一管理,并处于RUNNABLE状态,等待分配时间片开始并发运行。
注:线程第一次获取时间片开始执行时会自动执行run方法。启动线程一定是调用start方法,而不能调用run方法
3)String getName():获取线程名字
4)long getId():获取线程唯一标识
5)int getPriority():获取线程优先级,对应的是整数1-10
6)boolean isAlive():线程是否还活着
7)boolean isDaemon():是否为守护线程
8)boolean isInterrupted():是否被中断了
9)void setPriority(int priority):设置线程优先级
2.线程优先级:
1.线程start后会纳入到线程调度器中统一管理,线程只能被动的被分配时间片并发运行,而无法主动索取时间片.线程调度器尽可能均匀的将时间片分配给每个线程.
2.线程有10个优先级,使用整数1-10表示
- 1为最小优先级,10为最高优先级.5为默认值
- 调整线程的优先级可以最大程度的干涉获取时间片的几率.优先级越高的线程获取时间片的次数越多,反之则越少.
- Thread提供了对应的常量:MAX_PRIORITY表示最高优先级10,MIN_PRIORITY表示最低优先级,NORM_PRIORITY表示默认优先级5
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();
}
}
3.sleep阻塞
- 线程提供了一个静态方法:static void sleep(long ms)
- 使运行该方法的线程进入阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE状态等待再次获取时间片并发运行
-sleep方法处理异常:InterruptedException.
当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的interrupt()方法被调用时,sleep方法会抛出该异常从而打断睡眠阻塞.
Thread tang = new Thread("汤紫轩"){
public void run(){
try {
System.out.println(getName()+":上课太困了睡一会吧");
Thread.sleep(5000000);
} catch (InterruptedException e) {
System.out.println("别闹,困着呢~抬头看看,诶?老师我错了");
}
System.out.println(getName()+":下课了,好,我继续睡");
} };
Thread teacher = new Thread("传奇老师"){
public void run(){
System.out.println(getName()+":大家抬头!");
for (int i = 1; i <6 ; i++) {
System.out.println("看这个知识点"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+":诶干嘛呢?醒醒");
tang.interrupt();
}
};
tang.start();
teacher.start();
六、守护线程
1.守护线程也称为:后台线程
- 守护线程是通过普通线程调用setDaemon(boolean on)方法设置而来的,因此创建上与普通线程无异.
- 守护线程的结束时机上有一点与普通线程不同,即:进程的结束.
- 进程结束:当一个进程中的所有普通线程都结束时,进程就会结束,此时会杀掉所有正在运行的守护线程.
2.用途
- GC就是运行在守护线程上的
- 当我们有一个任务不需要关注何时结束,当程序需要结束时可以一起结束的任务就可以放在守护线程上执行
Thread rose = new Thread("rose"){
public void run(){
for(int i=0;i<5;i++){
System.out.println(getName()+":let me go !!!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println(getName()+":啊啊啊啊啊AAAAAAaaaaa....");
System.out.println("噗通!");
}
};
Thread jack = new Thread("jack"){
public void run(){
while(true){
System.out.println(getName()+":you jump!i jump!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}};
rose.start();
jack.setDaemon(true);//设置守护线程必须在线程启动前进行
jack.start();