首先得搞清,进程与线程的区别:
进程:正在运行中的程序
线程:进程中一个执行单元(或执行情景或执行路径)负责进程中程序执行的控制单元。
【那什么又是多线程呢?】
一个进程中至少要有一个线程。当一个进程中线程有多个时,就是多线程。多线程是为了同步完成多项任务。线程是在同一时间需要完成多项任务的时候实现的。
【多线程解决的问题】——可以让多部分代码同时执行。
在这儿曾经碰到过一个疑问:多线程可以同时执行很多任务,那么多线程可以提高程序的执行效率吗?要解决这个问题,首先必须得搞清“什么是同时执行”。【所谓的:同时执行的本质】——其实就是CPU在瞬间做着线程间的快速切换,完成多个任务。当我们开的线程越多时,CPU切换的速度越慢,一个线程在单位时间内被执行的次数就减少了!因此,多线程并不能提高程序的执行效率。
【面试题】——【如何理解Java程序的运行是多线程,如何理解JVM的多线程??】
【垃圾回收器只有一个吗?】
垃圾回收器是只有一个,但每个对象都有一个可以被垃圾回收器调用的方法,垃圾回收器在回收每一个对象时,都会自动的调用该对象的finalize的方法。
【为什么要启动多线程呢?】
因为有多部分代码需要同时执行,而且每一个线程都有自己要执行的内容,这个内容称之为:线程的任务。简言之:启动线程就是为了执行任务,当任务有多个需要同时执行时,就需要多个线程。
【如何自定义线程呢?】
Java需要调用底层才能完成进程的建立和线程的创建,那么,Java有对外提供描述线程的对象,方便与程序员对线程的操作。在Java的lang包中,去找描述线程的类,,Thread就是。
创建线程的两种方式:
方式一:继承Thread类,覆盖run方法;
步骤:
1. 定义类覆盖Thread类;
2. 覆盖Thread类中的run方法;
3. 创建Thread类的子类对象创建线程对象;
4. 调用线程对象的start( )方法,开启线程。
【创建线程为什么要覆盖Thread并覆盖run方法?】
线程要执行什么内容,线程自己有定义。这个定义就是run方法,这个方法中定义就是线程要运行的内容。但run()的内容并不是我们所需要的,只有覆盖Thread类中的run方法。覆盖之前需要先继承。
【注意】:
主线程要运行的任务都在main函数中,自定义的线程要运行的任务都在run 方法中。run 方法就是专门用于存储线程任务代码的地方。
【如何让线程跑起来?】 调用start( )方法,start( )方法做了两件事:1. 开启线程让线程可以运行;2. 调用run( )方法;
【调用run方法和调用start方法的区别】
1. 调用run ( )方法,仅仅是一般对象在调用对象中的方法,并没有开启线程,还是由主线程来完成run( )方法的运行。
2. 调用start( )方法,开启了一个线程(一个新的执行路径)这个线程去执行了run方法。
【线程的第二种创建方式】
1. 定义一个类实现Runnable接口;
2. 覆盖Runnable接口中的run方法。
将线程要运行的代码存储到run()方法中。
3. 创建该接口的子类对象。
4. 通过Thread 类进行线程的创建,并将Runnable接口的子类作为Thread类的构造函数的实参进行传递。
5. 调用Thread类中的start()开启线程。
【为什么要进行子类作为参数的传递呢?】
让线程对象创建时,就要明确要运行哪个run方法,而这个run方法是需要被对象调用的,所以将run方法所属的对象传递给Thread类的构造函数。
【Runnable接口设计的思想——超级重点,面试题】
1. 避免单继承的局限性。
2. Runnable 接口的由来 就是将线程的任务进行对象的封装。将线程任务封装成对象后,通过Runnable接口可以降低和Thread对象的耦合性。
【多线程的安全问题】
安全问题产生的原因:
1. 多个线程在操作共享数据;
2. 操作共享数据的代码有多条;一个线程在操作共享数据的过程中,其他线程参与了运算,这时就会发生安全问题。
【分析是否会发生安全问题】【依据】
线程任务中有没有共享数据,该数据是否被多条语句操纵。
【解决方案:】
只要保证一个线程在执行多条操作共享数据的语句时,其他线程不参与运算即可。当该线程都执行完成后,其他线程才可以执行这些语句。
【代码表现】
Synchronized(对象) // 对象可以是任意的对象
{
//需要被同步的语句;
}
【Synchronized同步的原理:】将需要同步的代码进行封装,并在该代码上加上一个锁。
【同步的好处】
解决了多线程的安全问题;
【同步的弊端】
会降低性能,损耗性能。
【同步的前提】
必须要保证在同步中有多个线程。因为同步中只有一个线程该同步就毫无意义。必须保证多个线程在同步中使用的是同一个锁。
必须保证多个线程使用的是同一个锁,这是才称为多个线程被同步了!
【同步函数】——同步的另一种表现形式。直接用synchronized 修饰函数即可。
【同步函数用什么锁?】
函数肯定被对象调用。代表调用函数对象的引用就是this。也就是说:同步函数使用锁是this。
【同步函数 和 同步代码块有什么区别呢?】
1. 同步函数比同步代码块写法简单。
2. 同步函数使用的锁是this.同步代码块使用的锁是任意指定的对象。
建议开发时,使用同步代码块儿。尤其是需要不同锁时。
【静态同步函数使用的锁是什么?】
静态进内存时,对象还没有创建,不能使用对象作为锁。静态随着类的加载而加载,这是内存中存储的对象至少有一个就是该类字节码文件对象。这个对象的表示方式:——类名.class 它就表示字节码文件对象。
【同步的另一个弊端:】——死锁。【两根 筷子,吃饭!】
最常见的死锁情况:同步嵌套(最常见的情景之一)。同步中还有同步,两个同步用的不是一个锁。
一个死锁程序——【代码体现】
class Test implements Runnable{
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
public void run(){
if (flag) {
while (true) {
synchronized (MyLock.locka) {
System.out.println("if …………locka………………" );
synchronized (MyLock.lockb) {
System.out.println("if………………lockb………………");
}
}
}
} else {
while (true) {
synchronized (MyLock.lockb) {
System.out.println("else………………lockb");
synchronized (MyLock.locka) {
System.out.println("else…………locka");
}
}
}
}
}
}
class MyLock{
public static Object locka = new Object();
public static Object lockb = new Object();
}
public class DeadLockTest {
public static void main(String[] args) {
Test a = new Test(true);
Test b = new Test(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}
【等待唤醒机制】
涉及到的方法:
wait();——等待,将正在执行的线程释放其执行资格和执行权,并存储到线程池中。
线程池:存储被wait()的线程的容器。
notify();——唤醒。唤醒线程池中被wait的线程,一次唤醒一个,而且是任意的。
notifyAll();——唤醒全部,可以将线程池中的所有wait线程对唤醒。
唤醒——让线程池中的线程具备执行资格。
【注意:】——这些方法都要使用在同步中才有效。 这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是那个锁上的线程。
【小疑惑】——【在查阅API时,发现,操作线程的方法都定义在Object类中,为什么这些操作线程的方法定义在Object类中?】
因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。
能被任意对象调用的方法一定定义在Object类中。
【等待唤醒机制的应用——生产者消费者】
—————————————————————————————————————————————————————————————————————在JDK1.5以后,出现了新的特性:Lock
同步函数和同步代码块 所做的都是隐式的锁操作。 同步函数和同步代码块 使用的锁和监视器是同一个。Lock接口的出现替代了 同步代码块,转隐式为显式,将锁单独封装,以前的锁是任意对象,现在的锁是指定的对象!Lock前 一定得加 final ,释放锁的动作一定得做】
Lock接口:是将锁进行单独对象的封装。而且提供了对锁对象很多功能。比如:lock()获取锁,unlock()释放锁。Lock对锁的操作都是显示操作。所以它的出现要比同步函数更或者同步代码块明确的多。更符合面向对象的思想。
在同步里,锁和监视器是同一个对象。升级后,锁是一个单独的对象而且将监视器的方法也单独封装到了一个对象中,这个对象就是升级后Condition.。升级后,都进行了单独的封装。锁被封装成了Lock对象。监视器方法都被封装到了Condition对象(监视器对象)中。——Lock代替了同步。
【Condition代替了Object中的监视器方法。】
【如何让锁和监视器产生联系?】直接通过Lock接口中的newCondition()方法就可以获取到能绑定到该Lock 对象上的监视器对象Condition上。
【生产者,消费者问题———具体应用】
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Resource6{
private String name;
private int count = 1;
private boolean flag;
//创建一个锁对象
private final Lock lock = new ReentrantLock();
//创建一个生产者的监视器
private Condition producer_con = lock.newCondition();
//创建一个消费者的监视器
private Condition consumer_con = lock.newCondition();
public void set(String name){
lock.lock();
try{
while(flag)
try {
producer_con.await();
} catch (InterruptedException e) {
// TODO: handle exception
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"…………生产者…………"+this.name);
flag = true;
consumer_con.signal();
}
finally{
lock.unlock(); // 异常处理的一种应用
}
}
public void get(){
lock.lock();
try{
while(!flag)
try {
consumer_con.await();
} catch (InterruptedException e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + "……………………消费者………………" + this.name);
flag = false;
producer_con.signal();
}
finally{
lock.unlock();
}
}
}
//生产者
class Producer0 implements Runnable{
private Resource6 r;
Producer0(Resource6 r){
this.r = r;
}
public void run(){
while(true){
r.set("馒头");
}
}
}
//消费者
class Consumer0 implements Runnable{
private Resource6 r;
Consumer0(Resource6 r){
this.r = r;
}
public void run(){
while(true){
r.get();
}
}
}
public class ProConDemo4 {
/**
* @param args
*
*/
public static void main(String[] args) {
Resource6 r = new Resource6();
Producer0 pro = new Producer0(r);
Consumer0 con = new Consumer0(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
【多线程面试题:】
wait() 和 sleep()方法的异同点:
两个方法都可以让线程处于冻结状态;sleep()必须指定时间,wait是可以指定时间,也可以不指定。
在同步代码块儿中:sleep():会释放执行权,不会释放锁;、wait():会释放执行权,会释放锁。
【一个有用的思想:切换——思想】
怎么切换:
int x = 0;
while(true)
{
if(x==0)
{
r.name = "Mike";
r.sex = "man";
}
else
{
r.name = "Mike";
r.sex = "man";
}
x = (x + 1)%2;
}