java线程
生命周期图示:
1).线程介绍及创建方式
进程:就是一个正在运行的程序,比如:typeroa、eclipse、浏览器等等
线程:线程是进程中的一个执行单元,例如:QQ音乐播放音乐和下载音乐
- 一个程序至少有一个进程,一个进程至少有一个线程。
- 线程的划分尺度小于进程,使得多进程程序的并发性高。
- 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
- 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
关系:在一个进程中可以包含多个线程
当需要在同一个时刻去做多件事情时,就需要开启多个线程
多线程的好处
- 充分利用CPU的资源
- 简化编程模型
- 带来良好的用户体验
在 Java 中实现多线程有两种手段,一种是继承 Thread 类,另一种就是实现 Runnable 接口
两者的区别:
一、继承Thread类
编写简单,可直接操作线程
适用于单继承
二、实现Runnable接口
避免单继承局限性
便于共享资源
//方式一:new完subThread之后,直接调用thread类的方法
//1、线程中的任务不需要手动调用,而是通过start方法在创建并开启线程后自动调用的
//2、多个线程之间的运行是互不影响的
//3、线程的运行是随机的(和资源争夺有关)
public class ThreadDemo {
public static void main(String[] args) {
SubThread st = new SubThread();
//创建并开启了一个线程
st.start();
int a = 1/0;
System.out.println("--Over--");
}
class SubThread extends Thread{
/**
* 指定要执行的任务
*/
@Override
public void run() {
System.out.println("执行任务");
}
}
}
/**方式二:new完sub1Thread之后还要将这一新建对象放进Thread方法中。
1.实现run()方法
2.编写线程执行体;
3.创建线程对象;
4.调用start()方法启动线程。
*/
public class ThreadDemo {
public static void main(String[] args) {
Sub1Thread st = new Sub1Thread();
//创建一个Thread对象创建并开启线程
Thread t = new Thread(st);//Runnable target = new SubThread();
t.start();
//当主线程结束后,如果循环还在执行就说明循环时在另一个线程中
int a = 1/0;
System.out.println("--over--");
}
}
/**
* Runnable接口中提供了任务
* 实现类只需要给到具体的任务即可
*/
class Sub1Thread implements Runnable{
@Override
public void run() {
for(int i=0;i<200;i++) {
System.out.println("Runnable 创建线程");
}
}
}
在Java中,每次程序运行至少启动2个线程:一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程。
2).垃圾回收机制
虚拟机的内存是有限的,因此,对于在程序运行过程中创建的对象(占用的资源),虚拟机需要对其进行监控,当发现有对象不再有引用的时候就将其进行回收,该回收的动作是贯穿整个程序的,它是一个单独的线程,称该线程为垃圾回收机制。
@Override
protected void finalize() throws Throwable {
System.out.println("垃圾回收器运行");
super.finalize();
}
public static void main(String[] args) {
new User();
new User();
new User();
//通知 gc()
System.gc();
new User();
new User();
new User();
}
注意:
1、虽然手动通知了回收器需要回收垃圾对象,但是回收器并不是立马就执行的
2、垃圾回收器需要判断通知要回收的对象是否真的能回收
3).线程同步分析
关键词:synchronize 同步锁
语法:synchronize(锁){锁的内容}
要求:多个线程在操作时所用的锁是同一把锁
private int count = 200;
//使用Object对象作为唯一锁,前提是该对象只被创建一次供多个线程使用
Object object = new Object();
@Override
public void run() {
while(true) {
try {
synchronized (object) {
//假设A先执行,B进来,C进来
if(count>0) {
//A睡眠 B睡眠 C睡眠
Thread.sleep(10);
//谁先醒是随机的(需要争夺CPU资源)
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余"+(--count)+"张");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
//每当线程进来时都会重新创建一个新的锁
Object object = new Object();
while(true) {
try {
synchronized (object) {
//假设A先执行,B进来,C进来
if(count>0) {
//A睡眠 B睡眠 C睡眠
Thread.sleep(10);
//谁先醒是随机的(需要争夺CPU资源)
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余"+(--count)+"张");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private int count = 200;
//Object object = new Object();
@Override
public void run() {
while(true) {
try {
//使用class对象作为唯一锁
synchronized (Tickets.class) {
//假设A先执行,B进来,C进来
if(count>0) {
//A睡眠 B睡眠 C睡眠
Thread.sleep(10);
//谁先醒是随机的(需要争夺CPU资源)
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余"+(--count)+"张");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 锁方法
* 每个方法都默认持有一个this
* 所以方法的默认锁是this
*/
public synchronized void sale() {
try {
//假设A先执行,B进来,C进来
if(count>0) {
//A睡眠 B睡眠 C睡眠
Thread.sleep(10);
//谁先醒是随机的(需要争夺CPU资源)
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余"+(--count)+"张");
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
4).单例模式及同步锁
概念:单例模式就是保证对象在使用的过程是唯一的
思考:如何保证对象唯一?
考虑:对象是怎么来的----通过构造器来的
单例的实现步骤:
1、私有化构造器
2、提供一个功能的方法用来获取唯一的对象
3、将全局属性静态并私有化
//懒汉式
public class Single {
private static Single single;
private Single() {}
//节约资源,对象只在使用的时候才会创建
public static Single newInstance() {
if(single == null) {
single = new Single();
}
return single;
}
}
//开发选择该方式--省事 饿汉式
public class Single2 {
//类加载后就创建了,一致存在
private static Single2 single = new Single2();
private Single2() {}
public static Single2 newInstance() {
return single;
}
}
单例模式的目的是为了保证对象唯一,那么在多线程的情况下是否还能保证唯一?
public class Single {
private static Single single;
private Single() {}
public static Single newInstance() {
if(single == null) {//先判断,在考虑是否要等待锁,以提高性能
//假设线程A进来,B进来
synchronized (Single.class) {//加锁后提高了安全性,但是影响性能
if(single == null) {
try {
Thread.sleep(100);
single = new Single();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
return single;
}
}
5).synchronized和Lock锁的区别
1、synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁
2、用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了
3、synchronized放弃锁只有两种情况:①线程执行完了同步代码块的内容②发生异常;而lock不同,它可以设定超时时间,也就是说他可以在获取锁时便设定超时时间,如果在你设定的时间内它还没有获取到锁,那么它会放弃获取锁然后响应放弃操作
6).sleep和wait区别
public class ThreadDemo {
public static void main(String[] args) {
Object obj = new Object();
SubThread st = new SubThread(obj);
new Thread(st,"窗口A").start();
new Thread(st,"窗口B").start();
}
}
/**
* 线程在阻塞的过程中是否会释放锁
* sleep在阻塞的过程中是不释放锁的
* wait在阻塞的过程中是会释放锁的
*/
class SubThread implements Runnable{
private int count = 200;
private Object obj;
public SubThread(Object obj) {
this.obj = obj;
}
@Override
public void run() {
while(true) {
synchronized (obj) {
if(count>0) {
try {
System.out.println(Thread.currentThread().getName()+"持有锁");
--count;
//Thread.sleep(100);
//先唤醒其他的线程
obj.notify();
//然后自己进行等待
obj.wait();
System.out.println(Thread.currentThread().getName()+"重新持有锁 ");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
sleep和wait的区别:
sleep在阻塞的过程中是不释放锁的
wait在阻塞的过程中是会释放锁的
7).线程池Excutors
所谓的线程池其实就是将所有的线程都在同一个空间内进行管理,管理的是线程的数量、线程的创建、任务的分配
在实际需求中,可能会出现短时间内的任务量远远超出的线程数量,这种情况下,不同的处理方式对性能的影响是不同的,常见的处理方式有:
方式一:当有新任务的时候,其他线程是非空闲状态,那就新创建线程 不推荐
方式二:当任务数大于线程数时,不再创建新的线程,对任务进行排队
方式三:指定默认的线程数,超出后不再创建,对任务进行排队 推荐