进程和线程
进程:并发情况下占有计算机资源的程序,是一个动态的概念,是对计算机系统而言的资源使用者。
线程:获得资源的进程会利用多个线程完成工作。线程是进程的颗粒级执行单元,属于轻量级的进程。
Thread、Runnable、Callable
Thread
开启多线程的核心类,内部提供很多有关线程的方法,并实现了Runnable接口。
Thread开启线程基本使用
public class DemoThread {
/*
* 进程:一个正在运行的程序,可以包含多条线程,但至少要有一条线程再执行
*
* java程序运行时默认开启一条主线程
*
* 线程:就是程序的执行路径,多线程就是多条线程同时执行(假象),Cpu再多条线程之间交替执行
*
* 开启线程的方式:
*
* 1.继承Thread类,实现run()方法,再run()方法中放想要执行的代码
*/
public static void main(String[] args) throws Exception {
// 4.创建线程对象
MyThread myThread = new MyThread("线程3");
// 设置线程名
myThread.setName("线程1");
System.out.println(myThread.getPriority());
// 5.使用start()方法开启线程
myThread.start();
for (int i = 0; i < 200; i++) {
System.out.println("bb");
}
}
}
//1.创建继承Thread类的子类,实现run()方法
class MyThread extends Thread {
public MyThread() {
super();
// TODO 自动生成的构造函数存根
}
public MyThread(String name) {
super(name);
// TODO 自动生成的构造函数存根
}
@Override
// 2.重写run()方法
public void run() {
// 3.run()方法中放需要执行的任务
for (int i = 0; i < 200; i++) {
// 获取线程名称
System.out.println(getName() + "aa");
}
}
}
Runnable
为弥补Thread的单继承缺陷,Runnable接口同样可以开启多线程,不同的是其可以完成类的“多继承”。
Runnable开启线程基本使用
public class DemoRunnable {
public static void main(String[] args) {
//4.创建runnable对象,并放入Thread类中。
MyRunnable mr=new MyRunnable();
Thread thread = new Thread(mr);
//设置线程名
thread.setName("线程2");
//5.使用start方法
thread.start();
for (int i = 0; i < 2009; i++) {
System.out.println("bbbbbb");
}
}
}
//1.找一个类实现Runnable接口
class MyRunnable implements Runnable {
@Override
//2.重写run方法
public void run() {
//3.将需要执行的任务放在这
for (int i = 0; i < 2009; i++) {
//获取线程名Thread.currentThread().getName()
System.out.println(Thread.currentThread().getName()+"aa");
}
}
}
Callable
Thread、Runnable多实现的多线程都无法带出返回值,而往往实际应用中需要将数据在多个线程中传递,为此Callable出现并解决该问题。
Callable开启线程基本使用
public class DemoCallable {
/**
* Callable 既能开启线程,又能得到线程返回值结果
*
* 1.实现Callable接口,重写call方法
*
* 2.创建FutureTask对象
*
* 3.创建Thread对象,并将FutureTask对象传入构造方法中
*
* 4.开启start方法
*
* @throws Exception
* @throws InterruptedException
*
*/
public static void main(String[] args) throws InterruptedException, Exception {
// 创建FutureTask对象
FutureTask<Integer> ft1 = new FutureTask<Integer>(new myCallable());
FutureTask<Integer> ft2 = new FutureTask<Integer>(new myCallable());
// 创建Thread对象,放入FutureTask对象,开启start方法
Thread thread1 = new Thread(ft1, "线程a");
thread1.start();
Thread thread2 = new Thread(ft2, "线程b");
thread2.start();
// 调用get方法获取call方法的返回值
System.out.println(ft1.get());
System.out.println(ft2.get());
}
}
class myCallable implements Callable<Integer> {
// 重写call方法
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 70; i++) {
System.out.println(Thread.currentThread().getName() + ",当前使用的值" + i);
sum += i;
}
return sum;
}
}
线程状态
线程分类
线程可以分为用户线程和后台线程。
用户线程正相反,可以理解为前台线程,虚拟机必须等待其运行完毕才可以结束进程。
后台线程可以按其表面意义进行理解,作为后台的线程运行,其最本质的特征就是虚拟机无需等待其运行完毕即可结束进程。
public class DemoDaemon {
public static void main(String[] args) {
Thread thread1 = new Thread() {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("我是后台线程");
}
}
};
// 将线程设置为后台线程
thread1.setDaemon(true);
Thread thread2 = new Thread() {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("我是前台线程");
}
}
};
thread2.start();
thread1.start();
}
}
Thread对应的线程方法
线程优先级
线程可以有优先级,优先级从1-10,越高越容易被执行,但是最主要还是看cpu心情。
优先级常量 | Value |
最小优先级 | MIN_PRIORITY(1) |
默认优先级 | NORM_PRIORITY(5) |
最大优先级 | MAX_PRIORITY(10) |
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Thread threadMin = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("最小优先级" + i);
}
});
Thread threadMax = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("最大优先级" + i);
}
});
//注意:要先设置优先级,在启动线程才会生效!
threadMin.setPriority(Thread.MIN_PRIORITY);
threadMax.setPriority(Thread.MAX_PRIORITY);
threadMin.start();
threadMax.start();
}
}
线程状态
在Thread中定义了一个enum类型用来存放线程状态,可以使用getState方法获取当前线程的状态。
状态 | Value |
新建 | NEW |
运行 | RUNNABLE |
阻塞 | BLOCKED |
等待 | WAITING |
超时等待 | TIMED_WAITING |
终止 | TERMINATED |
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Thread thread=new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.print("T");
}
});
System.out.println(thread.getState());
thread.start();
System.out.println(thread.getState());
Thread.sleep(1000);
System.out.println("\n"+thread.getState());
}
}
线程休眠
sleep(long millis)方法,可以让当前线程休眠millis毫秒,其特殊在如果该线程有锁,则会抱着锁休眠,即不释放资源!
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("T");
}
}).start();
}
}
线程停止
stop()和 interrupt()
stop()强行终止当前线程,存在线程安全问题,会破坏线程的原子性。
interrupt()不会真的中止线程,只是给线程加上了一个标识,即isInterrupted从false变成了true,我们需要通过isInterrupted标识进行手动条件判断终止线程,而利用标志位终止线程也是最好的线程停止方式。
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread(()->{
for (int i = 0; i < 100; i++) {
if (i==5)Thread.currentThread().stop();
System.out.println("stop *");
}
}).start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
if (i==5)Thread.currentThread().interrupt();
if (Thread.currentThread().isInterrupted())break;
System.out.println("interrupt *");
}
}).start();
}
}
线程让步
yield()可以让当前线程停止重新进入就绪状态抢占时间片,虽然让步很礼貌,但是运气好的时候会一直抢到时间执行,因此让步可能会失败。
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread(()->{
for (int i = 0; i < 10; i++) {
if (i==5)Thread.yield();
System.out.println("*");
}
}).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("/");
}
}).start();
}
}
线程插入
join()方法可以进行线程插队,该方法插入后会独占时间片知道执行完。
public class DemoTest extends JFrame {
private Thread threadA;
private Thread threadB;
JProgressBar jProgressBar1=new JProgressBar();
JProgressBar jProgressBar2=new JProgressBar();
int i=0;
public DemoTest(){
super();
getContentPane().add(jProgressBar1, BorderLayout.NORTH);
getContentPane().add(jProgressBar2, BorderLayout.SOUTH);
jProgressBar1.setStringPainted(true);
jProgressBar2.setStringPainted(true);
threadA=new Thread(new Runnable() {
int i=0;
@Override
public void run() {
while (true){
jProgressBar1.setValue(++i);
try {
Thread.sleep(100);
threadB.join();
}catch (Exception e){
e.printStackTrace();
}
}
}
});
threadA.start();
threadB=new Thread(new Runnable() {
int i=0;
@Override
public void run() {
while (true){
jProgressBar2.setValue(++i);
try {
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
if (i==100){
break;
}
}
}
});
threadB.start();
}
public static void init(JFrame jFrame,int width,int height){
jFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
jFrame.setSize(width, height);
jFrame.setVisible(true);
}
public static void main(String[] args) {
init(new DemoTest(),200,100);
}
}
线程通信
wait()和notify()实现线程间的通信,要利用锁对象控制与该锁有关的线程,可以利用标志位进行线程执行,wait会使当前线程释放锁并进入等待状态,notify会通知其他线程获取锁并进行执行。
public class DemoPrinter {
/**
* 多线程通信:
*
* 需求:
* 要求有两条线程,具有通信效果
* 每个线程只执行一次
*
* 效果:
* 传智播客
* 黑马程序员
* 传智播客
* 黑马程序员
*
* wait和notify方法必须用于同步代码中
*
* 并且必须用锁对象来调用
*
* 小细节:
*
* sleep等待不释放锁
*
* wait等待释放锁
*/
public static void main(String[] args) {
Printer pr = new Printer();
new Thread() {
public void run() {
while (true) {
try {
pr.print1();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start();
new Thread() {
public void run() {
while (true) {
try {
pr.print2();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer {
int flag=1;
public void print1() throws Exception {
synchronized (Printer.class) {
if(flag!=1) {
//flag不是1,不是线程1执行时间,线程1等待
Printer.class.wait();
}
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.println();
flag=2;
Printer.class.notify();
}
}
public void print2() throws Exception {
synchronized (Printer.class) {
if(flag!=2) {
//flag不是2,不是线程2执行时间,线程2等待
Printer.class.wait();
}
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.println();
flag=1;
Printer.class.notify();
}
}
}
不安全情况
当多个线程同时操作同一数据时,由于时间片存在影响线程代码的运行进度,就可能导致数据出现不一致的情况,破坏数据的原子性。
public class DemoThreadTest2 {
/**多个线程处理同一个共享资源的情况
*
* 需求:模拟火车售票过程
* 两个售票窗口共享10张票
*
* A:接口式实现
*
* B:继承类实现
*
*/
public static void main(String[] args) {
Thread thread1 = new MyTicketsThread("窗口1");
Thread thread2 = new MyTicketsThread("窗口2");
thread1.start();
thread2.start();
}
}
class MyTicketsThread extends Thread{
public MyTicketsThread() {
super();
}
public MyTicketsThread(String name) {
super(name);
}
int tickets=20;
@Override
public void run() {
while (true) {
if (tickets<=0) {
break;
}
System.out.println(getName()+"卖出了"+(tickets--)+"号票");
}
}
}
锁机制
为了解决多线程的不安全情况,引入synchronized关键字和锁机制,实际上就是一个显示锁(Lock),一个隐式锁(synchronized)。用这俩种方法保证多线程的数据操作同步性。
synchronized隐式锁
synchronized可以直接用于方法中,锁为该类对象,也可以独立成块synchronized(一般用类.class){ }
public class DemoThreadTest3 {
/**
* 多个线程处理同一个共享资源的情况
*
* 需求:模拟火车售票过程 两个售票窗口共享10张票
*
* A:接口式实现
*
* B:继承类实现
*
* 同步代码方法 在方法返回值之前加 synchronized
*
* 锁对象:非静态方法就是this类对象
*
* 静态方法就是class字节码
*/
public static void main(String[] args) {
MyTickets1 myTickets1 = new MyTickets1();
Thread thread1 = new Thread(myTickets1, "窗口1");
Thread thread2 = new Thread(myTickets1, "窗口2");
Thread thread3 = new Thread(myTickets1, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyTickets1 implements Runnable {
int tickets = 100;
public void run() {
while (true) {
extracted();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace(); }
}
}
public synchronized void extracted() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了" + (tickets--) + "号票");
}
}
}
Lock显示锁
Lock接口实现显示锁,最常用的是其实现类ReentrantLock(可重入锁),主要依靠lock()和unlock()进行数据同步。
public class DemoThreadTest4 {
public static void main(String[] args) {
Tickets ti=new Tickets();
new Thread(ti,"窗口1").start();
new Thread(ti,"窗口2").start();
new Thread(ti,"窗口3").start();
}
}
class Tickets implements Runnable {
int tickets = 100;
Lock lock=new ReentrantLock();
public void run() {
while (true) {
lock.lock();//上锁
if (tickets <= 0) {
lock.unlock();
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖出了" + (tickets--) + "号票");
lock.unlock();//释放锁
}
}
}
死锁
虽然锁的出现解决了数据操作同步问题,保证多线程安全性,但同时也引发一个很严重的现象——死锁。该现象会导致进程卡死,如果运行在服务器中将会是极其严重的错误。
死锁的出现条件(四条皆必要,缺一不可)
- 互斥条件:多条线程互相占有对方想要的资源。
- 占有和等待条件:每条线程占有对方想要的资源,同时等待自己想要的资源。
- 不剥夺条件:线程所占有的资源不可以被强制剥夺,只可以等待其主动释放。
- 循环等待条件:每条线程都占有一个他人想要的资源,并等待自己想要的资源,由此形成一条死链。
避免死锁的核心思想就是破坏其出现的条件。
线程池
手动开启线程需要新建很多Thread类启动,因此不仅非常浪费空间,更重要的是无法对这些线程进行统一管理。为此引入了线程池的概念。
线程池最主要的工作就是管理线程相关事宜。
以Callable接口进行举例
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//新建固定容量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//提交(运行)两个线程,由于该线程实现Callable接口,因此具有Future类型的返回值,其泛型根据Callable接口而定
Future submit1 = executorService.submit(new Cosumer());
Future<Integer> submit2 = executorService.submit(new Producer());
//利用get方法获取返回值
Object res1 = submit1.get();
Integer res2 = submit2.get();
//输出
System.out.println(res1);
System.out.println(res2);
//停止所有线程
executorService.shutdownNow();
}
}
class Producer implements Callable<Integer> {
private int i = 0;
@Override
public Integer call() throws Exception {
for (; i < 10; i++) {
System.out.println(i);
}
return i;
}
}
class Cosumer implements Callable {
private int i = 10;
@Override
public Object call() throws Exception {
for (; i < 20; i++) {
System.out.println(i);
}
return i;
}
}
总结
一个程序就是一个进程,该进程默认有一个主线程,开启多线程后会由CPU进行时间调度完成各个线程。
开启线程有三种方法
- 继承Thread类重写run方法
- 实现Runnable接口重写run方法
- 实现Callable接口重写call方法
Thread类是线程的核心类,其封装有很多控制线程的方法。
当线程太多时可以利用线程池进行管理。
最重要的事情是保证数据同步,在进行数据操作的位置或方法加Lock显示锁或synchronized关键字。
Author
小葳宝贝最爱吃饭