多线程基础知识
1、线程和进程的区分
一个进程中包含多个进程,一个进程相当于一个应用程序,一个应用程序底层就是CPU来运行的,比如我们的电脑同时打开了多个应用,表面看起来在同时运行,实际上同一时间只运行了一个应用程序,只不过CPU的运行时间非常快,会进行高速切换,让人觉得是在同时进行,最典型的的例子就是迅雷,我们使用迅雷下载东西的时候,比如说,下载两部电影,那么在迅雷中就存在两个不同的执行路径,也就是有两个线程在同时做下载工作。所以进程包含线程,相当于所有线程的集合。一个线程就是一个执行路径。
2、为什么要用多线程?
多线程的好处就是提高程序效率。但是可能会影响性能。
3、多线程的创建方式
常规来说创建多线程的方式应该有三种:继承Thread类、实现Runnable接口、匿名内部类
实际的逻辑处理是在run方法中实现的,实现时则调用start方法,将线程交给线程规划器管理。
注意:
实际情况中实现runnable接口可能更多一点,因为如果继承了Thread类的方式的话就不能继承其他类,这在实际的开发中势必会带来一些局限性
得到当前线程名称:Thread.currentThread().getName()
判断当前线程是否处于活跃状态:Thread.currentThread().isAlive()
当前线程中断标志:this.isInterrupted()
使线程暂时休眠:Thread.sleep();
4、线程的停止
线程的生命周期就是新建,就绪,运行,阻塞,消亡
新建:没有调用start方法
就绪:调用了start方法,但jvm没有调用run方法
运行:调用了run方法
阻塞:在运行中调用了sleep方法
消亡:线程停止
线程停止的方法:Thread.interrupt(),为线程打上停止标记,并且在判断到中断标志后抛出一个中断异常,然后再捕获异常,就可以避免for循环之后继续被执行的情况。
5、线程安全问题
涉及到多个线程同时访问一个共享变量的情况就会出现线程安全问题,因为线程的执行顺序并不是代码的书写问题。
解决方法:在set方法中用synchronized来修饰,当线程调用时会把set方法锁住,执行完后才会被其他线程调用
上面的方法在操作耗时的情况下会降低性能效率,解决思路就是只对引起线程安全的代码进行synchronized同步
int age;
Object a=new Object;
public void setAge(int b){
synchronized(o){
this.age=b;
}
}
此时为每个调用线程调用方法都创建一个新的对象。
6、volatile关键字
作用:使变量在多个线程之间可见。
子线程读取到的是在子线程中的本地内存中的共享变量副本,虽然主线程在isRun方法中将共享变量改变,但子线程中读取到的依然存放在本地内存中的未改变的副本
解决办法:在主线程中的共享变量前加volatitle修饰,这样再次执行这个程序就会发现线程立马结束了,这是因为变量一旦加上volatile关键字,就强制要求每次使用共享变量都必须从主内存中取值。
原因:线程之间是不可见的,读取的是副本,没有及时读取到主内存结果。
7、线程之间的通信wait和notify和join
线程不是一个个独立的个体,是可以通信协作的,所以就需要等待和通知,使用的前提是实现了同步锁
String s=new String();
synchronized(s){
s.wait();
}
当子线程执行结束会返回一个值,而主线程会用到这个值,可能主线程执行完后子线程还在执行,如果子线程调用join方法就必须等子线程执行完成后才能干其他事
join(long) join(2000) thread.join();
join和sleep的本质区别是join会释放锁,而sleep不会。
8、lock的使用
在jdk1.5版本中出现一个ReentrantLock也能实现线程之间的同步,防止线程安全问题的产生,且比synchronized更强大
private Lock lock=new ReentranLock();
set方法{
lock.lock();
...
lock.unlock();
}
synchronized配合wait和notify实现等待、通知模式
public class Thread2{
public static void main(String[] args){
Object lock=new Object();
MyThread myThread=new MyThread(lock);
Thread thread=new Thread();
thread.start();
thread.setName("ds");
OtherThread otherThread=new OtherThread(lock);
otherThread.start();
otherThread.setName("df");
}
class MyThread implements Runnable{
private Object lock;
public MyThread(Objec lock){
this.lock=lock;
}
public void run(){
synchronized(lock){
try{
lock.wait();
}catch(){}
}
}
}
}
class OtherThread extends Thread{
private Object lock;
public OtherThread(Object lock){
this.lock=lock;
}
public void run(){
synchronized(lock){
lock.nitify();
}
}
}
使用ReentrantLock如何实现等待、通知模式,除了使用相同的锁,还需要一个相同的Condition对象
首先创建两个线程一个等待一个通知
class MyThread1 extends Thread{
private Lock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
public MyThread1(Lock lock,Condition condition){
this.lock=lock;this.condition=condition;
}
public void run(){
try{
lock.lock();
condition.await();
}catch(){}finally{
lock.unlock();
}
}
}
class OtherThread1 eextends Thread{
private Lock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
public OtherThread1(Lock lock,Condition condition){
this.lock=lock;this.condition=condition;
}
public void run(){
lock.lock();
condition.signal();
lock.unlock();
}
}
然后为了保证是同一把锁和condition对象
private Lock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
public OtherThread1(Lock lock,Condition condition){
this.lock=lock;this.condition=condition;
}
注意:在等待通知模式中,同一把锁很重要,wait相当于condition中的await,notify相当于condition中的signal
10、定时器Timer
java中多线程定时器的作用就是可以让一段代码持续性运行或者在规定时间后运行
new Timer().schedule(new TimerTask(){
public void run(){
sout("延迟1秒")
}
},1000)
放在主线程中运行
,1000,1000);代表一秒后执行run中的代码,然后每隔一秒重复执行一次
Timer类的主要作用就是设置计划任务,但封装任务的类却是TimerTask类,Timer实际是一个工具类,然后调用schedule来执行相关的定时任务