Java学习笔记——多线程、GUI

本文详细介绍了多线程的基本概念,包括并发与并行的区别、线程创建方式、线程同步与通信机制等内容。探讨了不同创建线程的方法及其优缺点,并通过实例展示了线程池的使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

十二、多线程

1.并发与并行

并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。

2.创建线程方式

继承Thread

class MyThread extends Thread {         //1,定义类继承Thread
    public void run() {                 //2,重写run方法
        //业务逻辑
    }
}
public static void main(String[] args) {
    MyThread mt = new MyThread();                           //4,创建自定义类的对象
    mt.start();                                             //5,开启线程
}

实现Runnable

class MyRunnable implements Runnable {  //1,自定义类实现Runnable接口
    @Override
    public void run() {         //2,重写run方法
        //业务逻辑
    }
}
public static void main(String[] args) {
    MyRunnable mr = new MyRunnable();           //4,创建自定义类对象
    Thread t = new Thread(mr);                  //5,将其当作参数传递给Thread的构造函数
    t.start();                                  //6,开启线程
}

实现原理

查看源码
* 1,看Thread类的构造函数,传递了Runnable接口的引用
* 2,通过init()方法找到传递的target给成员变量的target赋值
* 3,查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法

两种方式的区别

继承Thread
* 好处是:可以直接使用Thread类中的方法,代码简单
* 弊端是:如果已经有了父类,就不能用这种方法
实现Runnable接口
* 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
* 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

3.常用api说明

获取当前线程的对象

Thread.currentThread();

休眠线程

try {
    Thread.sleep(10);
} catch (InterruptedException e) {
        e.printStackTrace();
}

设置守护线程

设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出

Thread t1 = new Thread() {
    public void run() {
    }
};
Thread t2 = new Thread() {
    public void run() {
    }
};
t1.setDaemon(true); //将t1设置为守护线程
t1.start();
t2.start();

join方法

join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
join(int), 可以等待指定的毫秒之后继续

final Thread t1 = new Thread() {
    public void run() {
        for(int i = 0; i < 50; i++) {
            System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaaaa");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
};
Thread t2 = new Thread() {
    public void run() {
        for(int i = 0; i < 50; i++) {
            if(i == 2) {
                try {
                    //t1.join();    //插队,加入 
                    t1.join(30);    //加入,有固定的时间,过了固定时间,继续交替执行
                    Thread.sleep(10);
                } catch (InterruptedException e) {

                    e.printStackTrace();
                }
            }
            System.out.println(getName() + "...bb");

        }
    }
};

t1.start();
t2.start();

yield让出cpu

4.同步代码块

作用

当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.

同步代码块

使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
多个同步代码块如果使用相同的锁对象, 那么他们就是同步的

Object lock = new Object();
public static void print() {
    //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
    synchronized(lock ){                
        //需要同步的业务逻辑
    }
}

同步方法

使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的

public static void function1() {
    synchronized(Printer.class){                
        //锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
    }
}

 //非静态同步函数的锁是:this
 //静态的同步函数的锁是:字节码对象

public static synchronized void function1() {   
    //需要同步的业务逻辑
}

上面两种写法等价

5.死锁案例

多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁

private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] args) {
    new Thread() {
        public void run() {
            while(true) {
                synchronized(s1) {
                    System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
                    synchronized(s2) {
                        System.out.println(getName() + "...拿到" + s2 + "开吃");
                    }
                }
            }
        }
    }.start();

    new Thread() {
        public void run() {
            while(true) {
                synchronized(s2) {
                    System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
                    synchronized(s1) {
                        System.out.println(getName() + "...拿到" + s1 + "开吃");
                    }
                }
            }
        }
    }.start();
}

6.Runtime类

Runtime r = Runtime.getRuntime();
//r.exec("shutdown -s -t 300");     //300秒后关机
r.exec("shutdown -a");              //取消关机

7.Timer类

计时器,计时执行任务

