1进程和线程
进程:正在运行的程序。也就是代表了程序锁占用的内存区域。
线程:线程(thread)是操作系统能够进行运算调度的最小单位;一个进程可以开启多个线程。
进程和线程的区别:
一个软件运行至少依赖一个进程。
一个进程的运行至少依赖一个线程。
2多线程
多线程:多线程扩展了多进程的概念,使得同一个进程可以同时并发处理多个任务。
2.1线程状态
线程生命周期,总共有五种状态:
1) 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2) 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3) 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4) 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;
5) 根据阻塞产生的原因不同,阻塞状态又可以分为三种:
a) 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
b) 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
c) 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
6) 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
2.2线程创建
2.2.1继承Thread
创建对象:
Thread()
分配新的 Thread 对象。
Thread(Runnable target)
分配新的 Thread 对象。
Thread(Runnable target, String name)
分配新的 Thread 对象。
Thread(String name)
分配新的 Thread 对象。
常用方法:
long getId()
返回该线程的标识符。
String getName()
返回该线程的名称。
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
void setName(String name)
改变线程名称,使之与参数 name 相同。
static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),
void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
void stop()
终止线程。
测试:
package cn.tedu.thread;
//测试 Thread类
public class Test2_Thread {
public static void main(String[] args) {
//1,创建对象
MyThread t = new MyThread();//新建状态
//2,开启线程
// t.run();
t.setName("线程1");//修改线程名
t.start();//可运行状态
//3,run()和start()有什么区别?
//run()和start()都能执行任务,但是run()执行时就只是一个普通的方法调用没有多线程并发抢占的效果
//--模拟多线程--
MyThread t2 = new MyThread();
// t2.run();
t.setName("线程2");//修改线程名
t2.start();
/*
4,多线程的执行结果具有随机性
Thread-0---0
Thread-1---0
Thread-0---1
Thread-1---1
Thread-0---2
Thread-1---2
Thread-1---3*/
}
}
//1,继承Thread类,实现多线程编程
class MyThread extends Thread {
//2,在多线程编程里,把所有的业务放入--重写的run()里--generate--override methods..
//--需求:重复的打印10次线程名称
@Override
public void run() {//运行状态
for (int i = 0; i < 10; i++) {//fori
System.out.println(super.getName()+"---"+i);//getName()--获取线程名称
}
}//结束状态
}
2.2.2实现Runnable接口
概述:如果自己的类已经extends另一个类,就无法多继承,此时,可以实现一个Runnable接口。
常用方法:
void run()
使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。
测试:
package cn.tedu.thread;
//测试 Runnable接口
public class Test3_Runnable {
public static void main(String[] args) {
//1,创建目标对象
MyRunnable target = new MyRunnable();
//2,把要执行的目标 和Thread绑定关系
Thread t = new Thread(target);
t.setName("猪猪侠");
//3,启动线程
t.start();
//4,模拟多线程
Thread t2 = new Thread(target);
t2.setName("蜘蛛侠");
t2.start();
/* 5,多线程的随机性
Thread-0===0
Thread-0===1
Thread-1===0
Thread-1===1
Thread-1===2
Thread-1===3
Thread-1===4
Thread-0===2
Thread-0===3
*/
}
}
//1,实现Runnable接口,实现多线程编程
class MyRunnable implements Runnable{
//2,如果有自己的业务,需要把业务 -- 放在重写的run()里
//--需求:打印10次线程名称
@Override
public void run() {
for (int i = 0; i < 20; i++) {
//Thread.currentThread()--获取正在执行任务的线程对象
//getName()--获取线程的名字
//3,Thread.currentThread().getName() -- 获取当前执行任务的线程对象的 名字
System.out.println( Thread.currentThread().getName() +"==="+i );
}
}
}
2.2.3实现Callable接口
创建步骤:
1.创建Callable接口的实现类,并重写call()方法,该方法作为线程的执行体,并有返回值。
2.创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
3.创建Thread,调用start()方法。
4.可是有FutureTask.get()获取call()的返回值。
public class TestCallable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CallableDemo callableDemo = new CallableDemo();
FutureTask futureTask = new FutureTask(callableDemo);
new Thread(futureTask).start();//启动线程对象,并启动线程
//Integer a = (Integer) futureTask.get(); //通过get()获取返回值
for (int j = 0; j < 10; j++) {
System.out.println("main:"+j);
}
}
}
class CallableDemo implements Callable{
@Override
public Integer call() throws Exception {
int i =0;
for (; i < 10; i++) {
System.out.println("线程:"+i);
}
return i;
}
}
结果:
main:0
线程:0
main:1
线程:1
main:2
线程:2
main:3
线程:3
线程:4
线程:5
线程:6
线程:7
线程:8
线程:9
main:4
main:5
main:6
main:7
main:8
main:9
2.2.4创建线程池
线程池是池化思想的一种应用,都是通过复用对象,以减少因创建和释放对象所带来的资源消耗,进而来提升系统性能。
ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor之间的关系:
- Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;
- ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
- 抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
- ThreadPoolExecutor继承了类AbstractExecutorService。
推荐使用ThreadPoolExecutor创建线程
// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory);
// 向线程池提交任务
threadPool.execute(new Runnable() {
@Override
public void run() {
... // 线程执行的任务
}
});
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
4种线程池的创建方法:
- FixedThreadPool和SingleThreadExecutor:主要问题是堆积的请求处理队列均采用LinkedBlockingQueue,可能会耗费非常大的内存,甚至OOM。
- CachedThreadPool和ScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
2.3线程通信
线程通信的两种简单模型,共享内存和消息传递。
2.3.1使用volatile关键字
使用volatile关键字定义全局变量,当变量发生变化时,线程能够感应并执行相应的业务。采用了共享内存的思想。
2.3.2使用Object类提供的wait(),notify()和notifyAll()
在synchronized修饰的同步方法或代码块中使用Object类提供的wait(),notify()和notifyAll()3个方法进行线程通信。
1.wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。wait()释放锁。
2.notify():唤醒在此同步监视器上等待的单个线程。notify()不释放锁。
3.notifyAll():唤醒在此同步监视器上等待的所有线程。
3线程锁
3.1悲观锁和乐观锁
悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
3.2两种常见的锁
synchronized 互斥锁(悲观锁,有罪假设)
采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。
ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)
ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种情况下任何“读/读”、“读/写”、“写/写”操作都不能同时发生,这在一定程度上降低了吞吐量。然而读操作之间不存在数据竞争问题,如果”读/读”操作能够以共享锁的方式进行,那会进一步提升性能。因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
读写锁内部又分为读锁和写锁,读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。读锁和写锁分离从而提升程序性能,读写锁主要应用于读多写少的场景。
3.3synchronized同步锁
把共享资源,用锁锁起来,谁有钥匙,谁去用共享资源,没钥匙的就排队等待。
用法:
通过synchronized关键字实现同步锁
用在方法上:synchronized public StringBuffer append(String str)
用在代码块上:synchronized( 锁对象 ){ 需要同步的代码;}
用在方法上,使用的锁对象是默认的this.;
用在代码块上,锁的对象是任意的。
4单例设计模式
4.1饿汉式
public class Test6_Singleton {
public static void main(String[] args) {
MySingle m = MySingle.getSingle();
MySingle m2 = MySingle.getSingle();
//==如果比较的是两个引用类型的变量?比较的是地址值
System.out.println(m==m2);
}
}
//创建自己的单例程序
class MySingle {
//1,私有化构造方法,不让外界随意new
private MySingle() {}
//2,在类的内部,提供一个已经创建好的对象
//static是因为 静态资源getSingle()只能调用静态
static private MySingle single = new MySingle();
//3,对外提供一个全局访问点
//static 是因为,想要访问get()可以创建对象访问,但是目前已经不允许创建对象了。只能通过类名调用,就得修饰。
static public MySingle getSingle(){
//把内部创建好的对象返回调用位置
return single;
}
}
4.2懒汉式
//面试重点:
//1、延迟加载思想:是指不会第一时间就把对象创建好来占用内存,而是什么时候用什么时候创建
//2、线程安全问题:是指共享资源有线程并发的数据隐患,加同步锁,锁方法,也可以锁代码块
public class Test7_Single2 {
public static void main(String[] args) {
MySingleton my = MySingleton.getMy();
MySingleton my2 = MySingleton.getMy();
System.out.println(my==my2);
}
}
// 创建类
class MySingleton {
// 1,私有化构造方法 -- 控制外界new的权利
private MySingleton() {
}
// 2,在类的内部创建好对象 -- 延迟加载!!
static private MySingleton my;
// static Object obj = new Object();
// 3,提供全局访问点
// 问题:程序中有共享资源my,并且有多条语句(3条)操作了共享资源,此时,my共享资源一定会存在多线程编程的数据安全隐患
// 解决方案就是加 同步锁 。
//如果用同步代码块需要确定锁的位置? 锁的对象?由于方法中的所有代码都被同步了,可以直接变成同步方法。
synchronized static public MySingleton getMy() {
// synchronized (obj) {//同步代码块:静态区域内,不能是用this关键字?因为加载的先后顺序问题
if (my == null) {// 说明没new过,保存的是默认值null
my = new MySingleton();// 需要时才会创建对象,不需要也不会提前就开始占用内存。
}
return my;
// }
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。