程序 , 进程 , 线程
程序(programm):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的 代码。
进程(process)
概念:程序的一次执行过程,或是正在运行的一个程序。
说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(thread) 概念:进程可进一步细化为线程,是一个程序内部的一条执行路径。
说明:线程作为调度和执行的单位,每个线程拥独立的运行栈和程序计数器(pc),线程切换的 开销小。
每个线程,拥有自己独立的:栈、程序计数器
多个线程,共享同一个进程中的结构:方法区、堆。
线程的创建和使用
方式一: 继承 Thread类
-
创建一个继承于 Thread 类的子类
-
重写 Thread 类的 run() --> 将此线程执行的操作声明在 run() 中
-
创建 Thread 类的子类的对象
-
通过此对象调用 start() :①启动当前线程 ② 调用当前线程的 run()
class MyThread extends Thread { //创建一个线程,完成0-100内的偶数的输出
@Override //重写run()方法
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i + "---mythread---" + Thread.currentThread().getName());
}
}
}
public class ThreadTest1 {
public static void main(String[] args) { //通过子类对象调用start()方法开启线程 Thread
thread = new MyThread();
thread.start(); //此时有两个线程
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i + "----main()----" + Thread.currentThread().getName());
}
}
}
/* 存在交互 0---mythread 0----main()---- 2----main()---- 4----main()---- 6----main()---- 2---mythread 8----main()---- 4---mythread*/
}
}
说明两个问题:
问题一:我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。
问题二:如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象 的start().
方式二: 实现 Runnable接口
1. 创建一个实现了Runnable接口的类
2. 实现类去实现Runnable中的抽象方法:run()
3. 创建实现类的对象
4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5. 通过Thread类的对象调用start()
class MThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadTest2 {
public static void main(String[] args) {
MThread mThread = new MThread();
new Thread(mThread).start();//将实现类的对象作为真实参数传到Thread类的构造器中
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
方式三 : 实现Callable 接口
类似于实现Runnable接口, 只是 Callable接口 实现类 是作为 FutureTask类 构造器 中的参数 , 然后再将 FutureTask 对象 作为 Thread 类构造器中的参数 创建Thread对象,并调用start().
1.创建一个实现Callable的实现类
2.实现call方法,将此线程需要执行的操作声明在call()中
3.创建Callable接口实现类的对象
4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建 FutureTask的对象
5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并 调用start()
6.通过 futureTask.get() 获取Callable中call方法的返回值
//1.创建一个实现Callable的实现类
class NumThread implements Callable {
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public 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();
}
}
}
说明:
*如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
*1.call()可以返回值的。
*2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
*3.Callable是支持泛型的
方式四: 使用线程池
class NumberThread implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable {
@Override
public 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);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
说明:
*好处:
*1.提高响应速度(减少了创建新线程的时间)
*2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
*3.便于线程管理
*corePoolSize:核心池的大小
*maximumPoolSize:最大线程数
*keepAliveTime:线程没任务时最多保持多长时间后会终止
Thread类中常用的方法
* 1. start():启动当前线程;调用当前线程的run()
* 2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
* 3. currentThread():静态方法,返回执行当前代码的线程
* 4. getName():获取当前线程的名字
* 5. setName():设置当前线程的名字
* 6. yield():释放当前cpu的执行权 //不释放锁
* 7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线
程a才结束阻塞状态。
* 8. stop():已过时。当执行此方法时,强制结束当前线程。
* 9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时
间内,当前线程是阻塞状态。
* 10. isAlive():判断当前线程是否存活
线程的生命周期
当线程对象被创建时 , 它处于新建状态当使用线程对象调用 start() 后 , 线程进入就绪状态 .当线程获取到 CPU 执行权后开始运行 , 在运行过程中 , 遇到 sleep(),wait(), 等待同步锁等操作时会进入阻塞状态. 当 sleep() 结束或者获取到同步锁时 , 线程由阻塞状态进入就绪状态.当 run() 执行结束或者出现异常且未处理时 , 线程进入死亡状态 .
线程的同步机制
方式一: 同步代码块
synchronized(同步监视器){ //操作共享数据的代码 } //说明 共享数据:多个线程共同操作的变量.如售票系统中的ticket 同步监视器:锁;可以是任意一个对象;但要求必须是:多个线程共享同一个锁 //补充 在实现Runnable接口创建多线程的方式中,可以用 this 来 充当锁 在继承Thread类创建多线程的方式中,常用 当前类(如 window.class)来充当锁
class window1 implements Runnable {//创建三个窗口买票, 总票数为100张 (线程不安全的),使用实现Runnable接口的方式
private int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (this) {//锁。任何一个类的对象,都可以充当锁。 要求:多个线程必须要共用同一把锁。this:表示 w 这个对象
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() +
":买票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
方式二: 同步方法
相当于将操作共享数据的代码封装到一个方法当中 , 然后这个方法声明为synchronized 同步 方法
同步方法依然涉及到同步监视器 : 只是不需要我们显式的声明。
// 非静态同步方法中 (实现Runnable接口创建多线程方式中常用) : 锁 :this
// 静态方法中(继承Thread类创建多线程方式中使用) : 锁 : 当前类
class window2 implements Runnable {//创建三个窗口买票, 总票数为100张 ,使用实现Runnable接口的方式
private int ticket = 100;
@Override
public void run() {
while(true) {
show();
if (ticket ==0) {
return;
}
}
}
public synchronized void show() { // 同步监视器 :this
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":买票, 票号为:" + ticket);
ticket--;
}
}
}
方式三: Lock锁 ( jdk5.0 新增 )
//实例化ReentrantLock类,得到lock对象 ReentrantLock lock = new ReentrantLock(); //手动加锁,手动解锁 lock.lock(); 操作共享数据的代码 lock.unlock(); 类似于synchronized同步代码块 1. 面试题:synchronized 与 Lock的异同? * 相同:二者都可以解决线程安全问题 * 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器 * Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现 (unlock())
class window2 implements Runnable {
ReentrantLock lock = new ReentrantLock();
// ReentrantLock lock = new ReentrantLock(true); // 公平的,先进先出
@Override
public void run() {
while (true) {
try {
//手动加锁
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() +
":买票,票号为:" + ticket);
ticket--;
} else {
break;
}
} finally {
lock.unlock();//手动解锁
}
}
}
}
线程通信
线程通信涉及到的三个方法:
* wait():当前线程进入阻塞状态,并释放同步监视器。
* notify():唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
* notifyAll():唤醒所有被wait的线程。
1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
* 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视 器。否则,会出现IllegalMonitorStateException异常
* 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
多线程卖票demo
Window
public class Window implements Runnable {
// 创建10个窗口卖票,总票数为100张
private int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (this) { //锁。任何一个类的对象,都可以充当锁。 要求:多个线程必须要共用同一把锁
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()+ "在卖票,票号为: " + ticket);
ticket--;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
SaleTicket
public class SaleTicker {
public static void main(String[] args) {
Window w = new Window();
new Thread(w,"窗口1").start();
new Thread(w,"窗口2").start();
new Thread(w,"窗口3").start();
new Thread(w,"窗口4").start();
new Thread(w,"窗口5").start();
new Thread(w,"窗口6").start();
new Thread(w,"窗口7").start();
new Thread(w,"窗口8").start();
new Thread(w,"窗口9").start();
new Thread(w,"窗口10").start();
}
}
窗口10在卖票,票号为: 16
窗口10在卖票,票号为: 15
窗口9在卖票,票号为: 14
窗口9在卖票,票号为: 13
窗口9在卖票,票号为: 12
窗口9在卖票,票号为: 11
窗口9在卖票,票号为: 10
窗口9在卖票,票号为: 9
窗口9在卖票,票号为: 8
窗口9在卖票,票号为: 7
窗口9在卖票,票号为: 6
窗口9在卖票,票号为: 5
窗口8在卖票,票号为: 4
窗口8在卖票,票号为: 3
窗口8在卖票,票号为: 2
窗口8在卖票,票号为: 1