程序
程序program
是为了完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程
进程process
是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。【生命周期】
线程
进程可进一步细化为线程,是一个程序内部的一条执行路径。
单核CPU和多核CPU的理解
Java
应用程序java.exe
,其实至少有三个线程。main()主线程
、gc()垃圾回收线程
、异常处理线程
。并行与并发
并行:多个CPU同时执行多个任务。
并发:一个CPU(采用时间片)同时执行多个任务。
使用多线程的优点
何时需要多线程
Java
语言的JVM
允许程序运行多个线程,它通过java.lang.Thread
类来体现。
Thread
类的特性
Thread
对象的run()
方法来完成操作的,经常把run()
方法的主体称为线程体。Thread
对象的start()
方法来启动这个线程,而非直接调用run()
。Thread
类的构造器
Thread()
:创建新的Thread
对象Thread(String threadname)
:创建线程并指定线程实例名。Thread(Runnable target)
:指定创建线程的目标对象,它实现了Runnable
接口中的run
方法。Thread(Runnable target, String name)
:创建新的Thread
对象。多线程的创建一:继承Thread
类
Thread
类的子类。Thread
类的run()
----> 将此线程执行的操作声明在run()
中。Thread
类的子类的对象。start()
。// 1.创建一个继承与Thread类的子类。
class MyThread extends Thread{// 2.重写Thread类的run() public void run() {for(int i = 0; i < 100; i++) {if(i % 2 == 0) {System.out.println(Thread.currentThread().getName() + ":" + i);}}}
}public class ThreadTest {public static void main(String[] args) {// 1.创建Thread类的子类的对象MyThread t1 = new MyThread();// 2.通过此对象调用start()t1.start();}
}
多线程的创建二:实现Runnable
接口
Runnable
接口的类。Runnable
中的抽象方法:run()
Thread
类的构造器中,创建Thread
类的对象。Thread
类的对象调用start()
。// 1.创建一个实现了Runnable接口的类
class MThread implements Runnable {// 2.实现类去实现`Runnable`中的抽象方法:run()public void run() {for(int i = 0; i < 100; i++) {if(i % 2 == 0) {System.out.println(Thread.currentThread().getName() + ":" + i);}}}
}public class ThreadTest1 {public static void main(String[] args) {// 3.创建实现类的对象MThread mThread = new MThread();// 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象Thread t1 = new Thread(mThread);// 5.通过Thread类的对象调用start()t1.start();}
}
比较创建线程的两种方式
Runnable
接口的方式。public class Thread implememts Runnable
run()
,将线程要执行的逻辑声明在run()
中。Thread
中的常用方法
start()
:启动当前线程,调用当前线程的run()
。run()
:通常需要重写Thread
类中的此方法,将创建的线程要执行的操作声明在此方法中。currentThread()
:静态方法,返回执行当前代码的线程。getName()
:获取当前线程的名字。setName()
:设置当前线程的名字。yield()
:释放当前cpu
执行权。join()
:在线程a
中调用线程b
的join()
,此时线程a
就进入阻塞状态,知道线程b
完全执行完以后,线程a
才结束阻塞状态。stop()
:已过时。当执行此方法时,强制结束当前线程。isAlive()
:判断当前线程是否存活。线程的优先级
MAX_PRIORITY: 10
MIN_PRIORITY: 1
NORM_PRIORITY: 5 --> 默认优先级
如何获取和设置当前线程的优先级
getPriority()
:获取线程的优先级。setPriority(int p)
:设置线程的优先级。说明
高优先级的线程要抢占低优先级线程cpu
的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
线程的分类
Java
中的线程分为两类:一种是守护线程,一种是用户线程。
JVM
何时离开。start()
方法前调用thread.setDaemon(true)
可以把一个用户线程变成一个守护线程。Java
垃圾回收就是一个典型的守护线程。JVM
中都是守护线程,当前JVM
将退出。JDK
中用Thread.State
类定义了线程的几种状态
想要实现多线程,必须在主线程中创建新的线程对象。Java
语言使用Thread
类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态。
新建
当一个Thread
或其子类的对象被声明并创建时,新生的线程对象处于新建状态。
就绪
处于新建状态的线程被start()
后,将进入线程队列等待CPU
时间片,此时它已具备了运行的条件,只是没分配到CPU
资源。
运行
当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()
方法定义了线程的操作和功能。
阻塞
在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU
并临时中止自己的执行,进入阻塞状态。
死亡
线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束。
线程的生命周期图解
多线程出现了安全问题
问题的原因
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。Java
对于多线程的安全问题提供了专业的解决方式:同步机制。
方式一:同步代码块
格式
synchronized(同步监视器) {// 需要被同步的代码
}
操作共享数据的代码,即为需要被同步的代码。
共享数据:多个线程共同操作的变量。
同步监视器【俗称:锁】。任何一个类的对象,都可以充当锁。多个线程必须要共用同一把锁。
解决实现Runnable
接口方式的线程安全问题
class Window1 implements Runnable {private int ticket = 100;public void run() {while(true) {synchronized (this) {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(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("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}
}
在实现Runnable
接口创建多线程的方式中,我们可以考虑使用this
充当同步监视器。
解决继承Thread
类方式的线程安全问题
class Window2 extends Thread {private static int ticket = 100;public void run() {while(true) {synchronized(Window2.class) {if(ticket > 0) {try{Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + ":买票,票号为:" + ticket);ticket--;} else {break;}}}}
}public class WindowTest2 {public static void main(String[] args) {Window2 t1 = new Window2();Window2 t2 = new Window2();Window2 t3 = new Window2();t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}
}
在继承Thread
类创建多线程的方式中,慎用this
充当同步监视器,考虑使用当前类充当同步监视器。
方式二:同步方法
格式
权限修饰符 synchronized 返回值类型 方法名() {}
如果操作共享数据的代码完整的声明在以方法中,我们不妨将此方法声明为同步的。
同步方法仍然设计到同步监视,只是不需要我们显式的声明。
非静态的同步方法,同步监视器是this
。静态的同步方式,同步监视器是当前类本身。
解决实现Runnable
接口方式的线程安全问题
class Window3 implements Runnable {private int ticket = 100;public void run() {while(true) {show();}}private synchronized void show() {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + ":买票,票号为:" + ticket);ticket--;}}
}public class WindowTest3 {public static void main(String[] args) {Window3 w = new Window3();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();}
}
解决继承Thread
类方式的线程安全问题
class Window4 extends Thread {private static int ticket = 100;public void run() {while(true) {show();}}public static synchronized void show() {if(ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);ticket--;}}
}public class WindowTest4 {public static void main(String[] args) {Window4 t1 = new Window4();Window4 t2 = new Window4();Window4 t3 = new Window4();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
方式三:Lock
(锁)
JDK 5.0
开始,Java
提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock
对象充当。java.util.concurrent.locks.Lock
接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock
对象加锁,线程开始访问共享资源之前应先获得Lock
对象。ReentrantLock
类实现了Lock
,它拥有synchronized
相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock
,可以显示加锁、释放锁。class Window implements Runnable {private int ticket = 100;// 1.实例化ReentrantLockprivate ReentrantLock lock = new ReentrantLock();public void run() {while(true) {try {// 2.调用锁定方法lock()lock.lock();if(ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(getName() + ":售票,票号为:" + ticket);ticket--;} else {break;}} finally {// 3.调用解锁方法:unlock()lock.unlock();}}}
}public class LockTest {public static void main(String[] args) {Window w = new Window();Thread t1 = new Thread(w);Thread t2 = new Thread(w);Thread t3 = new Thread(w);t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}
}
synchronized
与Lock
的异同
相同点:二者都可以解决线程安全问题。
不同点:
synchronized
机制在执行完相应的同步代码以后,自动的释放同步监视器。Lock
需要手动的启动同步lock()
,同时结束也需要手动的实现unlock()
。线程死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
public class ThreadTest {public static void main(String[] args) {StringBuffer s1 = new StringBuffer();StringBuffer s2 = new StringBuffer();new Thread() {public void run() {synchronized(s1) {s1.append("a");s2.append("1");try{Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}synchronized (s2) {s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();new Thread(new Runnable() {public void run() {synchronized (s2) {s1.append("c");s2.append("3");try{Thread.sleep(100);}catch (InterruptedException e) {e.printStackTrace();}synchronized (s1) {s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}).start();}
}
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
使用两个线程交替打印1-100
class Number implements Runnable {private int number = 1;public void run() {while(true) {synchronized (this) {// 线程通信方法二notify();if(number <= 100) {System.out.println(Thread.currentThread().getName() + ":" + number);number++;try{// 线程通信方法一wait();} catch (InterruptedException e) {e.printStackTrace();}} else {break;}}}}
}public class CommunicationTest {public static void main(String[] args){Number number = new Number();Thread t1 = new Thread(number);Thread t2 = new Thread(number);t1.setName("线程1");t2.setName("线程2");t1.start();t2.start();}
}
线程通信涉及到的方法
wait()
:一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。notify()
:一旦执行此方法,就会唤醒被wait
的一个线程。如果有多个线程被wait
,就唤醒优先级高的那个。notifyAll()
:一旦执行此方法,就会唤醒所有被wait
的线程。说明
wait()
、notify()
、notifyAll()
这三个方法必须使用在同步代码块或同步方法中。wait()
、notify()
、notifyAll()
这三个方法的调用这必须是同步代码块或同步方法中的同步监视器。否则会出现IllegalMonitorStateException
异常。wait()
、notify()
、notifyAll()
这三个方法是定义在java.lang.Object
类中。sleep()
和wait()
的异同
相同点
一旦执行方法,都可以使得线程进入阻塞状态。
不同点
Thread
类中声明sleep()
,Object
类中声明wait()
。sleep()
可以在任何场景下调用。wait()
必须使用在同步代码块或同步方法中。sleep()
不会释放锁,wait()
会释放锁。方式一:实现Callable
接口
Runnable
相比,Callable
功能更强大一些。 run()
方法,可以有返回值。FutrueTask
类 FutrueTask
是Futrue
接口的唯一实现类。FutrueTask
同时实现了Runnable
、Future
接口。它既可以作为Runnable
被线程执行,又可以作为Future
得到Callable
的返回值。//1.创建一个实现Callable的实现类
class NumThread implements Callable{//2.实现call方法,将此线程需要执行的操作声明在call()中@Overridepublic Object call() throws Exception {int sum = 0;for (int i = 1; i <= 100; i++) {if(i % 2 == 0){System.out.println(i);sum += i;}}return sum;}
}public class ThreadNew {public static void main(String[] args) {//3.创建Callable接口实现类的对象NumThread numThread = new NumThread();//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象FutureTask futureTask = new FutureTask(numThread);//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()new Thread(futureTask).start();try {//6.获取Callable中call方法的返回值//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。Object sum = futureTask.get();System.out.println("总和为:" + sum);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}
}
方式二:使用线程池
背景
经常创建和销毁、使用量特别大的资源【并发情况下的线程】,对性能影响很大。
思路
提前创建好多个线程,放入线程池中,使用时直接获取,用完放回池中。可以避免频繁创建销毁、实现重复利用
好处
线程池相关API
JDK 5.0
起提供了线程池相关API
:ExecutoService
和Executors
。ExecutorService
:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command)
:执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task)
:执行任务,有返回值,一般又来执行Callable
void shutdown()
:关闭连接池Executors
:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。 Executors.newCachedThreadPool()
:创建一个可根据需要创建新线程的线程池Executors.newFixedThreadPool(n)
:创建一个可重用固定线程数的线程池Executors.newSingleThreadExecutor()
:创建一个只有一个线程的线程池Executors.newScheduledThreadPool(n)
:创建一个线程池,它可安排在给定延迟后运行命令或定期执行class NumberThread implements Runnable{@Overridepublic void run() {for(int i = 0;i <= 100;i++){if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ": " + i);}}}
}public class ThreadPool {public static void main(String[] args) {//1. 提供指定线程数量的线程池ExecutorService service = Executors.newFixedThreadPool(10);//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象service.execute(new NumberThread());//3.关闭连接池service.shutdown();}
}