多线程详解——Java.Thread
1.1 多任务
在计算中,多任务是一种多个任务(也称之为进程)共享处理资源(如CPU)的方法。在多任务操作系统上,例如Windows XP,您可以同时运行多个应用程序。多任务实质是指操作系统在每个计算任务间快速切换,以致于看上去不同的应用似乎在同时执行多项操作。当CPU时钟频率稳步提高时,不仅应用程序的运行速率可以更快,而且操作系统在应用间的切换速率也更快。这样就提供了更好的整体性能——一台计算机可以同时发生多项操作,每项应用可以更快速地运行。
1.2 程序,进程,线程
1.程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。
2、进程(Process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。—生命周期
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
3、线程(Thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
1)若一个进程同一时间并行执行多个线程,就是支持多线程的
2)线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
3)一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
1.3 线程创建
创建一个新的执行线程有两种方法。
1.继承Thread类
一个是将一个类声明为Thread
的子类。 这个子类应该重写run
类的方法Thread
。 然后可以分配并启动子类的实例。
(线程开启不一定立即执行,有CPU调度执行)
class MyThread extends Thread {
public void run() {
. . .
}
}
比如说我使用多线程从网上下载三张图片:
package com.dgut;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class MyThread extends Thread{
String url;
String name;
public MyThread(String url,String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
WebLoad webLoad = new WebLoad(url,name);
System.out.println("下载了图片"+name);
}
public static void main(String[] args) {
MyThread t1 = new MyThread("https://pics3.baidu.com/feed/aa64034f78f0f736bf2dffdd5eaebe10e9c4131a.jpeg?token=bd72d0c2a5381c455e8421f904868dd9","1.jpg");
MyThread t2 = new MyThread("https://pics6.baidu.com/feed/b3b7d0a20cf431ad11f241b61fcda1a62fdd9825.jpeg?token=081d60e4180c5f45a9608e1b9fdc4738","2.jpg");
MyThread t3 = new MyThread("https://pics2.baidu.com/feed/960a304e251f95caf4a39d7085ec7237650952a1.jpeg?token=82c61ff80d87cd1771d45e408e2c5654","3.jpg");
t1.start();
t2.start();
t3.start();
}
}
class WebLoad{
public WebLoad(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("成功下载图片 "+name);
}
}
}
2.实现Runnable接口:
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程体
- 创建线程对象,调用start()方法启动线程
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,以下代码将创建一个线程并启动它运行:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
实现Runnable接口可以不用继承Thread类,避免了继承Thread后不能继承其他类的局限
故推荐实现Runnable接口
3.扩展:匿名对象的使用方法:
1 当对象对方法仅进行一次调用的时候,就可以简化成匿名对象。
如一个 对象需要进行调用方法2次,用匿名对象的
new Car().run()
new Car().run()
这是2个对象分别调用了run(),不是一个对象调用了多方法。
2 匿名对象可以作为实际参数进行传递。
public static void show(Car c)
{
//......
}
show(new Car());
1.4 线程不安全
多个线程操作同一个资源的情况下,线程不安全,数据紊乱;
例子:
package com.dgut;
import static java.lang.Thread.*;
public class TextRunnable implements Runnable {
private int ticket = 10; //票数为10
public void run() {
while(true){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"获得了第"+ticket--+"张票");
}
}
public static void main(String[] args) {
TextRunnable textRunnable = new TextRunnable();
new Thread(textRunnable,"甲1").start();
new Thread(textRunnable,"甲2").start();
new Thread(textRunnable,"甲3").start();
}
}
甲2和甲3两个顾客都抢到了第一张票;
1.5 实现Callable接口
- 实现Callable接口
- 重写call方法
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool()
- 提交执行: Future
r1 = ser.submit(Thead thead); - 获取结果: boolean rs1 = r1.get();
- 关闭服务: ser.shutdownNow();
package com.dgut;
import java.util.concurrent.*;
public class MyThread implements Callable<Boolean> {
public Boolean call() throws Exception {
System.out.println(Thread.currentThread().getName()+"运行");
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService ser = Executors.newFixedThreadPool(3);
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
Future<Boolean> r1 = ser.submit(t1);
Future<Boolean> r2 = ser.submit(t2);
Future<Boolean> r3 = ser.submit(t3);
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
ser.shutdownNow();
}
}
1.6 静态代理对象
1.7 Lamda表达式
- 避免匿名内部类定义过多
- 使得代码更加简洁
- 去掉一堆没有意义的代码,只留下逻辑;
函数式接口
- 任何接口,如果只包含一个抽象方法,那么它就是一个函数式接口。
public interface Runnable{
public abstract void run();
}
-
对于函数式接口,我们都可以用lambda表达式来创建该接口的对象;
new Thread(()->{System.out.print("多线程学习")}).start;
-
lambda表达式只能有一行代码的情况下才能简化一行,如果有多行那么只能用代码块包裹;(前提是接口是函数性接口)
-
多个参数也可以去掉类型,要去掉就都去掉,必须加上括号;
public class MyThread {
public static void main(String[] args) {
/* ILove love = new ILove(){
@Override
public void love() {
System.out.println("love you");
}
};
love.love();*/
ILove iLove = null;
iLove = ()-> System.out.println("love you too");
iLove.love();
}
}
interface ILove{
void love();
}
1.8 线程礼让:
- 礼让线程,让当前运行的线程暂停,但不阻塞;
- 将线程从运行状态转为就绪状态;
- 让CPU重新调度,礼让不一定成功!看CPU心情;
Thread.yield();
1.9 线程强制执行
自己阻塞,让其他线程运行;
Thread.join();
2.0 观察线程状态
Thread.State state = thread.getState();
//
判断线程是否死亡状态
while(state != Thread.State.TERMINATED){
...
}
2.1 线程优先级
MyThread t1 = new MyThread();
ti.setpriority(2);//优先级数为 1~10
ti.start();
2.2 守护线程
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守护线程的保姆:
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
User和Daemon两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果 User Thread已经全部退出运行了,只剩下Daemon Thread存在了,虚拟机也就退出了。 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。
值得一提的是,守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。下面的方法就是用来设置守护线程的。
Thread daemonTread = new Thread();
// 设定 daemonThread 为 守护线程,default false(非守护线程)
daemonThread.setDaemon(true);
// 验证当前线程是否为守护线程,返回 true 则为守护线程
daemonThread.isDaemon();
2.3 死锁
2.4 生产者消费者问题
- 信号灯法