线程
进程:当前正在运行的程序,一个应用程序在内存中的执行区域
线程:进程中的一个执行控制单元,执行路径
一个进程可以有一个线程,也可以有多个线程
单线程:安全性高,但是效率低
多线程:安全性低,效率高
多线程案例:360,迅雷等
1、程序运行原理
分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间
抢占式调度:优先让优先级高的线程使用cpu,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
抢占式调度
大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,“感觉这些软件好像在同一时刻运行着”。
实际上,cpu(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于cpu的一个核而言,某个时刻,只能执行一个线程,而cpu的在多个线程间切换速度相对我们感觉要快,看上去就是在同一时刻运行。
其实,多线程并不能提高程序的运行速度,但能够提高程序运行速率,让cpu的使用率更高。
2、线程的使用
在Java中创建线程的方法有两种
方法一:
继承java.lang.Thread类
继承Thread类
自定义一个类继承Thread类
重写Thread的run方法
创建一个该类的对象
使用该类对象调用start方法开启线程
package LESSON13; public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println( getName()+":"+i);//调用父类方法getName
//System.out.println("i"+i+"当前线程:"+Thread.currentThread().getName()); } } }
package LESSON13; public class ThreadDemo1 { public static void main(String[] args) { MyThread mt1 = new MyThread(); mt1.start();//使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 mt1.setName("张三"); MyThread mt2 = new MyThread(); mt2.start(); mt2.setName("老王");
//MyThread myThread01 = new MyThread("红色线程");//也可创建对象时直接赋予名称,输出时使用Thread.currentThread().getName()得到名称
//MyThread myThread02 = new MyThread("蓝色线程"); } }
方法二:
实现java.lang.Runnable接口
好处:面向接口编程,低耦合
实现Runnable接口
自定义一个类实现Runnable接口
重写Thread的run方法
创建一个该类的对象
使用Thread(Runnable target)构造方法创建Thread类对象
使用Thread类对象调用start方法开启线程
package LESSON13; public class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(i);
//System.out.println("i"+i+"线程名称:"+Thread.currentThread().getName()); } } }
package LESSON13; public class RunnableDemo { public static void main(String[] args) { MyRunnable mr = new MyRunnable();//MyRunnable实现Runnable接口,也属于Runnable类型 Thread t = new Thread(mr);//使用Thread(Runnable target)构造方法创建Thread类对象 t.start();
//MyRunnable mr = new MyRunnable();
//Thread t = new Thread(mr,"黄色线程");//创建对象时直接赋予名称
//t.start(); } }
3、使用匿名内部类
使用线程的内匿名内部类,可以方便的实现每个线程执行不同的线程任务操作。
方式一:创建线程对象时,直接重写Thread类中的run方法
package com.zy.dmeo03; public class Demo03 { public static void main(String[] args) { // 使用匿名内部类的方式()一般都有重写的发生,简化线程开发步骤 //创建线程,和指定任务一气呵成 new Thread(){//发生了重写 说明一定有一个类重写了Thread中的run方法 只是这个类没有名字(匿名) @Override//重写 public void run() { for (int a = 0; a < 200; a++) { System.out.println("a"+a); } } }.start();//调用start方法说明产生了一个对象(只有对象才能调用方法) //实际上有一个类重写了Thread中的run方法,并且产生了一个匿名子类对象,然后调用start方法 //1为什么匿名内部类------class Demo03中还有一个内部类,只是没有名字 //重写:1子类重写父类方法,2实现类重写接口方法 //----------------------------------------------- new Thread(){ @Override public void run() { for (int b = 0; b < 200; b++) { System.out.println("b"+b); } } }.start(); //以上代码创建了两个线程,并开启了两个线程 } }
方式2:使用匿名内部类的方式实现Runnable接口,重写Runnable接口中的run方法
package com.zy.dmeo03; public class Demo04 { public static void main(String[] args) { // TODO Auto-generated method stub new Thread( //new 一个接口 new Runnable() {//Runnable实现类(匿名) @Override//重写 public void run() { for (int a = 0; a < 200; a++) { System.out.println("a"+a); } } } //整体就是一个匿名的runnbale实现类对象 ).start(); //new Thread(runnbale实现类对象).start new Thread( new Runnable() { @Override public void run() { for (int b = 0; b < 200; b++) { System.out.println("b"+b); } } } ).start(); } }
4、线程池
线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去l频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。
package com.zy.demo04; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class MyPool { public static void main(String[] args) throws Exception { // 使用线程池,创建和管理线程------提高效率 //使用Executors工厂,创建线程池对象,保留3预备线程 ExecutorService pool = Executors.newFixedThreadPool(3); //池对象,提交任务 ExecutorService pool2 = pool; pool2.submit(//执行该任务会从该线程池中取出一个空闲的线程 new Runnable() { @Override public void run() { System.out.println("我的任务1"); } } ); pool2.submit(//执行该任务会从该线程池中取出一个空闲的线程 new Runnable() { @Override public void run() { System.out.println("我的任务2"); } } ); pool2.submit(//执行该任务会从该线程池中取出一个空闲的线程 new Runnable() { @Override public void run() { System.out.println("我的任务3"); } } ); //pool.shutdown();//结束 线程池一般处于等待执行状态,在程序中一般不需要关闭 Future<String> ft = pool.submit(new Callable<String>() { @Override public String call() throws Exception { System.out.println("任务5"); return "奖励一瓶矿泉水"; } }); System.out.println(ft.get()); } }
5、线程原理
在某一个时间段内,多个线程在CPU上交替运行,给人的感觉是“同时”执行。
实际上,在某一个时间点上,只能在CPU上运行一个线程。
原理:
1.线程启动之后,会先到CPU调度队列中排队,CPU会计算各线程优先级确定队里线程的先后顺序,挑选优先级最高的线程运行。
2.CPU运行某个线程,会给该线程分配运行的时间片,如果时间片到期,该线程自动退出CPU,然后重新排队。
3.该线程再一次运行在CPU上,接着上一次运行的位置继续向后运行,直到运行结束,线程死亡。
sleep方法
void sleep(long time)方法用于使当前线程休眠指定的毫秒数
特点:
让当前线程退出CPU的运行,处于阻塞(休眠)状态;一旦进入阻塞状态不会排队,但是该线程所持有的对象的锁不会释放;休眠一段时间后,结束阻塞,重新排队。
join方法
利用sleep方法对线程的控制是非常不精确的,然而 join方法可以精确控制线程。
void join() 等待该线程终止后再运行
void join(long millis) 等待线程运行多少毫秒后再运行
特点:
让当前线程退出CPU的运行,处于阻塞状态;直到调用的线程执行完毕后结束阻塞,重新排队。如果当前线程中调用了另外一个线程的 join方法,当前线程会立即阻塞,直到另外一个线程运行完成。
问题:
如果2个线程彼此调用对方的join方法,会导致程序无法进行。
解决办法:throws InterruptedException
6、线程同步
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
其实,线程安全问题都是由全局变量即静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时操作写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
产生数据不一致的原因
多个线程并发访问了同一个对象,如果破坏了不可分割的操作, 从而就会造成数据不一致,被多线程并发访问时如果一个对象有可能出现数据不一致的问题,那么这个对象称为线程不安全的对象。
如何解决多线程并发访问的问题
方式1:同步代码块
synchronized(锁对象){
可能会产生线程安全问题的代码
}
同步代码块中的锁对象可以使任意的对象;但多个线程是,要是用同一个锁对象才能够保证线程安全。
package com.zy.demo05; public class Demo05 { public static void main(String[] args) throws Exception { for (int a = 0; a < 200; a++) { Thread.sleep(1000);//毫秒//休眠 该线程在改时间内不抢夺cpu的使用权 System.out.println("*"); } } }
方式2:同步方法
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步方法中的锁对象是this
package com.zy.demo06; public class Demo06 { public static synchronized void show(){//保护方法(线程同步) for (int a = 0; a < 200; a++) { } }//在该方法运行期间不会被抢走,方法结束后才允许别的线程抢夺 }
静态同步方法:在方法声明上加上static synchronized
public static synchronized void method(){
可能会产生线程安全问题的代码
}
静态同步方法中的锁对象是类名.class
7、死锁
同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一众现象:程序出现无线等待,这种现象我们称为死锁。这种情况能避免就避免掉。
synchronized(A锁){
synchronized(B锁){
}
}
8、等待唤醒机制
在开始讲解等待唤醒机制之前,有必要搞清一个概念——线程之间的通信:多个线程在处理同一资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即——等待唤醒机制。
其实,所谓唤醒的意思就是让线程池的线程具备执行资格。必须注意的是,这些方法都是在同步中才能生效。同时这些方法在使用时必须标明所属锁,这样才可以明确这些方法操作的到底是那个锁上的线程。