前言:
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
一、为什么使用多线程
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率(我们知道现在的cpu一般都是多核的,多核情况下
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
二、java多线程的创建方式
方式1:继承Thread,重写run()方法
//实现输出100内的偶数
class demo extends Thread{
public void run()
{
for(int i=0;i<=100;i++)
{
if(i%2==0)
{
System.out.println(i);
}
}
}
}
public class day1{
public void main(String [] args)
{
demo d1=new demo();
d1.setName("线程1");
//设置线程的优先级
d1.setPriority(Thread.MAX_PRIORITY);
d1.start()
}
}
在这种方式中怎么实现真正的多线程,比如说有数据共享,利用静态static 修饰符,你创建多个对象我也只有一个。
方式2:实现Runnable接口,重写其中的run()方法,生成对象传递给Thread构造器
共用同一个对象用于实现数据共享,多个平台卖100张票的例子
class Window1 implements Runnable{
public int ticket=100;
public void run()
{
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}else{
break;
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
方式3:实现Callable接口
创建实现Callabel接口的实现类,实现call方法
创建Callable接口实现类的对象
将此对象传递到FutureTask构造器中,创建FutureTask的对象
将FutureTask的对象传递给Thread类的构造器中,创建Thread对象,调用start方法。
获取call方法的返回值。
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
- call()可以有返回值的。
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
- Callable是支持泛型的
class NumThread implements
方式4:创建线程池
优势
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
在实际情况中,我们需要依赖ExecutorService接口来实现,ExecutorService的实现类是ThreadPoolExecutor,我们可以通过该实现类的对象来设置线程池的相关属性。
//设置
ExecutorService service=Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1=(ThreadPoolExecutor) service;
service1.setCorePoolSize(15);
service1.setKeepAliveTime();
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
三、多种实现方式的比较
开发中:优先选择:实现Runnable接口的方式
原因:
- 实现的方式没有类的单继承性的局限性,因为要继承Thread类的类(window)自己也有一套继承体系,但是由于java只允许单继承,所以继承一个类就无法继承其他类,java中为什么会出现接口,就是为了破单继承。
- 实现的方式更适合来处理多个线程有共享数据的情况。
- 联系:public class Thread implements Runnable
- 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
四、线程的分类
守护线程、用户线程
五、线程的生命周期
Thread.State
六、线程的安全问题
####问题概述
- 实际问题引出:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
- 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
- 解决方案:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
Java中的同步机制
同步代码块
- 代码
synchronized(同步监视器){
//需要被同步的代码
}
- 操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了
包含代码少了依然会有线程安全问题
包含代码多了,可能想达到的并行效果就没有了
举一个例子:
下面的代码中是不能将while(true)
包含到同步代码块中的,这样会导致一个窗口把所有的票都卖完(他拿到了锁,不出去了。。。),更不能把run方法直接包进去
while(true){
synchronized (this){//此时的this:唯一的Window1的对象 //方式二:synchronized (dog) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
-
共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
-
同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。 -
同步代码块解决继承Thread类线程安全问题
-
同步代码块解决实现Runnable接口的线程安全问题
同步方法
- 代码
public sychronized void show()
{
//其中放处理共享数据的代码
}
- 同步方法解决继承Thread类线程安全问题
注意该方法要声明称静态的,为了保证监视器的唯一,静态方法的代码中的监视器是类 - 同步方法解决实现Runnable接口的线程安全问题
- 关于同步方法的总结:
同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
lock锁
不同线程的实现方式导致的同步机制的实现的注意事项
同步监视器(锁)的唯一性的要求
-
继承Thread类的方式
慎用this充当监视器,但是可以用xxx.class代替(体现万物皆对象,类也是对象,并且解释一个问题,案例可以体现类也是对象的代码的回答) -
实现Runnable的方式
同步方式存在的局限性
- 优点:同步的方式,解决了线程的安全问题。
- 局限性:操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
七、多线程之间的通信
涉及到的三个方法:
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
- notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
方法使用说明: - wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
- wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器,
- 不能出现监视器与这三个方法调用者不一致的情况否则,会出现IllegalMonitorStateException异常
- 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
sleep()方法与wait的比较
sleep方法是要抛异常的,我们需要捕获异常进行处理。
- 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
- 2.不同点:
- 两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
- 调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
- 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
八、 面对实际问题时怎么使用多线程
- 判断是否时多线程问题
判断是否存在多个线程 - 判断是否存在线程安全问题
判断是否存在共享数据 - 共享数据的类怎么生命保证只有一份?
在各个线程中都声明一个共享数据类的对象,并且将其作作为线程子类的一个属性
4.处理线程安全问题
九、多线程在JVM中内存的结构
其中方法区与堆是多个线程共用一个,也就是一个进程一份,而虚拟机栈与程序计数器是一个线程一个,多个线 程具有多个。