线程
线程与进程
- 进程:正在运行的应用程序。
在某一个时间点上,cpu(单核)只能只能执行一个进程。比如我们现在听歌(网易云)那还我使用IDEA敲代码,你的感觉肯定是这个两个事情是同时进行。是因为CPU可以在多个进程间进行一个高速的切换,人耳跟眼睛,是感觉不出来,多进程的意义,提高CPU的利用率。
- 线程:线程依赖于进程,进程开启后,会执行很多的任务,那么每个任务,我们就称之为线程。
word进程——比如有两个任务,一个任务,是进行文字的输入,一个任务是自动保存。文字输入和自动保存是不是在同时进行。线程他是具有随机性的,他会取抢占CPU的执行权。谁抢到,CPU就会在某一个时刻执行对应线程。
并行与并发
- 并行:多个任务同时执行。
- 并发:多个任务交替执行,只是交替的间隔,很短,让你感觉在同时执行。
多线程
多线程:是指从软件或者硬件上实现多个线程并发执行的技术,是一种并发的思想。
单线程开发中的main方法就属于一个线程,叫做主线程。当我们开启其他线程和主线程同时执行是,就是多线程的思想。
Thread类
Thread 类是 Java 的线程类,通过它可以开启一个线程。
- 构造方法
方法 | 功能 |
---|---|
Thread() | 分配新的 Thread 对象 |
Thread(Runnable target) | 分配新的 Thread 对象 |
Thread(Runnable target, String name) | 分配新的 Thread 对象,并定义线程名 |
Thread(String name) | 分配新的 Thread 对象 ,并定义线程名 |
- 成员方法
方法 | 功能 |
---|---|
static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
String getName() | 返回该线程的名称 |
int getPriority() | 返回线程的优先级 |
void interrupt() | 中断线程 |
static boolean interrupted() | 测试当前线程是否已经中断 |
boolean isDaemon() | 测试该线程是否为守护线程 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
开启线程
- 方式一: 使用Java提供的 Thread 类,来创建线程
- 定义一个类,继承Thread类
- .重写Thread类中的run方法
- 创建此类的对象
- 开启线程(开启线程并不是调用run()方法,而是调用start() 开启线程,由线程去调用run()去执行run方法里面的代码)
public class 方式一 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
System.out.println("线程执行了");
}
}
- 方式二:通过实现 Runnable 接口,来创建线程
- 创建一个类,实现Runnable接口,重写接口中的run方法
- 创建Thread类对象,将Runnable接口的子类对象传递进来
- 调用start()方法开启线程
public class 方式二 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
System.out.println("线程执行了");
}
}
- 方式三:通过实现 Callable 接口,来创建线程
- 创建一个类,实现Callable接口,重写接口中的call方法
- 创建FutureTask类对象,将Callable接口的子类对象传递进来
- 创建Thread类对象,将FutureTask类对象传递进来
- 调用start()方法开启线程
public class 方式三 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
Integer i = futureTask.get();
System.out.println("1-100总和是"+i);
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("线程执行了");
int sum=0;
for (int i = 1; i <= 100; i++) {
sum+=i;
}
return sum;
}
}
实现Callable接口开启线程,在线程执行完后可以获取一个结果并用FutrueTask对象调取get()方法获得。
线程安全问题
- 案例:模拟某电影院目前正在上映贺岁大片,共有100张票,3个售票窗口同时售票
public class 多线程售票 {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable, "窗口1");
Thread thread2 = new Thread(runnable, "窗口2");
Thread thread3 = new Thread(runnable, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
public class MyRunnable implements Runnable {
private static int ticket=100;
@Override
public void run() {
while (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"剩余"+(ticket--)+"张票");
}
}
}
输出结果:
会发现出现相同票数的情况,零票甚至负数票的情况。
- 相同票:是由于原子性所导致的 ticket- - 他不是一个原子性的操作 (原子性:不可再分割)。在此线程运行完成后,ticket - -还未操作完成另一线程就开始运行,就会导致多个线程共享的数据出现被污染。
- 零票或负数票:由于线程的随机性所导致。
条件
出现线程安全问题的条件(缺一不可):
- 要是多线程环境
- 多个线程在并发操作共享数据
- 有多条语句在操作这个共享数据
解决
所以我们要解决线程安全问题,就从安全问题发生的条件入手:
使用同步代码块来解决线程安全问题
同步代码块可以看做一块锁,在一个进程操作共享数据时,将其它进程锁住,知道一个进程操作完毕,再放下一个进程运行。
synchronized (锁对象){
放置有可能出现线程安全问题代码
}
public class MyRunnable implements Runnable {
private static int ticket=100;
Object object=new Object();
@Override
public void run() {
synchronized (object) {
while (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"剩余"+(ticket--)+"张票");
}
}
}
}
这时共享数据被污染的安全问题迎刃而解。
锁对象
- 当我们使用同步代码块时,锁对象可为任意对象:
- 当我们使用同步方法时,锁对象为this:
- 当我们使用静态同步方法时,需要使用类锁,锁对象为当前类的字节码类型: