进程:
正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和资源。
线程:
是进程的单个顺序控制流,或者说就是一个单独执行的路径
一个进程如果只有一条执行路径,称之为单线程
一个进程如果有多条执行路径,称之为多线程
线程是包含在进程中。 举例:扫雷,360杀毒软件,百度网盘
了解三个关键词:
1、串行,指的是一个程序中所有的任务都是按照先后顺序执行的,在前一个任务还没有处理完的情况下,是不会进行处理下一个任务的
举例:理发店只有一个理发师,很多人去理发,先等前面的人理完发,再轮到后面的人。
2、并行,指的是将任务分给不同的处理器去处理,每一个处理器中再进行串行处理。
举例:火车站上有很多个窗口,多个窗口同时卖票,但是针对于一个窗口来说,一个人的一个人的去卖票
3、并发,实质上是一种现象,并发需要处理器的支持,比如在出库一个任务的时候操作系统可以进行调用再处理其他的任务,不论串行还是并行
都需要操作系统的支持并发。假设喝水是一个任务,每个火车站售票员,再售票的同时也能喝水,这就表示支持并发。
JVM启动的时候是单线程还是多线程呢?
多线程:
main(主线程)
垃圾回收线程
所以在JVM启动的时候,最低要求要有两个线程存在,所以JVM启动的时候是多线程的。
创建线程的第一种方式:继承Thread类
1、创建一个自定义类继承Thread类
2、这个类要重写Thread类中的run方法
为什么是run()方法呢?
当线程启动之后,执行的代码逻辑仅是run()方法的代码逻辑
3、根据这个类创建线程对象
4、启动线程
面试题:调用start()与调用run()的区别
run()方法中仅仅是封装了被线程执行的代码,但是呢,直接调用run()与调用普通的方法方式没有任何区别
start()方法的调用,首先单独启动了一个线程,然后再由JVM去调用该线程类中的run()方法
//MyThread1类
public class MyThread1 extends Thread {
@Override
public void run() {
//写我们要线程执行的逻辑代码
//一般来说,被线程执行的逻辑代码都是比较耗时的,为了模拟这里的耗时,我这里使用循环打印
for (int i=0;i<200;i++){
System.out.println(i);
}
}
}
//MyThreadDemo2测试类
public class MyThreadDemo2 {
public static void main(String[] args) {
//每创建一个对象,相当于创建一个新的线程对象
// MyThread1 myThread1 = new MyThread1();
//启动线程
// myThread1.run();
// myThread1.run();
//单纯的调用run方法仅仅表示的是一个对象调用普通的方法,所以这里依旧是单线程程序
//要想看到多线程的效果,就必须换一种方式启动线程 start()
//当一个线程对象启动多次的时候,报错:IllegalThreadStateException 非法的线程状态异常
// myThread1.start();
System.out.println("========================");
MyThread1 myThread1 = new MyThread1();
MyThread1 myThread11 = new MyThread1();
myThread1.start();
myThread11.start();
}
}
如何给线程设置名字呢?
通过构造方法给线程起名字:
Thread(String name) 分配一个新的 Thread对象。
如何获取线程的名字呢?
public final String getName()返回此线程的名称
MyThread2类
public class MyThread2 extends Thread{
public MyThread2() {
}
public MyThread2(String name) {
super(name);
}
@Override
public void run() {
for (int i=1;i<=200;i++){
System.out.println(getName()+":"+i);
}
}
}
测试类
//MyThreadDemo3
public class MyThreadDemo3 {
public static void main(String[] args) {
//创建线程对象
//通过构造方法给线程起名字
//由于我们要模拟多线程环境,所以创建线程的个数为2个或2个以上
// MyThread2 t1 = new MyThread2("明");
// MyThread2 t2 = new MyThread2("王");
// t1.start();
t2.start();
// MyThread2 t1 = new MyThread2();
// MyThread2 t2 = new MyThread2();
// MyThread2 t3 = new MyThread2();
// //public final void setName(String name)将此线程的名称更改为等于参数name 。
// t1.setName("明");
// t2.setName("王");
// t3.setName("红");
// t1.start();
// t2.start();
// t3.start();
//public static Thread currentThread()返回对当前正在执行的线程对象的引用。
System.out.println(Thread.currentThread().getName());
}
}
我们在前几个代码中都没有设置优先级,我们猜测一定会有一个默认的优先级。
默认的优先级是多少呢?
获取线程优先级的方法: public final int getPriority()返回此线程的优先级。
设置线程优先级的方法: public final void setPriority(int newPriority)更改此线程的优先级。
public final static int MAX_PRIORITY = 10; 线程可以拥有的最大优先级。
public final static int MIN_PRIORITY = 1; 线程可以拥有的最小优先级。
总结:
1、线程的默认优先级是5
2、线程优先级的范围是1-10
3、线程优先级高仅仅表示的获取CPU时间片的机率会高一些,但是呢,并不是绝对会获取到
//MyPriorityThread类
public class MyPriorityThread extends Thread {
@Override
public void run() {
for (int i=1;i<=200;i++){
System.out.println(getName()+":"+i );
}
}
}
测试类
public class ThreadPriorityDemo {
public static void main(String[] args) {
MyPriorityThread t1 = new MyPriorityThread();
MyPriorityThread t2 = new MyPriorityThread();
MyPriorityThread t3 = new MyPriorityThread();
//获取t1,t2,t3线程的优先级
// System.out.println(t1.getPriority());
// System.out.println(t2.getPriority());
// System.out.println(t3.getPriority());
//设置优先级
//IllegalArgumentException 非法的参数传入
// t1.setPriority(100);
//newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY
t1.setPriority(10);
t2.setPriority(1);
t3.setPriority(2);
t1.setName("小明");
t2.setName("小红");
t3.setName("小蓝");
t1.start();
t2.start();
t3.start();
}
}
演示休眠线程的效果
//MySleepThread
public class MySleepThread extends Thread{
@Override
public void run() {
for (int i=1;i<=200;i++){
System.out.println(getName()+":"+i);
//加入休眠的方法
//public static void sleep(long millis)
//停1秒钟 1秒=100毫秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}//这里要求加入try catch方法
}
}
}
//ThreadSleepDemo
public class ThreadSleepDemo {
public static void main(String[] args) {
MySleepThread t1 = new MySleepThread();
MySleepThread t2 = new MySleepThread();
MySleepThread t3 = new MySleepThread();
t1.setName("小明");
t2.setName("小白");
t3.setName("小红");
t1.start();
t2.start();
t3.start();
}
}
//ThreadJoinDemo
public class ThreadJoinDemo {
public static void main(String[] args) {
MyJoinThread t1 = new MyJoinThread();
MyJoinThread t2 = new MyJoinThread();
MyJoinThread t3 = new MyJoinThread();
t1.setName("小米");
t2.setName("小蓝");
t3.setName("小黄");
t1.start();
try {//这里最好用try catch
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start();
}
}
礼让线程:
public static void yield()
暂停当前正在执行的线程对象,并执行其他线程
它的作用是为了让多个线程之间运行的时候看起来更加和谐,但是呢,并不能保证多个线程一人一次。
//MyYieldThread
public class MyYieldThread extends Thread {
@Override
public void run() {
for (int i=1;i<=200;i++){
System.out.println(getName()+":"+i);
Thread.yield();
}
}
}
ThreadYieldDemo类
public class ThreadYieldDemo {
public static void main(String[] args) {
MyYieldThread t1 = new MyYieldThread();
MyYieldThread t2 = new MyYieldThread();
MyYieldThread t3 = new MyYieldThread();
t1.setName("小米");
t2.setName("小即");
t3.setName("小好");
t1.start();
t2.start();
t3.start();
}
}
MyStopThread 类
import java.util.Date;
public class MyStopThread extends Thread{
@Override
public void run() {
System.out.println("开始执行时间:"+new Date());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束时间:"+new Date());
}
}
//ThreadStopDemo类
public class ThreadStopDemo {
public static void main(String[] args) {
MyStopThread t1 = new MyStopThread();
t1.start();
try {//这里表示的是对MyStopThread里的内容睡眠10秒在睡到第3秒的时候进行打断
Thread.sleep(3000);
// t1.stop();//强制打断睡眠,程序停止 该方法已经弃用
t1.interrupt();//打断睡眠,run方法后面的代码继续执行,执行完后,抛出异常
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
多线程的实现方案二:实现Runnable接口
1、自定义一个类实现Runnable接口
2、实现run()方法
3、创建自定义类对象
4、创建Thread线程对象,将自定义的对象作为参数传递到构造方法中
// MyRunnable1类
public class MyRunnable1 implements Runnable {
@Override
public void run() {
for (int i=1;i<=200;i++){
//由于Runnable接口中没有getName()方法,所以这里无法使用获取线程对象名字
//间接调用,我们可以先获取当前线程的对象,然后再调用Thread类中getName()方法
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
//MyRunnableDemo1类
public class MyRunnableDemo1 {
public static void main(String[] args) {
//创建自定义类对象
MyRunnable1 t1 = new MyRunnable1();
MyRunnable1 t2 = new MyRunnable1();
MyRunnable1 t3 = new MyRunnable1();
//创建线程对象
Thread thread = new Thread(t1);
Thread thread1 = new Thread(t2);
Thread thread2 = new Thread(t3);
thread.setName("小明");
thread1.setName("小红");
thread2.setName("小兰");
thread.start();
thread1.start();
thread2.start();
}
}
某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
两种方式实现
继承Thread类
//TicketWinodw1类
public class TicketWinodw1 extends Thread{
private static int tickets=100;
@Override
public void run() {
//在run方法中定义100张票是有问题的
//每个线程都会执行run方法,这样的话,相当于每个线程都有自己的那100张票
//所以,我们将100张票定义在这里是不合适的。
// int tickets = 100;
while (true){
if (tickets>0){
System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
}
}
}
}
//SellTicketDemo1类
public class SellTicketDemo1 {
public static void main(String[] args) {
TicketWinodw1 winodw1 = new TicketWinodw1();
TicketWinodw1 winodw2 = new TicketWinodw1();
TicketWinodw1 winodw3 = new TicketWinodw1();
winodw1.setName("窗口1");
winodw2.setName("窗口2");
winodw3.setName("窗口3");
winodw1.start();
winodw2.start();
winodw3.start();
}
}
实现Runnable接口
//TicketWindow2类
public class TicketWindow2 implements Runnable{
private static int ticket=100;
@Override
public void run() {
while (true){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"张票");
}
}
}
}
//SellTicketDemo2类
public class SellTicketDemo2 {
public static void main(String[] args) {
TicketWindow2 ticketWindow2 = new TicketWindow2();
Thread t1 = new Thread(ticketWindow2);
Thread t2 = new Thread(ticketWindow2);
Thread t3 = new Thread(ticketWindow2);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
改进
//TicketWindow3类
public class TicketWindow3 implements Runnable {
private static int ticket=100;
@Override
public void run() {
while (true){ //为了模拟更加真实的售票场景,我们加入延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"张票");
//窗口1正在出售第100张票
//窗口2正在出售第100张票
//出现重复的原因是,CPU的操作是原子性,
//由于tickets--是两步操作,先赋值输出再--
//当窗口1赋值输出后,还没有来得及--,这时候窗口2也执行到了这一步,此时tickets的值还没有发生变化
//所以出现了相同的票卖了多次。
//理想状态下:
//窗口1正在出售第100张票
//窗口2正在出售第99张票
//出现第0张票的现象解释:
//两个或3个窗口同时在tickets的值为1的时候,都进入到if之中,都会进行一次睡眠
//当第一个窗口睡眠结束,也打印结束,此时的tickets的值从1变成0
//所以当后面的线程睡眠结束打印结果是0
//负数的来源是当tickets的值为1的时候,三个线程都进入if语句可能会造成的现象
}
}
}
}
//SellTicketDemo3类
public class SellTicketDemo3 {
public static void main(String[] args) {
TicketWindow3 ticketWindow2 = new TicketWindow3();
TicketWindow3 ticketWindow3 = new TicketWindow3();
TicketWindow3 ticketWindow4 = new TicketWindow3();
Thread t1 = new Thread(ticketWindow2);
Thread t2 = new Thread(ticketWindow3);
Thread t3 = new Thread(ticketWindow4);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
上一个案例加入了延迟操作,出现的问题,其实就称之为:线程安全问题。
要想去解决这个问题,就要搞清楚哪些原因导致的问题出现:
(三点总结出是否会出现线程安全问题,缺一不可)
1、是否存在多线程环境
2、是否存在共享数据/共享变量
3、是否有多条语句操作着共享数据/共享变量
回想一下上一个案例是否满足判读线程安全问题的条件:
1、是否存在多线程环境 存在,有3个窗口线程
2、是否存在共享数据/共享变量 存在,共享数据是100张票
3、是否有多条语句操作着共享数据/共享变量 是
三个条件都满足,由此可见,我们上一个案例出现问题是一个正常的现象,因为它同时满足以上3个条件
如何解决这些问题呢?
第1,2条件是我们无法改变的,我们只能想办法改变第3个条件,只要其中一个不满足,就不会发生线程安全问题。
解决问题的思想:
要是有一个办法可以将多条语句操作共享数据的代码给包成一个整体,在某个线程执行的时候,别的线程进不来就可以了。
直到某个线程执行完一次run方法后,其他线程才能进入执行。
Java提供了一个机制给我们使用,来解决线程安全的问题:同步安全机制
解决方案一:同步代码块
语句格式:
格式:
synchronized(对象){需要同步的代码;}
1、这里的对象是什么呢?
随便创建一个对象试试
2、需要同步的代码又是哪些呢?
多条语句操作共享数据的代码