进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程,有它的自身的产生,存在,消亡的过程。 -----生命周期
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。
如果一个进程同一时间 “并行” 执行多个线程,就是支持多线程的。
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个cpu(采用时间片)同时执行多个任务。比如:秒杀,多个人做同一件事。
创建线程有三种方式,分别是继承Thread类、实现Runnable接口、实现Callable接口。
1.1通过继承Thread类来创建并启动线程的步骤如下:-
定义Thread类的子类,并重写该类的run()方法,该run()方法将作为线程执行体。
-
创建Thread子类的实例,即创建了线程对象。
-
调用线程对象的start()方法来启动该线程。
特别注意:进去start的源码可知,实际调用的是start0方法,而start0又是调用JVM底层的方法实现线程,我们不能通过直接调用run()的方式启动线程(),run方法就是一个普通的方法,没有真正启动一个线程,就会把run方法执行完毕,会变成串行。
-
定义Runnable接口的实现类,并实现该接口的run()方法,该run()方法将作为线程执行体。
-
创建Runnable实现类的实例,并将其作为Thread的target来创建Thread对象,Thread对象为线程对象。
-
调用线程对象的start()方法来启动该线程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值。然后再创建Callable实现类的实例。
- 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
3.线程的生命周期:线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。
新建:就是刚使用new方法,new出来的线程;
就绪:就是调用的线程的 start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢到CPU资源,谁开始执行;
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能;
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAlI()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;
4.线程安全问题:线程安全:就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
解决方法:加锁 ---- > 1.使用使用synchronized关键字 2.使用lock接口下的实现类
两者的不同点:
* 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器。 * lock需要手动的启动同步( lock() ),同时结束同步也需要手动的实现( unclock() )5.synchronized关键字的使用 5.1同步方法: 就是把需要同步的代码提取一些出来作为一个方法,进行同步。 public synchronized 方法名(){ };
class window implements Runnable { private static int ticket = 100; // 同步方法: public void run() { while (true) { boolean show = show(); if (show==false){ return; } } } public synchronized boolean show() { if (ticket > 0) { try { Thread.sleep(50); // 出现安全问题,票数有0张和-1张的情况 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ": 票号 " + ticket); ticket--; } else { System.out.println("票已经卖完了"); return false; } return true; } }5.2:同步代码块:
class window implements Runnable { private static int ticket = 100; Object o1 = new Object(); // 锁要定义在run()的外面,保证唯一性 public void run() { while (true) { synchronized (o1) { if (ticket > 0) { try { Thread.sleep(50); // 出现安全问题,票数有0张和-1张的情况 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ": 票号 " + ticket); ticket--; } else { break; } } } } }
特别注意: 能同步代码块就不要同步方法,因为同步方法对内存的开销大。
6.调用Lock方法class wind implements Runnable{ private static int ticket=100; // 1.实例化ReentrantLock private ReentrantLock lock=new ReentrantLock(); @Override public void run() { while (true){ try { // 调用锁定方法lock() lock.lock(); if (ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" : 票数 "+ticket); ticket--; }else { break; } }finally { //3.调用解锁方法 lock.unlock(); } } } }7.sleep()和wait()的区别
1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
2.不同的:
- sleep()是Thread类中的静态方法,而wait()是Object类中的成员方法;
- sleep()可以在任何地方使用,而wait()只能在同步方法或同步代码块中使用;
- sleep()不会释放锁,而wait()会释放锁,并需要通过notify()/notifyAll()重新获取锁。
-
notify()
用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。
-
notifyAll()
用于唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行
何为同步: 只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
-
同步方法
即有synchronized关键字修饰的方法,由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。需要注意, synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
-
同步代码块
即有synchronized关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。需值得注意的是,同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
同步:
-
wait()、notify()、notifyAll()
如果线程之间采用synchronized来保证线程安全,则可以利用wait()、notify()、notifyAll()来实现线程通信。这三个方法都不是Thread类中所声明的方法,而是Object类中声明的方法。原因是每个对象都拥有锁,所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作。并且因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。另外,这三个方法都是本地方法,并且被final修饰,无法被重写。
wait()方法可以让当前线程释放对象锁并进入阻塞状态。notify()方法用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。notifyAll()用于唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。
。并且因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。另外,这三个方法都是本地方法,并且被final修饰,无法被重写。
wait()方法可以让当前线程释放对象锁并进入阻塞状态。notify()方法用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。notifyAll()用于唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了已就绪(将要竞争锁)的线程,阻塞队列存储了被阻塞的线程。当一个阻塞线程被唤醒后,才会进入就绪队列,进而等待CPU的调度。反之,当一个线程被wait后,就会进入阻塞队列,等待被唤醒。