线程
多线程原理:
程序启动时,JVM启动一个进程;
调用main()入栈时,主线程main被创建;
随着main()中语句执行,在线程对象创建后,调用其start方法时,启动一个新的线程;
此时程序就在多线程下运行;
当执行线程的任务结束,线程自动在栈内存中释放;
当所有执行线程都结束,进程就结束了。
创建多线程程序的三种方式:(还可以实现Callable接口/使用Lambda表达式创建)
方法1.继承Thread类(代码方便,但不推荐)
创建Thread接口的子类MyThread,重写run方法->
创建MyThread的对象->
调用MyThread类对象的start方法
方法2.实现Runnable接口(推荐使用)
创建Runnable接口的实现类,重写run方法->
创建Runnable实现类的对象->
创建Thread类对象,在其构造方法中传递实现类对象->
调用Thread类对象的start方法
方法2创建多线程的好处:
1.避免了单继承的局限性
一个类只能继承一个类,而实现Runnable接口后还能继承其他类,实现其他接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
方法2把设置线程任务和开启新线程进行了分离(解耦)
实现类中重写run方法:用来设置线程任务
创建Thread类对象:用来开启新线程
方法3.匿名内部类(简化代码)例:
//方法1+3:
new Thread() {
@Override
public void run() {...
}
}.start();
//方法2+3:
new Thread(new Runnable() {
@Override
public void run() {...
}
}).start();
获取线程名称:
1.使用Thread类中方法getName();
String getName(); //返回线程名称
2.先获取当前正在执行的线程,使用getName()获取线程名称
static Thread currentThread(); //返回当前正在执行的线程对象的引用
Thread.currentThread().getName() //在main方法中写此语句:获取主线程名称
设置线程名称:
1.使用Thread类中的方法setName
void setName(String name)
2.创建带参数构造方法(参数传递线程名称);调用父类的带参构造方法,让父类给子线程起一个名字
Thread(String name)
public static void sleep(long millis);使当前正在执行的线程以指定毫秒数暂停
Thread.sleep(1000); //可以使用try catch和声明异常throws两种方式
解决线程安全问题的方法
1.同步代码块
synchronized(锁对象){
可能出现线程安全问题的代码(访问了共享数据的代码)
}
锁对象可以是任意对象(this也可以,但最好不要用)
2.使用同步方法
把访问了共享数据的代码抽取出来,放在一个方法中
在方法上添加synchronized修饰符
(同步方法的锁对象就是实现类对象,即this)
注:静态同步方法
静态方法优先于对象,其锁对象不能是this
锁对象:本类的class属性-->class文件对象(反射)
3.Lock锁
在成员位置创建ReentrantLock对象
在可能会出现安全问题的代码前调用Lock接口中的lock方法获取锁
在可能会出现安全问题的代码后调用unlock方法释放锁
线程6种状态:
NEW(新建状态、初始化状态)
RUNNABLE(可运行状态、就绪状态)
BLOCKED(阻塞状态、被中断运行)
WAITING(等待状态)
TIMED_WAITING(定时等待状态)
TERMINATED(死亡状态、终止状态)
Object类中的方法:
void wait()
在其他线程调用此对象的notify()或notifyAll()方法前,使线程进入等待状态
void notify()
唤醒在此对象监视器上等待的单个线程 (优先唤醒等待时间最长的)
会继续执行wait方法后的代码
notifyAll:唤醒此对象监视器上等待的所有线程
使用注意:
wait和notify方法必须放在同步代码块或同步方法中,且两者由同一个锁对象调用。
wait和notify方法属于Object类,任何对象都可使用。
sleep()方法和wait()方法的区别:
1.所属类
sleep()是Thread类的(静态)方法,wait()是自Object类的方法
2.(关键☆)释放锁
sleep()方法没有释放锁,wait()方法释放了锁:
使得其他等待此 同步资源(锁) 的线程可以使用同步控制块或方法
3.使用限制
wait,notify和notifyAll必须在synchronized块中使用,而sleep可以在任何地方使用
4.使用场景
sleep一般用于线程休眠、循环控制,wait多用于多线程之间通信
线程池:
容纳多个线程的容器,其中线程可以反复使用,省去了频繁创建线程对象的操作:
降低资源消耗,提高响应速度,提高线程的可管理性
容器-->集合 ( LinkedList<Thread> )
当程序第一次启动时,创建多个线程,保存在一个集合中;
当想要使用线程时,就从集合中取出线程使用;
使用完毕线程后,需要把线程归还给线程池
在JDK1.5之后,JDK内置了线程池,可以直接使用
线程池的使用步骤:
1.使用线程池工厂类Executors中的静态方法newFixedThreadPool生产一个指定数量的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
2.创建一个类,实现Runnable接口,设置线程任务
public class RunnableImpl implements Runnable {
@Override
public void run() {...}
}
3.调用ExecutorService中的submit()方法,传递线程任务(实现类),开启线程
es.submit(new RunnableImpl());
4.调用ExecutorService中的shotdown()方法,销毁线程池(不建议执行)