一、程序、进程、线程
1、程序:为完成特定任务,用某种语言编写的一组指令的集合,是一段静态的代码
2、进程:是程序的一次执行,正在运行的程序,cpu分配一个进程。进程是资源分配的单位
在内存中会为每个进程分配不同的内存区域。
进程具有生命周期,自身的产生,存在和消除的过程
3、线程:是一个程序内部的一条执行路径
若一个进程可以同一时间执行多个线程,就是支持多线程的
线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。
二、并行和并发
1、并行:多个cpu同时执行多个任务
2、并发:一个cpu"同时”执行多个任务(采取时间片切换的方式)
三、创建线程的方式(四种)
方式1、实现java.Thread类,并重写run()方法,在方法内写实现;
使用:在main()方法内创建线程的对象,然后开启线程,thread.start(),然后会自动调用run方法
直接调用thread.run方法,只会当做是普通的方法调用
方式2、实现 Runable接口,并重写run()方法,在方法内写实现;
使用:A、创建接口实现类的对象,
B、将实现类的对象与Thread类绑定:(这些线程共享同一个实现类类的数据)
Thread thread1=new Thread(runableThread)
Thread thread2=new Thread(runableThread)
Thread thread3=new Thread(runableThread)
//Thread thread=new Thread(runableThread,"线程名")
开启多线程 thread1.start()
thread2.start()
thread3.start()
方式1与2对比,使用方式2较多。
原因:java是单继承,继续Thread类之后就不能再继承别的类。
方式2的共享能力强一些,不需要一定得static
方式1、2的缺点:不能返回值,run为void;不能抛出异常
方式3:使用callable和Future接口创建线程,重写call()方法
好处:有返回值和能抛出异常
接口有泛型callable<T>,如果不写,返回类型为Object;写了,就是对应的类型
步骤:A、写实现类和call的逻辑实现
B、开启线程
创建实现类对象
实现类对象绑定到FutureTask类的对象
Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口
将FutuerTaske类的对象绑定到Thead类
然后Thread类的对象开启线程
C、获取线程的方法返回值
FutureTask类的对象.get();
方式四、使用线程池(ThreadPoolExcutor、excetorService)
前三种的线程如果创建关闭频繁会消耗系统资源影响性能,而使用线程池可以不用线程的时候放回线程池,用的时候再从线程池取,项目开发中主要使用线程池。
4、线程的生命周期
新生->就绪->运行->消亡
阻塞
四、设置和获取线程的名字
1、start():开启线程,线程获取cpu资源后调用run方法
2、run():线程类都要实现这个方法,run方法的内容就是线程要实现的内容
3、currentThread():是Thread类的一个静态方法。
Thread.currentThread() 获取当前线程
4、设置和获取线程的名字
Thread.setName()
Thread.getName()
//通过带参构造器调用父类的构造器,设置线程名
public 类型(String Name){super(name)}
5、setPriority() 设置线程的优先级 ,优先级:1-10,默认是5
优先级相同,先到先服务;优先级高,被调用的概率就高
getPriority 获取线程的优先级
6、join()
当一个线程调用了join方法,这个线程就会先别执行,它执行结束以后才可以执行其余的线程
注意:必须先start,在join
7、sleep() 让线程阻塞
8、setDemo() 将进程设置为伴随进程(守护进程),主进程停止的时候,子进程也会停止执行。(必须设置在start方法之前)
例子:在对应的线程内开启伴随进程
比如在main线程内开启
main{
tt.setDemo(true) // 注意:先将进程设置为伴随进程,再启动
tt.start()}
9、stop() 让进程死亡,不推荐使用
五、线程安全
1、原因:多个线程在争抢资源的过程中,导致共享资源出现问题。一个线程还没执行完,另一个线程就参与进来,开始争抢。(此时,就要进行加锁,并且这个锁必须是共享的,必须唯一)
2、解决
A、同步代码块:synchronized()
a、 把具有安全隐患的代码锁住,如果锁多了就会效率低
公共资源放在synchronized(){} 里
b、认识Synchronized (同步监视器)
1)同步监视器的参数必须是引用数据类型,不能使用简单类型
2)同步监视器没有什么业务含义,只是一个锁的名字,一般使用共享资源即可
3)尽量不适用String 和包装类做同步监视器
4)尽量使用final修饰同步监视器
c、同步代码块的执行流程
线程一找到同步代码块,发现同步代码块的标志位open,则进入执行,然后把标志改为close。如果线程一还没执行完同步代码块就被cpu切换出去,此时进来线程二,线程二发现同步代码块是close,线程就会进入阻塞状态。当线程一再次被cpu调用并执行完同步代码块时,就会将close变为open。当线程二发现同步代码块标志为open,则会进入就绪态,等待被cpu调用执行。
d、多个代码块被同一个同步监视器锁住时,当其中一个代码块被调用执行,这个代码块就会被锁住,其它被这个锁绑定的代码块也被锁住。
B、同步方法
public synchronized add(){} 锁住的是调用这个方法的对象
static public synchronized add(){}
锁住的是调用这个方法的对象所在类的全部对象 即类名.class 字节码信息对象
C、同步代码块和同步方法的对比
非静态同步方法的锁是this,锁住一个方法,类内的其它同步方法就会被锁住
同步代码块只会锁住同步监视器的代码块,其它不会被锁住
D、Lock锁
a、JDK1.5以后,新增的线程同步方式
synchronized是Java中的关键字,这个关键字的失败是靠JVM来识别完成的,是虚拟机级别
Lock锁是API级别,提供了相应的接口和对应的实现类(有多个实现类),这个方法更灵活,表现的性能优于synchronized
实现类 ReentrantLock (可重入锁)、ReadWriteLock(读写锁)
读写操作:实现读与读不互斥,读与写和写与写互斥
synchronized同步代码块或同步方法,被对象调用后 相同同步监视器就会锁上,实现了全部排斥,不能实现读与读不互斥
collable接口的ReadWriteLock就可以实现这个操作
condition
b、代码示例
Lock lock=new ReetrantLock(); //实现类多,根据需要选择
lock.lock() //使用前打开锁
//捕捉异常,防止抛出异常而没关闭锁
try{
}catch(){
}finally{
lock.unlock(); //关闭锁}
c、lock和synchronized的区别
1)lock是显示锁,需要手动开启和关闭,没关闭可能造成死锁现象
synchronized是隐式锁,同步代码块或同步方法执行完毕后,系统释放对锁的占有
2)lock是代码锁,synchronized是方法锁和代码锁
3)使用lock锁,jvm将花费较少的时间来调度线程,性能更好,并且有更好的扩展性(提供更多的子类)
4) synchronized它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。synchronized是内置的语言实现;synchronized在发生异常时,会自动释放线程占有的锁,,因此不会导致死锁现象发生。Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
d、使用优先顺序
lock>synchronized代码块>synchronized同步方法
e、线程同步的缺点
a、线程安全,效率低
b、可能造成死锁。出现死锁后,不会出现报错,不会出现异常,只是所有线程处于阻塞状态,无法继续。
六、线程状态切换
1、线程睡眠 线程类的对象.sleep(1000) /休眠一秒
2、线程唤醒
noyify() 唤醒当前对象阻塞队列的任意线程
notifyAll() 唤醒当前对象阻塞队列的所有线程
3、interrupte() 将调用该线程的中断状态设为true
isInterrupted() 判断该线程的中断状态
4、线程阻塞 wait()
5、线程合并是优先执行调用该方法的线程,执行完执行其他线程
join()
6、线程让步 yield() 可以让当前正在执行的线程暂停,将线程转入就绪队列
(只有优先级与当前执行线程相同且处于就绪状态才会获得执行机会)
七、Condition
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用
在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现,这里注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。
class BoundedBuffer {
final Lock lock = new ReentrantLock();//锁对象
final Condition notFull = lock.newCondition();//写线程条件
final Condition notEmpty = lock.newCondition();//读线程条件
final Object[] items = new Object[100];//缓存队列
int putptr/*写索引*/, takeptr/*读索引*/, count/*队列中存在的数据个数*/;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)//如果队列满了
notFull.await();//阻塞写线程
items[putptr] = x;//赋值
if (++putptr == items.length) putptr = 0;//如果写索引写到队列的最后一个位置了,那么置为0
++count;//个数++
notEmpty.signal();//唤醒读线程
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)//如果队列为空
notEmpty.await();//阻塞读线程
Object x = items[takeptr];//取值
if (++takeptr == items.length) takeptr = 0;//如果读索引读到队列的最后一个位置了,那么置为0
--count;//个数--
notFull.signal();//唤醒写线程
return x;
} finally {
lock.unlock();
}
}
}
线程同步:就是多个线程同时访问同一资源,必须等一个线程访问结束,才能访问其它资源,比较浪费时间,效率低
线程异步:访问资源时在空闲等待时可以同时访问其他资源
sleep()和wait()的区别
sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。