JAVAThread 多线程学习

时间:2022-12-25 10:38:03

开始第二遍学习java了,发现有好多的知识点在之前学习的时候是不了解的。在java多线程的板块中,学到了除了继承Thread 实现runnable接口以外 还学到了第三种的基于线程池的实现callable接口的线程方式。感觉java真实博大精深。对java线程的笔记总结:

java线程

java程序由一条线程执行完毕 称为单线程程序

java程序由多条程序执行完毕 称为多线程程序

1:多线程 
(1)多线程:一个应用程序有多条执行路径 进程:正在执行的应用程序 线程:进程的执行单元,执行路径 单线程:一个应用程序只有一条执行路径 多线程:一个应用程序有多条执行路径

    多进程的意义?   
提高CPU的使用率
多线程的意义?
提高应用程序的使用率
(2)Java程序的运行原理及JVM的启动是多线程的吗?
A:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。
B:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。

(3)多线程的实现方案

A:继承Thread类
B:实现Runnable接口
* 两种方式的比较   

JAVAThread 多线程学习

(4)线程的调度和优先级问题

A:线程的调度
  • a:分时调度
  • b:抢占式调度 (Java采用的是该调度方式)
  • B:获取和设置线程优先级
    a:默认是5
    b:范围是1-10

(5)线程的控制(常见方法)

A:休眠线程

API方法:

线程睡眠毫秒数

  • public static void sleep(long millis)

B:加入线程

API方法

join:等待线程终止,等待线程执行完毕后其它的线程才可以运行

  • public final void join();

C:礼让线程

API方法

一定程度上让多个线程的执行和谐,不靠谱

  • public static void yield()

D:后台线程

API方法
  • public final void setDaemon(boolean on) 
    当正在运行的线程都是守护线程时,java虚拟机退出,该方法必须在线程启动之前调用 
    标记为守护线程后,线程会依附于某个线程,不会独立的run

E:终止线程(掌握)

API方法
  • public final void stop() 让线程停止,已过时

  • public void interrupt() 终止线程,并且抛出InterruptedException异常,并会执行后续的代码

线程的生命周期(参照 线程生命周期图解.bmp)

JAVAThread 多线程学习

A:新建

创建线程对象的过程

B:就绪

有执行的条件和资格,没有执行权

c:运行

有运行的资格,有执行权

D:阻塞

没有执行资格,没有执行权

E:死亡

线程对象变成垃圾,等待被回收

(7)电影院卖票程序的实现
A:继承Thread类
B:实现Runnable接口
(8)电影院卖票程序出问题
A:为了更符合真实的场景,加入了休眠100毫秒。
B:卖票问题
a:同票多次
b:负数票
(9)多线程安全问题的原因(也是我们以后判断一个程序是否有线程安全问题的依据)
A:是否有多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据

(10)同步解决线程安全问题

A:同步代码块

synchronized(对象) { 
需要被同步的代码; 
}

这里的锁对象可以是任意对象。

B:同步方法

把同步加在方法上。

这里的锁对象是this

C:静态同步方法

把同步加在方法上。

这里的锁对象是当前类的字节码文件对象(反射再讲字节码文件对象)

(11)回顾以前的线程安全的类
  • A:StringBuffer
  • B:Vector
  • C:Hashtable
  • D:如何把一个线程不安全的集合类变成一个线程安全的集合类用Collections工具类的方法即可。

相关知识点

(1)JDK5以后的针对线程的锁定操作和释放操作

Lock锁
    // 定义锁对象
private Lock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
try {
// 加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
} finally {
// 释放锁
lock.unlock();
}
}
}
(2)死锁问题的描述和代码体现
  • 是指两个或者两个以上的线程在执行的过程中,因争夺资源产一种互相等待现象
  • 同步代码块的嵌套案例
    @Override
public void run() {
// 死锁代码
if (flag) {
// 同步嵌套
synchronized (MyLock.objA) {
System.out.println("if obja");
synchronized (MyLock.objB) {
System.out.println("if objb");
}
}
} else {
synchronized (MyLock.objB) {
System.out.println("else objb");
synchronized (MyLock.objA) {
System.out.println("else obja");

}
}
}
}
(3)生产者和消费者多线程体现(线程间通信问题)
    以学生作为资源来实现的

资源类:Student
设置数据类:SetThread(生产者)
获取数据类:GetThread(消费者)
测试类:StudentDemo

代码:
A:最基本的版本,只有一个数据。
B:改进版本,给出了不同的数据,并加入了同步机制
C:等待唤醒机制改进该程序,让数据能够实现依次的出现
wait() 线程等待后立即释放所持有的锁,被唤醒后在等待的位置继续执行
notify() 唤醒并不代表立马可以执行 线程会转为就绪状态 等待下一次执行
notifyAll() (多生产多消费)
  • 为什么定义在Object中?
    • 这些方法的调用通过锁对象调用,而我们使用的锁可能是任意锁对象。所以,这些方法必须定义在Object类中 =* 等待唤醒机制的代码优化。把数据及操作都写在了资源类中
(4)线程组
  • Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。 默认情况下,所有的线程都属于主线程组(main组)。 
    返回该线程所属的线程组
  • public final ThreadGroup getThreadGroup() 
    我们也可以给线程设置分组
  • Thread(ThreadGroup group, Runnable target, String name)
    新建线程组代码:

        // ThreadGroup(String name)
    ThreadGroup tg = new ThreadGroup("这是一个新的组");

    MyRunnable my = new MyRunnable();
    // Thread(ThreadGroup group, Runnable target, String name)
    Thread t1 = new Thread(tg, my, "林青霞");
    Thread t2 = new Thread(tg, my, "刘意");

    System.out.println(t1.getThreadGroup().getName());
    System.out.println(t2.getThreadGroup().getName());

    //通过组名称设置后台线程,表示该组的线程都是后台线程
    tg.setDaemon(true);
(5)线程池

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

  • 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
  • 在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
  • JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法

public static ExecutorService newCachedThreadPool()

创建一个具有缓存功能的线程池
缓存:百度浏览过的信息再次访问

public static ExecutorService newFixedThreadPool(int nThreads)

创建一个可重用的,具有固定线程数的线程池

public static ExecutorService newSingleThreadExecutor()

创建一个只有单线程的线程池,相当于上个方法的参数是1   

protected void shutdown() 
顺序关闭线程池中的线程 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法 
Future<?> submit(Runnable task) 
Future submit(Callable task)

(6)多线程实现的第三种方案

实现Callable接口 步骤和刚才演示线程池执行Runnable对象的差不多。 但是还可以更好玩一些,求和案例演示 好处: 可以有返回值 可以抛出异常 弊端: 代码比较复杂,所以一般不用

匿名内部类方式使用多线程

new Thread(){代码…}.start(); New Thread(new Runnable(){代码…}).start();

多线程的应用

定时器的使用
描述:
  • 定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能

  • Timer 一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。

public Timer() 
public void schedule(TimerTask task, long delay) 
public void schedule(TimerTask task,long delay,long period) 
* TimerTask 任务类用于为Timer指定任务 public abstract void run() 
public boolean cancel() 
* 开发中 
Quartz是一个完全由java编写的开源调度框架。

线程的生命周期转换图:

JAVAThread 多线程学习