一、多线程的介绍
1、现在的操作系统基本都多用户,多任务的操作系统。每个任务就是一个进程,而在这个进程中就会有线程。真正完成程序运行和功能的
实现靠的是进程中的线程。
2、多线程:在一个进程中,我们同时开启多个线程,让多个线程同时去完成某个任务或功能。
3、多线程的目的:充分利用CPU资源,提高程序的运行效率。
4、进程和线程的区别:
1、进程在执行过程中拥有独立的内存单元(CPU、内存等),而多个线程共享内存,从而极大地提高了程序的运行效率;
2、线程必定也只能属于某一个进程,而进程可以拥有多个线程而且至少拥有一个线程;
5、多线程原理:
多线程并发执行,只是视觉的假象,多线程并发只是CPU在线程中做时间片的切换;
CPU负责程序的运行,而CPU在运行程序中的某个时刻点上,它其实只能运行一个程序,而不是多个程序,且CPU它可以在多个线程之
间进行高速的切换,由于切换速度很快,导致出现了视觉上的假象。
每个程序就是一个进程,每个进程有多个线程,而CPU在这多个线程之间进行高速切换,形成多线程的假象。
6、创建线程的几种方式:
1、继承Thread类;
2、实现Runnable接口;
public class MyThread extends Thread {
public MyThread(String threadName) {
//自定义线程的名字
super.setName(threadName);
}
@Override
public void run() {
//Thread.currentThread().getName()获取当前线程的名字
System.out.println(Thread.currentThread().getName()+"work");
}
public static void main(String[] args) {
new MyThread("thread1").start();
new MyThread("thread2").start();
}
}
注意:1、在main方法中调用start()是启动一个线程,然后,重写run()方法执行;
2、不能因为线程1代码在上,线程2代码在下,就判定先打印线程1后打印线程2。这两个线程是并发执行的,看谁先抢到cpu资源,
先抢到的就先执行。
二、多线程同步之synchronized
1、线程同步:在引入多线程后,由于线程执行的并发性,会给系统造成混乱,特别是在急用临界资源的时,可能会导致数据处理出错,
因此,线程同步的主要任务是使并发执行的各个线程之间能够有效的共享资源和相互协作。
通过同步对临界区的访问可以避免其他各线程的干扰,某些动作操作对象之前,必须要先获得该对象的锁,获取该操作对象上
的锁可以阻止其他对象获取这个锁,直到这个锁的持有者释放它为止,这样,多线程就不会同时执行那些会互相干扰的动作。
2、
1、锁加在对象上 :给实例对象加锁
2、锁加在实例方法上:相当于对当前实例加锁
1:synchronized如果加载非静态方法上,就等于加在实例对象上
2:要实现线程同步,必须保证使用同一把锁
3、锁加在静态方法上:相当于对当前类加锁
3、死锁和活锁:
三、多线程同步之lock
一般来说synchronized必须等当前线程执行完后才自动释放锁,如果获得锁的对象因其他原因(如:调用了sleep()方法)被阻塞,没有
释放锁,而其它线程只能等待,这样就影响了线程的效率,因此,就需要有一种机制可以不让线程一直等待下去(如:等待一段时间或者
响应中断),通过lock可以办到。它是一个接口
如果采用lock(),就必须主动去释放锁,并且在发生异常时,不会自动释放锁,因此,使用lock时,必须在try{}catch{}块中进行,并且
将释放锁的操作放在finally{}块中进行,以保证锁一定被释放,防止死锁的发生。
unLock()方法是用来释放锁的。
trylock(),它是有返回值的,它表示用来尝试获得锁,如果获得成功,则返回true,如果获得失败,则返回false(即锁被其它对象获得),
也就是说这个方法无论如何会立即返回,即便拿不到锁也不会一直等待下去。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间
期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的
等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,
那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,
只有进行等待的情况下,是可以响应中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直
等待下去。
说明:这种方式拿不到锁也不用等待了可以中断,而synchronized拿不到锁会一致等待,线程无法结束。
Lock和synchronized的区别
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
四、多线程通信
多线程间通讯就是多个线程在操作同一资源,但是操作的动作不同.
(1)为什么要通信
多线程并发执行的时候, 如果需要指定线程等待或者唤醒指定线程, 那么就需要通信.比如生产者消费者的问题,
生产一个消费一个,生产的时候需要负责消费的进程等待,生产一个后完成后需要唤醒负责消费的线程,
同时让自己处于等待,消费的时候负责消费的线程被唤醒,消费完生产的产品后又将等待的生产线程唤醒,
然后使自己线程处于等待。这样来回通信,以达到生产一个消费一个的目的。
(2)怎么通信
在同步代码块中, 使用锁对象的wait()方法可以让当前线程等待, 直到有其他线程唤醒为止.
使用锁对象的notify()方法可以唤醒一个等待的线程,或者notifyAll唤醒所有等待的线程.
多线程间通信用sleep很难实现,睡眠时间很难把握。
五、Java中活锁和死锁有什么区别?
活锁和死锁类似,不同之处在于处于活锁的线程或进程的状态是不断改变的,活锁可以认为是一种特殊的饥饿。
简单的说就是,活锁和死锁的主要区别是前者进程的状态可以改变但是却不能继续执行。
六、什么是乐观锁和悲观锁
(1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,
因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,
那么就应该有相应的重试逻辑。
(2)悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,
因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
七、线程的几种状态:******************
新建:new一个Thread对象或者其子类对象就是创建一个线程,当一个线程对象被创建,但是没有开启,这个时候,
只是对象线程对象开辟了内存空间和初始化数据。
就绪:新建的对象调用start方法,就开启了线程,线程就到了就绪状态。
在这个状态的线程对象,具有执行资格,没有执行权。
运行:当线程对象获取到了CPU的资源。
在这个状态的线程对象,既有执行资格,也有执行权。
冻结:运行过程中的线程由于某些原因(比如wait,sleep),释放了执行资格和执行权。
当然,他们可以回到运行状态。只不过,不是直接回到。
而是先回到就绪状态。
死亡:当线程对象调用的run方法结束,或者直接调用stop方法,就让线程对象死亡,在内存中变成了垃圾。