进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。线程(Thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
程序启动运行main方法的时候,Java虚拟机会启动一个进程,并创建主线程main。随着调用Thread对象的 start(),另外一个新的线程也启动了,整个应用就在多线程下运行。 所以,一个进程可以包括多个线程。而多进程是对于操作系统和CPU而言的,比如电脑同时启动了QQ、微信、音乐软件等应用程序时,每个程序都是一个进程,此时CPU就是多进程的。
/**
* @author RuiMing Lin
* @date 2020-03-12 14:19
*/
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("mythread1");
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
}
2.2、实现Runnable接口
/**
* @author RuiMing Lin
* @date 2020-03-12 14:22
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable 1");
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
2.3、使用匿名内部类实现
/**
* @author RuiMing Lin
* @date 2020-03-12 14:25
*/
public class Demo1 {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名内部类实现");
}
});
thread1.start();
}
}
2.4、实现Runnable接口的好处
实现Runnable接口比继承Thread类所具有的优势:
可以避免java中的单继承的局限性; 增加程序的健壮性,实现解耦操作; 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。 2.5、使用Callable和Future创建线程 Callable是一个接口,有点类似于Runnable接口,不过较之更加强大。Callable提供了一个类似于run方法的call(),但call()更加强大。主要体现在两点:call()可以有返回值,call()可以抛出异常。
理论上我们可以类似于上述匿名内部类的方法将Callable接口传递给Thread的构造方法,但实际上不行,因为Thread类提供的构造方法没有传递Callable参数类型的,而Callable又不是Runnable的子接口。所以就有了Future接口,该接口是Runnable的子接口,并有一个实现类FutureTask封装了Callable接口。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author RuiMing Lin
* @date 2020-03-12 14:25
*/
public class Demo1 {
public static void main(String[] args) throws Exception{
FutureTask futureTask = new FutureTask(new Callable() {
@Override
public Integer call() throws Exception {
int i = 0;
for ( ;i < 10; i++){
System.out.println(Thread.currentThread().getName() + "i = " + i);
}
return i;
}
});
new Thread(futureTask).start();
Integer integer = futureTask.get();//返回call()的返回值,必须等到子栈结束后才会得到返回值,会导致程序阻塞
System.out.println("integer = " + integer);
}
}
3、线程的生命周期
新建态: 当一个线程被new出来,也就是被创建出来的时候;
就绪态: 被创建的线程调用start()方法
阻塞态线程休眠时间到或者获得同步锁等等;
运行态: 就绪态线程获得CPU资源;
阻塞态: 运行态线程调用sleep()方法进行休眠或者等待同步锁中;
死亡态: 线程被关闭或者出现异常等等。
Thread的join()可以让一个线程等待调用该方法的线程完全完成的方法:
/**
* @author RuiMing Lin
* @date 2020-03-13 20:39
*/
public class MyRunnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "执行了" + i + "次");
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
if (i == 20){
Thread thread = new Thread(new MyRunnable1());
thread.setName("我的线程");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程main方法执行了" + i + "次");
}
}
}
结果输出为:
如果不使用join()方法:
/**
* @author RuiMing Lin
* @date 2020-03-13 20:39
*/
public class MyRunnable1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "执行了" + i + "次");
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
if (i == 20){
Thread thread = new Thread(new MyRunnable1());
thread.setName("我的线程");
thread.start();
// try {
// thread.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
System.out.println("主线程main方法执行了" + i + "次");
}
}
}
结果输出为:
原因分析:当主线程的i = 20时,创建自定义线程,开启自定义线程并调用join方法,此时主线程main()被挂起,直到自定义线程完全执行结束后才放行让主线程执行。
守护线程表示只要被守护的线程一结束,它也就被结束了。调用方法:
thread.setDaemon(true);
4.3、休眠线程运行态的线程调用sleep()进入休眠线程,调用方法:
Thread.sleep(1000);
5、线程安全与线程同步 5.1、安全问题先抛出一个线程之间的安全问题:如今有十张火车票,两个窗口同时售卖
/**
* @author RuiMing Lin
* @date 2020-03-13 15:57
*/
public class Ticket1 implements Runnable{
private int ticket = 10; // 表示有十张火车票
@Override
public void run() {
for (int i = 0; i 0){
System.out.println(Thread.currentThread().getName()
+ "卖了编号为" + ticket + "的火车票");
ticket --;
try {
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Ticket1 ticket = new Ticket1();
Thread thread1 = new Thread(ticket);
Thread thread2 = new Thread(ticket);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread1.start();
thread2.start();
}
}
结果输出为:
窗口A卖了编号为10的火车票
窗口B卖了编号为9的火车票
窗口A卖了编号为8的火车票
窗口B卖了编号为8的火车票
窗口A卖了编号为6的火车票
窗口B卖了编号为6的火车票
窗口B卖了编号为4的火车票
窗口A卖了编号为4的火车票
窗口B卖了编号为2的火车票
窗口A卖了编号为2的火车票
很明显,这样的结果是不满足需求的。原因分析:因为多线程是CPU随机执行的,当线程一执行System.out.println(Thread.currentThread().getName()+ “卖了编号为” + ticket + “的火车票”)语句时,还未执行ticket --跳转到线程二,此时就会出现数据没有及时更新、同时售卖同一张票的情况。
5.2、解决方案一 ---- 同步代码块对需要连续执行的代码加一把锁,称为同步代码块。代码代码块中的代码必须执行完才会切换到其他线程,这样就能保证了线程之间的安全。
/**
* @author RuiMing Lin
* @date 2020-03-13 15:57
*/
public class Ticket2 implements Runnable{
private int ticket = 10; // 表示有十张火车票
@Override
public void run() {
for (int i = 0; i 0){
System.out.println(Thread.currentThread().getName()
+ "卖了编号为" + ticket + "的火车票");
ticket --;
try {
Thread.sleep(400);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
Thread thread1 = new Thread(ticket);
Thread thread2 = new Thread(ticket);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread1.start();
thread2.start();
}
}
结果输出为:
窗口A卖了编号为10的火车票
窗口B卖了编号为9的火车票
窗口B卖了编号为8的火车票
窗口B卖了编号为7的火车票
窗口B卖了编号为6的火车票
窗口B卖了编号为5的火车票
窗口B卖了编号为4的火车票
窗口B卖了编号为3的火车票
窗口B卖了编号为2的火车票
窗口A卖了编号为1的火车票
可以看出,这样就满足需求了。
5.3、解决方案二 ---- 同步方法/**
* @author RuiMing Lin
* @date 2020-03-13 15:57
*/
public class Ticket2 implements Runnable{
private int ticket = 10; // 表示有十张火车票
private synchronized void sale(){
if (ticket > 0){
System.out.println(Thread.currentThread().getName()
+ "卖了编号为" + ticket + "的火车票");
ticket --;
try {
Thread.sleep(400);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
sale();
}
}
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
Thread thread1 = new Thread(ticket);
Thread thread2 = new Thread(ticket);
thread1.setName("窗口A");
thread2.setName("窗口B");
thread1.start();
thread2.start();
}
}
结果输出:
窗口A卖了编号为10的火车票
窗口A卖了编号为9的火车票
窗口A卖了编号为8的火车票
窗口A卖了编号为7的火车票
窗口A卖了编号为6的火车票
窗口A卖了编号为5的火车票
窗口B卖了编号为4的火车票
窗口A卖了编号为3的火车票
窗口B卖了编号为2的火车票
窗口A卖了编号为1的火车票
wait():导致当前线程等待;
notify():唤醒共享当前同一把锁的线程其中之一;
notifyAll():唤醒共享当前同一把锁的所有线程。
需求:实现1与2交替出现
/**
* @author RuiMing Lin
* @date 2020-03-13 21:03
*/
public class Communication {
public static Object lock = new Object(); // 定义一把锁
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
MyThread2 myThread2 = new MyThread2();
myThread1.start();
myThread2.start();
}
}
// 定义通讯类一
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (Communication.lock){ //互相通信的线程必须共享同一把锁
System.out.print(1);
Communication.lock.notify();
try {
Communication.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int j = 0; j < 10; j++) {
synchronized (Communication.lock){
System.out.print(2);
Communication.lock.notify();
try {
Communication.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
7、线程池
7.1、线程池介绍
线程池是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。这样做有三个好处:1.降低资源消耗,减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务;2. 提高响应速度,当任务到达时,任务可以不需要的等到线程创建就能立即执行;3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内 存,而把服务器累趴下。
7.2、线程池的使用import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author RuiMing Lin
* @date 2020-03-13 21:22
*/
public class Pool {
public static void main(String[] args) {
// 1.创建一个容量为5的线程池
ExecutorService service = Executors.newFixedThreadPool(5);
// 2.创建Runnable对象
MyRunnable runnable1 = new MyRunnable();
MyRunnable runnable2 = new MyRunnable();
MyRunnable runnable3 = new MyRunnable();
MyRunnable runnable4 = new MyRunnable();
MyRunnable runnable5 = new MyRunnable();
// 3.调用线程池方法
service.submit(runnable1);
service.submit(runnable2);
service.submit(runnable3);
service.submit(runnable4);
service.submit(runnable5);
// 4.无需关闭线程,run()结束后自动将线程归还给线程池
// 5.关闭线程池
service.shutdown();
}
}
有错误的地方敬请指出!觉得写得可以的话麻烦给个赞!欢迎大家评论区或者私信交流!