程序、进程、线程的基本概念
程序
是为了完成特定的任务,用某种语言编写的一组指令的集合。即指一段特定的静态的代码。
进程
正在运行中的程序,(程序的一次执行过程),加载在内存中,操作系统会为进程分配内存空间,是动态(交换数据)的。
线程
进程进一步划分为线程,是程序内部的一条执行路径。并行的去执行多条线程,称之为多线程。
(新建一条线程:主程序新开一条线程,并行的执行)
进程和线程的关系:一个进程可能会有多个线程,多个线程共享进程的内存资源。
线程的创建和使用
1. 声明一个类,继承Thread类,重写run()方法
/**
* 多线程的创建
* 方式一
* 继承 Thread
* 重写 run方法
* 调用 start方法启动
*
* 例子:遍历100以内的偶数
*/
//1. 继承 Thread 类
class MyThread extends Thread{
//2. 重写 run 方法
@Override
public void run() {
for (int i = 1; i <= 100; i++){
if(i % 2 == 0){
System.out.println(i+Thread.currentThread().getName());
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3. 创建对象
MyThread t1 = new MyThread();
//4. 开始线程
t1.start();
}
}
调用start()方法会自动调用该线程的run()方法
问题1 :那么能否不调用satrt()方法,直接去调用run()方法呢?
不可以!!!!
直接调用run()方法时,实际上还是main线程
Thread.currentThread().getName() 可以使用这个方法获取线程名字测试一下
问题2 : 能使用start方法,再创建一个线程吗?
不可以!!!!!
一个线程只能开启一次,二次调用 start 则会报 java.lang.IllegalThreadStateException 异常
Thread 的常见方法
/**
* Thread 的常用方法
* 1. start() 启动线程
* 2. run() 当前线程要执行的操作
* 3. getName() 获取线程的名字
* 4. currentThread() 静态方法,返回当前执行的线程
* 5. setName() 设置线程的名字
* 6. yield() 释放cpu执行权
* 7. join() 线程a中调用线程b的join方法,a进入阻塞状态,直到线程b执行完毕,然后a继续执行
* 8. stop() 强制线程结束,已过时
* 9. sleep() 让当前线程阻塞等待多少秒
*/
线程的优先级
getPriority() 获取优先级
setPriority() 设置优先级
优先级:::
Thread.MAX_PRIORITY 10
Thread.NORM_PRIORITY 5 默认情况下
Thread.MAX_PRIORITY 1
我们设置的优先级只是我们想要某些线程先执行,java会给他的资源更多一些,但是cpu其实也是有可能去调度低的线程。
2. 实现Runnable接口
/**
* 创建多线程的方式二:实现Runnable 接口
* 1. 创建一个实现Runnable接口的类
* 2. 实现类去实现Runnable接口的抽象方法: run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器,创建Thread类的对象
* 5. 调用start()
*/
class MThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
public class RunnableTest {
public static void main(String[] args) {
MThread t1 = new MThread();
//调用的是当前线程的run --》(但是调用了Runnable类型的run方法)
new Thread(t1).start();
}
}
两种方法的比较
Thread类其实也实现了Runnable接口,所以比较推荐第二种,同时第二种方式也打破了extends的单继承性
线程的生命周期
Thread.State 的变量指代表线程的状态
New:新建,当一个Thread类或其子类的对象被创建时,新生的线程对象处于新建状态
就绪,当线程被satrt()后,将进入线程队列等待cpu的调用
运行,当cpu调用该线程的时候
阻塞,当该线程被人为挂起或执行输入输出操作时,中断自己的执行,让出cpu控制权
死亡,线程完成了自己的任务,或被强制结束,或异常退出后进入死亡状态
线程的一些常用的方法示例
线程停止 stop
不推荐使用Thread 类的stop()方式来进行线程停止,在java8版本就已经表示出要被废弃了
最好是等待线程自己停止,如有需要我们可以自己设置一个标记位来让线程停止
public class ThreadTest implements Runnable{
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("thread run...." + i++);
}
}
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
ThreadTest threadTest = new ThreadTest();
new Thread(threadTest).start();
for (int i = 0; i < 999; i++) {
System.out.println("main ..." + i);
if (i == 900) {
threadTest.stop();
System.out.println("threadTest stop..........");
}
}
}
}
线程休眠 sleep
sleep参数以毫秒为单位,可以用来模拟延时之类的,调用sleep后,线程进入指定时间的阻塞状态,sleep时间到达之后线程会进入就绪态。
如果当前线程有锁的话,sleep并不会释放锁。
线程礼让 yield
该方法可以释放cpu,重新进入就绪态,注意是就绪态不是阻塞态!!(就是谦让一下,我现在抢到cpu了,但我谦让一下,我退出来,咱们再抢一次)。
值得注意的是礼让不一定成功,这确实得看cpu的心情,有可能该线程 yield后,又会被cpu调度。
线程插队 join
如果当前cpu正在调度线程A,在A中调用B的join方法。这个时候线程B会进入cpu,A进入阻塞状态,直到B执行完毕,A被唤醒。
举个例子就会明白
public class JoinTest implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程vip来了:" + i);
}
}
public static void main(String[] args) throws InterruptedException {
JoinTest joinTest = new JoinTest();
Thread thread = new Thread(joinTest);
thread.start();
for (int i = 0; i < 200; i++) {
System.out.println("main:"+i);
if (i == 50) {
//这个时候主线程会进入阻塞态,直到调用join的线程执行完毕
thread.join();
System.out.println("joinTest thread is died");
}
}
}
}
线程的同步
线程的安全问题与解决
问题:卖票过程中出现重票、错票—>出现了线程安全问题
问题出现的原因:当某个线程在操作车票的过程中,尚未操作完成,其他线程参与进来也操作车票
解决:在一个线程a在操作ticket的时候,其他线程不能参与进来,直到线程a操作完ticket之后,其他线程才可以操作ticket。
即使线程a出现了阻塞,也不能被改变
在java中我们通过同步机制来解决线程安全问题
方式一:同步代码块
/*
synchronized(同步监视器){
需要被同步的代码
}
说明:
1。操作共享数据的代码,即为被同步的代码
2。共享数据:多个线程共同操作的数据
3。同步监视器,俗称:锁。任何一个类的对象都可以充当锁
要求:多个线程必须共用同一个锁(同一个对象)
*/
public class WindowRunnableTest {
public static void main(String[] args) {
WindowRunnable windowRunnable = new WindowRunnable();
Thread window1 = new Thread(windowRunnable);
Thread window2 = new Thread(windowRunnable);
Thread window3 = new Thread(windowRunnable);
window1.setName("窗口1");
window2.setName("窗口2");
window3.setName("窗口3");
window1.start();
window2.start();
window3.start();
}
}
class WindowRunnable implements Runnable{
private int ticket = 100;
//锁
Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (this){//synchronized (obj) 或者synchronized (WindowRunnable.class){ 都可以
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+": 卖票,票号为"+ticket);
ticket--;
}else{
break;
}
}
}
}
}
方式二:同步方法
将被同步的代码提取出来封装成一个方法
//同步方法中,同步监视器为this
//如果同步方法被static修饰 同步监视器就为类名.class
private synchronized void show(){
if(ticket > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+": 卖票,票号为"+ticket);
ticket--;
}
}
好处:解决了线程的安全问题
坏处:运行慢,变成了串行,操作同步代码时,只能有一个线程参与,相当于单线程的过程,有局限性
懒汉式的线程安全问题
线程的通信
线程间有的时候是需要通信的,比如说最经典的生产者和消费者问题,消费者看到商品里没有货物的时候
,就不再去购买货物了,生产者看到商店货物满了的时候,就不再去生产商品了,假设现在有两个线程,
一个是生产者线程,一个是消费者线程,那么谁来告诉他们商品的情况呢?
这个时候我们就需要使用,线程中的三个通信方法来进行通信了
- wait():一旦执行此方法,线程进入阻塞,并且释放同步监视器
- notify():一旦执行此方法,会唤醒一个线程,如果有多个线程,唤醒优先级最高的那个
- notifyAll():一旦执行此方法,会唤醒所有的线程。
- 说明,这三个方法,只能在同步代码块,或者同步方法当中,Lock有自己的wait
- 必须由同一个同步监视器调用 wait 和 notify,否则会报异常 java.lang.IllegalMonitorStateException
/**
* 两个线程循环打印 1-100
*/
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread thread1 = new Thread(number);
Thread thread2 = new Thread(number);
thread1.start();
thread2.start();
}
}
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while (true){
synchronized (Number.class) {
//唤醒一个线程
Number.class.notify();
//唤醒所有线程
//Number.class.notifyAll();
if (number <= 100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"打印:"+number);
number++;
try {
//从就绪态进入阻塞态,而且会释放同步锁,只能由其他线程唤醒。
Number.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
生产者消费者问题
/**
* 生产者和消费者问题
*/
public class ProductorAndCustomer {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer Consumer = new Consumer(clerk);
Thread thread = new Thread(producer);
Thread thread1 = new Thread(Consumer);
thread.setName("生产者1");
thread1.setName("消费者1");
thread.start();
thread1.start();
}
}
class Clerk{
private int productCount = 0;
//生产产品,此时同步监视器是 this
public synchronized void produceProduct() {
if(productCount < 20){
productCount++;
System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
notify();
}else {
try {
System.out.println("仓库满20个");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumeProduct() {
if(productCount > 0){
System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
productCount--;
notify();
}else {
try {
System.out.println("没有商品了");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产者
class Producer implements Runnable{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":开始生产");
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
//消费者
class Consumer implements Runnable{
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":开始消费");
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
jdk 5.0 新增的创建多线程的方式
Callable
创建多线程的第三种方式,实现Callable 接口
这中方式较之前实现Runnable 的方式相比。多了返回值
但是需要知道,Callable并不是Thread 的子类,所以,想要启动的话,需要借助 Future接口
Future 接口中有唯一的实现类 FutureTask,同时FutureTask也实现了Runnable接口
Callable 的优点
- 可以返回值
- 可以抛出异常,被外面的操作捕获
- 支持范型
public class CallableNewThread {
public static void main(String[] args) {
NewThread newThread = new NewThread();
FutureTask<Integer> futureTask = new FutureTask<>(newThread);
new Thread(futureTask).start();
try {
//futuretask 的get方法会去调用->构造参数传入的对象的call(回调)方法
Integer sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class NewThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++){
if (i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;//自动装箱,变为Integer类型
}
}
线程池
线程池的好处
- 提高响应速度,减少了创建线程的时间
- 降低资源消耗,重复利用线程池中的线程
- 便于线程管理
public class ThreadPool {
public static void main(String[] args) {
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(() -> {
for (int i = 0; i < 100; i++){
if (i % 2 == 0)
System.out.println(Thread.currentThread().getName()+":打印-" + i);
}
});//适合使用Runnable,没有返回值
Future<Integer> future = service.submit(() -> {
int sum = 0;
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
sum += i;
System.out.println(Thread.currentThread().getName() + ":打印-" + i);
}
}
return sum;
});//适合使用于Callable,返回Future
Integer integer = null;
try {
integer = future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(integer);
//关闭线程池
service.shutdown();
}
}
如果需要对线程池进行设置的话,不能直接使用ExecuteService 对象,因为这是一个接口
我们可以将它进行强转(向下转型),找它的实现类,ThreadPollExecutor,然后进行相关设置