线程和进程的基本概念
程序:用某种语言编写的一段指令的集合,静态代码静态对象进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行 . 一个进程最少有一个线程 ,线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分 成若干个线程
并发与并行 &&同步与异步
并发:一个CPU同时执行多个任务
并行:多个CPU同时执行多个不同任务
前者是逻辑上的同时发生,后者是物理上的同时发生。
同步:排队执行 , 效率低但是安全。
异步:同时执行 , 效率高但是数据不安全。
线程的调度方式
分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)
注意:多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使
用率更高。
线程的相关API
//获取当前线程的名字
Thread.currentThread().getName()
1.start():1.启动当前线程2.调用线程中的run方法
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活
多线程的创建方式
1.继承Thread
1.创建一个集成于Thread类的子类 (通过ctrl+o(override)输入run查找run方法)
2.重写Thread类的run()方法
3.创建Thread子类的对象
4.通过此对象调用start()方法创建线程的方式: MyThread myThread = new MyThread();
2.实现Runnable
相比较Thread而言,通过实现runnable的方式可以更容易地实现资源共享,并且接口可以多实现且还能再继承其他类.
面向接口编程, 松耦合设计
不能独立运行, 需绑定在Thread实例上运行
主线程不能监控子线程何时结束, 也不能获取子线程返回结果
切记启动异步线程的方式是调用start()方法, 而非调用run()方法.
主线程不能捕获子线程的抛出的异常, 通常会在run()方法中包裹一个最大的try-catch,自行处理异常步骤
1.创建一个实现了Runable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
package com.example.paoduantui.Thread;
public class ThreadDemo01 {
public static void main(String[] args){
window1 w = new window1();
//虽然有三个线程,但是只有一个窗口类实现的Runnable方法,由于三个线程共用一个window对象,所以自动共用100张票
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();
}
}
class window1 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
if(ticket>0){
// try {
// sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
ticket--;
}else{
break;
}
}
}
}
3.实现callable接口方式:
Runnable与Callable
接口定义//Callable 接口public interface Callable<V> {V call() throws Exception;}//Runnable 接口public interface Runnable {public abstract void run();}
callable使用步骤
1. 编写类实现 Callable 接口 , 实现 call 方法class XXX implements Callable<T> {@Overridepublic <T> call() throws Exception {return T;}}2. 创建 FutureTask 对象 , 并传入第一步编写的 Callable 类对象FutureTask<Integer> future = new FutureTask<>(callable);3. 通过 Thread, 启动线程new Thread(future).start();
Runnable与Callable 相同点与不同点
相同:都是接口
都可以编写多线程程序都采用 Thread.start() 启动线程不同:Runnable 没有返回值; Callable 可以返回执行结果Callable 接口的 call() 允许抛出异常; Runnable 的 run() 不能抛出注意:Callalble 接口支持返回执行结果,需要调用 FutureTask.get() 得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
4.线程池方式
线程池 Executors
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。好处:降低资源消耗。提高响应速度。提高线程的可管理性。
Java中的四种线程池 . ExecutorService
缓存线程池:ExecutorService service =Executors.newCachedThreadPool();
定长线程池:ExecutorService service =Executors.newFixedThreadPool();
单线程线程池:ExecutorService service =Executors.newSingleThreadExecutor();
周期性任务定长线程池:
ScheduledExecutorService service = Executors.newScheduledThreadPool();
线程的生命周期:
线程通信的常用方法:
线程的安全问题:
线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。
线程安全解决方法:
方式1:同步代码块:
使用同步监视器(锁)
Synchronized(同步监视器){
//需要被同步的代码
}
方式2:同步方法:
使用同步方法,对方法进行synchronized关键字修饰
将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)
注意
1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
2.非静态的同步方法,同步监视器是this
静态的同步方法,同步监视器是当前类本身。继承自Thread。class
方式3:显式锁lock:
package com.example.paoduantui.Thread; import java.util.concurrent.locks.ReentrantLock; class Window implements Runnable{ private int ticket = 100;//定义一百张票 //1.实例化锁 private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while (true) { //2.调用锁定方法lock lock.lock(); if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售出第" + ticket + "张票"); ticket--; } else { break; } } } } 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("窗口1"); t2.setName("窗口1"); t3.setName("窗口1"); t1.start(); t2.start(); t3.start(); } }
Synchronized与lock的异同
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器
lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(同时以为着lock的方式更为灵活)优先使用顺序:
LOCK->同步代码块->同步方法