Timer t = new Timer();
t.schedule(new MyTimerTask(), new Date(114,9,15,10,54,20),3000);        
while(true) {
    System.out.println(new Date());
    Thread.sleep(1000);
}
class MyTimerTask extends TimerTask {
    @Override
    public void run() {
        System.out.println("起床背英语单词");
    }
}

8.线程间的通信

如果我们希望他们有规律的执行, 就可以使用通信

两个线程间通讯

如果希望线程等待, 就调用wait()
如果希望唤醒等待的线程, 就调用notify();
这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

三个线程之间通讯

notify()方法是随机唤醒一个线程
notifyAll()方法是唤醒所有线程
JDK5之前无法唤醒指定的一个线程
如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件

9.互斥锁

同步(何时上锁)

使用ReentrantLock类的lock()和unlock()方法进行同步

通信

使用ReentrantLock类的newCondition()方法可以获取Condition对象
需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程

10.线程组

线程组概述

Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
默认情况下,所有的线程都属于主线程组。

public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
public final String getName()//通过线程组对象获取他组的名字

我们也可以给线程设置分组

1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
2,创建线程对象
3,Thread(ThreadGroup?group, Runnable?target, String?name) 
4,设置整组的优先级或者守护线程

线程组的使用,默认是主线程组

MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, "张三");
Thread t2 = new Thread(mr, "李四");
//获取线程组
// 线程类里面的方法:public final ThreadGroup getThreadGroup()
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
// 线程组里面的方法:public final String getName()
String name1 = tg1.getName();
String name2 = tg2.getName();
System.out.println(name1);
System.out.println(name2);
// 通过结果我们知道了:线程默认情况下属于main线程组
// 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
System.out.println(Thread.currentThread().getThreadGroup().getName());

自己设定线程组

// ThreadGroup(String name)
ThreadGroup tg = new ThreadGroup("这是一个新的组");
MyRunnable mr = new MyRunnable();
// Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, mr, "张三");
Thread t2 = new Thread(tg, mr, "李四");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通过组名称设置后台线程,表示该组的线程都是后台线程
tg.setDaemon(true);

11.线程的五种状态

新建,就绪,运行,阻塞,死亡

12.线程池

概述

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。
而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

使用

JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法

public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()

这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法

Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
使用步骤:
    创建线程池对象
    创建Runnable实例
    提交Runnable实例
    关闭线程池

使用代码

// public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();

13.Callable线程

// 创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
// 结束
pool.shutdown();
public class MyCallable implements Callable<Integer> {
    private int number;
    public MyCallable(int number) {
        this.number = number;
    }
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int x = 1; x <= number; x++) {
            sum += x;
        }
        return sum;
    }
}

好处:

可以有返回值
可以抛出异常

弊端:

代码比较复杂,所以一般不用

十三、GUI

1.创建窗口

Frame  f = new Frame(“my window”);
f.setLayout(new FlowLayout());//设置布局管理器
f.setSize(500,400);//设置窗体大小
f.setLocation(300,200);//设置窗体出现在屏幕的位置
f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));

2.布局管理器

  • FlowLayout(流式布局管理器)
    • 从左到右的顺序排列。
    • Panel默认的布局管理器。
  • BorderLayout(边界布局管理器)
    • 东,南,西,北,中
    • Frame默认的布局管理器。
  • GridLayout(网格布局管理器)
    • 规则的矩阵
  • CardLayout(卡片布局管理器)
    • 选项卡
  • GridBagLayout(网格包布局管理器)
    • 非规则的矩阵

3.窗体监听

Frame f = new Frame("我的窗体");
//事件源是窗体,把监听器注册到事件源上
//事件对象传递给监听器
f.addWindowListener(new WindowAdapter() {
          public void windowClosing(WindowEvent e) {
                     //退出虚拟机,关闭窗口
        System.exit(0);
    }
});

4.GUI概念

  • 事件处理
    • 事件: 用户的一个操作
    • 事件源: 被操作的组件
    • 监听器: 一个自定义类的对象, 实现了监听器接口, 包含事件处理方法,把监听器添加在事件源上, 当事件发生的时候虚拟机就会自动调用监听器中的事件处理方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值