程序、进程与线程:
程序:为完成特定功能,用某种计算机语言编写的一组指令的集合;
进程:运行中的程序;
线程:进程中细分出来,用于完成程序众多功能中的某个功能的执行分支。
例:程序类似于没有开启的金山毒霸,打开后的金山毒霸类似一个进程,在金山毒霸里同时进行木马查杀、垃圾清理等操作相当于开启了多个线程。
多进程与多线程:
一个进程至少有一个线程,即主线程。
多进程:同时运行多个程序;
多线程:多个线程同时执行。
创建线程的方式一:继承Thread类
1、创建一个继承了Thread类的子类;
2、重写Thread类中的run()方法;
3、在main方法中创建该类的实例化对象;
4、调用该实例化对象的start()方法:①开启了该线程;②调用了该对象的run()方法。
例:
package cn.jingpengchong.thread;
//1、创建一个继承了Thread类的子类;
public class MyThread extends Thread {
//2、重写Thread类中的run()方法;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 3 == 0){
System.out.println(i);
}
}
}
}
package cn.jingpengchong.test;
import cn.jingpengchong.thread.MyThread;
public class Test {
public static void main(String[] args) {
//3、在main方法中创建该类的实例化对象;
MyThread thread = new MyThread();
//4、调用该实例化对象的start()方法
thread.run();
}
}
注意:
1、一个Thread类子类的实例化对象在一个程序中只能调用一次start()方法;
2、不能直接调用run()方法开启线程。
创建线程的方式二:实现Runnable接口
1、创建一个Runnable接口的实现类;
2、实现Runnable接口的run()方法;
3、创建一个Runnable接口实现类的实例化对象;
4、将Runnable接口实现类的实例化对象作为实参传给Thread类的有参构造方法,以创建一个Thread类的实例化对象;
5、调用Thread类的实例化对象的start()方法:①开启了该线程;②调用了该对象的run()方法。
例:
package cn.jingpengchong.thread;
//1、创建一个Runnable接口的实现类;
public class MyRunnable implements Runnable {
//2、实现Runnable接口的run()方法;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 3 == 0){
System.out.println(i);
}
}
}
}
package cn.jingpengchong.test;
import cn.jingpengchong.thread.MyRunnable;
public class Test {
public static void main(String[] args) {
//3、创建一个Runnable接口实现类的实例化对象;
MyRunnable runnable = new MyRunnable();
//4、创建一个Thread类的实例化对象;
Thread thread = new Thread(runnable);
//5、调用Thread类的实例化对象的start()方法
thread.run();
}
}
相比方法一的优点:
1、避免了单继承的局限;
2、可以实现多个线程之间的数据共享。
创建线程的方式三:实现Callable接口
1、创建一个Callable接口的实现类,泛型为call()方法的返回值类型;
2、实现Callable接口的call()方法,call()方法的返回值类型为Callable接口的泛型;
3、创建一个Callable接口的实现类的实例化对象;
4、将Callable接口的实现类的实例化对象作为实参传递给FutureTask的构造方法中,以创建一个FutureTask类的实例化对象,泛型为call()方法的返回值类型;
5、将FutureTask类的实例化对象作为实参传递给Thread类的构造方法中,以创建一个Thread类的实例化对象;
6、调用Thread类的实例化对象的start()方法;
7、调用FutureTask类的实例化对象的get()方法,可以获得call()方法的返回值。
例:
package cn.jingpengchong.thread;
import java.util.concurrent.Callable;
//1、创建一个Callable接口的实现类;
public class MyCallable implements Callable<Integer> {
//2、实现Callable接口的call()方法;
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 10; i++) {
if (i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
package cn.jingpengchong.test;
import cn.jingpengchong.thread.MyCallable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) {
//3、创建一个Callable接口的实现类的实例化对象;
MyCallable mc = new MyCallable();
//4、创建一个FutureTask类的实例化对象;
FutureTask<Integer> task = new FutureTask<Integer>(mc);
//5、创建一个Thread类的实例化对象;
Thread thread = new Thread(task);
//6、调用Thread类的实例化对象的start()方法;
thread.start();
try {
//7、调用FutureTask类的实例化对象的get()方法获得call()方法的返回值。
Integer sum = task.get();
System.out.println("总和为:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
相比方法二的优点:
1、可以抛出异常;
2、可以有返回值。
创建线程的方式四:使用线程池
1、创建指定数量的线程池;
2、执行指定的线程操作,需要提供Runnable或Callable接口实现类的实例化对象
- execute()方法用于启动Runnable方式创建的线程,没有返回值;
- submit()方法用于启动Callable方式创建的线程,有返回值。
3、关闭连接池。
例:
package cn.jingpengchong.test;
import cn.jingpengchong.thread.MyCallable;
import cn.jingpengchong.thread.MyRunnable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.execute(new MyRunnable());
pool.submit(new MyCallable());
pool.shutdown();
}
}
相对于其他方式的优点:
1、提高响应速度,减少了创建新线程的时间;
2、降低资源消耗,重复利用线程池中线程,不需要每次都创建;
3、便于线程管理。
线程的常用方法:
- 1、Thread():空参构造方法;
- 2、Thread(String name):直接为线程命名的构造方法;
- 3、currentThread():获取执行当前代码的线程;
- 4、getName():获取当前线程的名称;
- 5、setName():设置当前线程的名称;
- 6、yield():释放当前线程的cpu执行权;
- 7、join():执行当前代码的线程停止,直到该方法的调用者线程执行完毕才执行;
- 8、sleep():让当前线程停止指定的毫秒后再执行;
- 9、isAlive():判断当前线程是否存活;
线程的优先级:
getPriority():获取当前线程的优先级;
setPriority():设置当前线程的优先级:
- 优先级从低到高依次为:1-10
- 特定的几个优先级:MIN_PRIORITY(1)、MAX_PRIORITY(10)、NORM_PRIORITY(5)
某个线程优先级越高,越有可能先执行该线程,而并非一定先执行该线程。
线程的生命周期:
多线程小练习:多窗口卖票
package cn.jingpengchong.thread;
public class Tickets implements Runnable {
private int tickets = 100;
@Override
public void run() {
while (tickets>0){
System.out.println(Thread.currentThread().getName()+":卖票-->"+tickets);
tickets --;
}
}
}
package cn.jingpengchong.test;
import cn.jingpengchong.thread.Tickets;
public class Test {
public static void main(String[] args) {
Tickets tickets = new Tickets();
Thread window1 = new Thread(tickets);
Thread window2 = new Thread(tickets);
Thread window3 = new Thread(tickets);
window1.setName("窗口①");
window2.setName("窗口②");
window3.setName("窗口③");
window1.start();
window2.start();
window3.start();
}
}
运行测试类结果如下:
问题:从结果看,有两个问题:
- 输出了3个100;
- 输出没有严格按照递减顺序。
对于上述问题,是由于出现了线程安全性问题,即某个线程在刚刚输出tickets,尚未操作tickets的时候,其他线程也进来了,并且开始输出尚未改变的tickets。为了解决这个问题,我们引入了同步机制与Lock锁,即某个线程在操作tickets的时候,其他线程要等待该线程处理完毕才能操作tickets。