java--多线程基础
进程:正在运行的程序,是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源
多进程:在同一个时间段内执行多个任务
多线程的意义:多线程的存在,不是提高程序的执行速度,其实是为了提高应用程序的使用率(一个进程中更多的线程更容易抢到CPU资源,CPU执行权),就会使进程有更高的几率抢到CPU的执行权
注意:不能保证哪一个线程能够在哪一个时刻抢到,所以线程的执行有随机性
并行和并发的区别:
并行:逻辑上同时发生,指在某一个时间内同时发生
并发:物理上同时发生,指在某一个时间点运行多个程序
java程序运行原理:java命令会启动java虚拟机,启动JVM,等于启动一个应用程序,也就是启动了一个进行。该进程会自动启动一个“主线程”,然后主线程去调用某个类的main方法。所以main方法允许在主线程中。在此之前的程序都是运行在单线程中的。
问题:JVM虚拟机的启动是单线程还是多线程?
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的
如何实现多线程的程序?
由于线程是依赖于进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该调用系统功能创建一个进程。
java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。
但是java可以去调用C/C++写好的程序来实现多线程程序。即利用C/C++去调用系统功能创建进程,java对于这些C/C++的进程程序进行了封装,包装成一些类,这样就可以直接调用这些类去实现多线程程序。
java提供了Thread类中的run()用来包含哪些被线程执行的代码
主要内容:线程概述、多线程实现方案、线程调度和线程控制、线程生命周期、线程同步、死锁、线程间的通信、定时器的使用
多线程实现方案:基本是两种方式,还有一种作为扩展方式(实现Callable接口,依赖于线程池)
线程调度和控制:调度:设置线程的优先级 控制:线程的运行状态控制(具体涵盖:start/sleep/yield/join/...)
线程生命周期:创建、就绪、运行、阻塞、死亡(状态)
线程同步:防止多线程带来的安全问题
死锁:因多个不同种类的线程对同一个资源的抢占而产生的循环等待现象
线程间通信:java中的等待唤醒机制
1 实现多线程的方式:
方式一:继承Thread类
A:自定义类MyThread继承Thread类
B:在MyThread类中重写run( )方法
C:创建MyThread对象
D:启动线程对象
问题:
1、为什么要重写run( )方法?
run()方法里封装的是被线程执行的代码
2、启动线程对象的方法?
start()
3、run()和start()方法的区别?
run()直接调用仅仅是普通方法
start():先启动线程,再由JVM调用run( )方法
方式二:
A:自定义类MyRunnable实现Runnable接口
B:在MyRunnable里面重写run()方法
C:创建MyRunnable对象
D:创建Thread类的对象,并把C步骤的对象作为构造参数传递
问题:
1、方式1 方式2为什么有两种实现机制?
方式二解决了单继承的局限性
适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码、数据有效分离,较好体现面向对象的设计思想
线程的状态转换:
线程的生命周期图:
2 线程的安全问题:
判断一个 程序是否有线程安全的标准?
1)是否有多线程环境
2)是否有共享数据
3)是否对共享数据进行操作
java提供了同步机制:
格式:synchronized(对象){
需要同步的代码......
}
注意:同步问题可以解决的根本原因就在那个对象上,该对象如同锁的功能。
多个线程要求必须是同一把锁。
同步解决线程安全问题:
A:同步代码块:
synchronized(对象){
需要同步的代码......
}
B:同步方法
把同步加在方法上
这里的锁对象是this
C: 静态同步方法
把同步加在方法上
这里的锁对象是当前类的字节码文件对象(Class对象)
注意:当时在集合中提到,Vector是线程安全的类,但是即使在多线程条件下也不使用该类,这是因为什么呢?
主要是因为Collections这个集合工具类的存在:它有很多静态方法可以将一个其他不线程安全的集合例如ArrayList/LinkedList集合转成一个线程安全的集合:
例如:
ArrayList<String> list1 = new ArrayList<String>( ); //线程不安全的ArrayList集合
ArrayList<Stirng> list2 = Collections.synchronizedList(new ArrayList<String>( )); //线程安全的ArrayList集合
Lock锁:
虽然可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock接口:java.util.concurrent.locks.Lock
提供了两个方法:void lock( )和void unlock( )
其实现子类:
ReentrantLock
格式:
ReentrantLock lock = new ReentrantLock( );//创建ReentrantLock对象
lock.lock( ); //加锁
需要同步的代码;
lock.unlock( );//释放锁
通常考虑到同步的代码块出错的可能性,所以使用以下方式:
try{
lock.lock( );
需要同步的代码块...............
}finally{
lock.unlock( );
}
同步锁的弊端:
1)效率低:安全
2)如果出现同步嵌套,就容易产生死锁问题:相互等待
死锁问题及其代码:
是指两个或者两个以上的线程在执行过程中,因争夺资源产生的一种相互等待的现象
同步嵌套代码的:最少两个锁
同步嵌套代码的举例:
Class myThread extends Thread{
private boolean flag;
private static final Object objA = new Object( );
private static final Object objB = new Object( );
public myThread( boolean flag ){
this.flag = flag;
}
public void run( ){
if(flag){
synchronized(objA){
System.out.println("if ObjA");
synchronized(objB){
System.out.println("if ObjB");
}
}
}else{
synchronized(objB){
System.out.println("else ObjB");
synchronized(objA){
System.out.println("else ObjA");
}
}
}
}
}
在线程测试类中,创建两个线程,给与不同的flag值
可能就会出现死锁现象
线程之间的通信问题:不同种类的线程间针对同一个资源的操作
生产者和消费者:
不同种类的线程在操作时需要加锁
不同种类的线程加同一把锁
正常消费者和生产者的思路:
A:生产者:先看是否有数据,有就等待,没有就生产,生产完之后通知消费者来消费
B:消费者:先看有没有数据,有就消费,没有就等待,通知生产者生产数据
为了处理这样的问题,java就提供了一种机制:等待唤醒机制 保证一人一次
Object类中提供的方法:
1)wait( ):等待,该方法的特点是:一旦等待就释放锁,并且一旦被唤醒,就是在哪里开始等待的就从哪里开始继续
2)notify( ):唤醒单个线程。 被唤醒并不代表你可以立刻执行,还得去抢CPU执行权
3)notifyAll( ):唤醒相关的所有线程
问题:
为什么这些方法不定义在Thread类中呢??
因为这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象,所以这些方法必须定义在Object中。
线程组:ThreadGroup,java.lang.ThreadGroup
线程默认情况下都属于main线程组
如何修改线程所在的组???
1)创建一个线程组
2)创建线程的时候,把其他线程的组指定为新建的线程租
ThreadGroup tg = new ThreadGroup("producerThreadGroup");//创建一个线程组
Thread t1 = new Thread(tg,runnable,"thread1"); //创建一个线程对象1,其所属的线程组是新创建的
Thread t2 = new Thread(tg,runnable,"thread2"); //创建一个线程对象2,其所属的线程组是新创建的
//现在改线程组就包含了两个线程
但是线程组出现的意义在哪里呢???
可以实现统一管理整个线程组中所有的线程,
例如通过设置该组的线程优先级:tg.setMaxPriority( );可以使组内的所有线程都是最高优先级
线程池:
程序启动一个新线程成本是比较高的,因为它涉及到与操作系统进行交互,而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存周期很短的线程时,更应该考虑使用线程池
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲 状态。等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,java内置支持线程池
位于:java.util.concurrent.Executors
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:
- public static ExecutorService newCachedThreadPool( ) //并没有指定创建几个线程
- public static ExecutorService newFixedThreadPool( int nThreads ) //可以指定在线程池放几个线程
- public static ExecutorService newSingleThreadExecutor( ) //该方法就是nThreads=1的情况下
这些方法的返回值都是ExecutorService接口(实现的父接口:Executor)对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下的方法:
- Future<?> submit( Runnable task )
- <T> Future<T> submit( Callable<T> task)
例如:
1)创建线程池对象:控制要创建几个线程对象
2)创建Runnable实例(创建一个实现Runnable接口或者Callable接口的类)
3)提交Runnable实例
4)关闭线程池(如果在使用线程后想关掉线程池,使用shutdown( )方法)
例如:
1)创建一个实现Runnable接口的类
public class MyRunnable implements Runnable {
@Override
public void run() {
for( int i=0; i<50; i++){
System. out.println(Thread. currentThread().getName()+":"+i);
}
}
}
2)创建测试类
public class ThreadPoolDemo {
public static void main(String[] args) {
//newFixedThreadPool()返回的是ThreadPoolExecutor:ExecutorService的子类
ExecutorService pool = Executors.newFixedThreadPool(3);//在线程池中创建三个线程
pool.submit( new MyRunnable()); //启动线程
pool.submit( new MyRunnable()); //启动线程
pool.shutdown(); //启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
}
}
在之前提到创建线程的方式:
1)继承Thread类
2)实现Runnable接口
而在线程池中有一种实现Callable接口创建线程的方式,但是这种方式只能依赖线程池,该接口有个特点,线程运行后有返回值。
而其他两种方式中的run( )方法的返回值是void。这也是该种方法的特点所在。
3)实现Callable接口
使用的好处:可以有返回值,可以抛出异常(而在run方法中的异常只能try----catch处理)
弊端:代码复杂,一般不用
匿名内部类实现多线程:
第一种方式:
new Thread(){
@Override
public void run() {
for( int i=1; i<50; i++){
System. out.println(Thread. currentThread().getName()+"----"+i);
}
}
}.start();
第二种方式
new Thread(new Runnable(){
@Override
public void run() {
for( int i=1; i<50; i++){
System. out.println( "java"+ "----"+ i);
}
}
}).start();
第三种方式:开发中不会出现,但是在一般面试时会遇到:
1)该方式会报错吗? 不会
2)线程会执行哪段代码? 会执行 new Thread中{ }中的代码,即下面用红色标注的部分有效
new Thread(new Runnable(){
@Override
public void run() {
for( int i=1; i<50; i++){
System. out.println(Thread. currentThread().getName()+"----"+i);
}
}
}){
@Override
public void run() {
for( int i=1; i<50; i++){
System. out.println( "hello"+ "----"+ i);
}
}
}.start();
定时器:
在java中可以通过Timer和TimerTask类来实现定义调度的功能
Timer: java.util.Timer(具体类)
- public Timer( )
- public void schedule( TimerTask task, long delay)
- public void schedule( TimerTask task, long delay,long period)
TimerTask:java.util.TimerTask(抽象类),实现了Runnable接口
- public abstract void run( )
- public boolean cancel( )
开发中,Quariz是一个完全由java编写的开源调度框架
多线程相关的问题:
1、多线程有几种实现方案,分别有哪几种?
实现方式有两种:
1)继承Thread类:
2)实现Runnable接口
2、同步有几种方式,分别是什么?
有两种方式。
1)同步块的方式 (锁的是任意对象)
2)同步方法的方式 (锁的是当前类对象)
3、启动一个线程是run( )还是start( )?他们的区别是?
启动线程用start( )。
run方法封装了线程执行的代码,直接调用仅仅是普通方法的调用
start方法启动线程,并由JVM自动调用run方法
4、sleep( )和wait( )方法的区别?(sleep是在Thread类中定义的,而wait方法定义在Object类)
首先:sleep方法必须指定时间,而wait可以指定时间也可以不指定时间
其二:sleep方法在线程休眠期间不释放资源(锁),而wait方法在进入等待状态时释放自己持有的锁。
5、为什么wait( ) notify( ) notifyAll( )等方法都定义在Object类中?
因为wait notify notifyAll等方法的调用依赖于锁对象,而同步代码块的锁对象是任意锁。
由因为Object对象代表是任意一个对象,所以这些方法的定义必须在Object类中
银联笔试看到这样一道题:
class NewThread extends Thread implements Runnable(){
public void run(){
System.out.println("run()");
}
}
class Demo{
public static void main(String[] args){
Thread t1 = new Thread(new NewThread());
t1.start();
}
}
试问这道题会正常运行吗?如果不能运行,会在第几行报错?
经过测试这是可以的,NewThread 既继承了Thread又实现了Runnable接口,new NewThread()就可以看成一个Thread或者一个Runable接口的实现类,在测试类中
Thread t1 = new Thread(new NewThread());Thread构造函数的参数是一个实现Runnable接口的实现类,而NewThread类正好实现类Runnable接口,故可以当做参数传入。