1.进程和线程
1.1概述
(1)进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
(2)线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
(3)多进程的意义:单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),
所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
(4)多进程的意义:多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
1.2多线程程序实现
多线程的一些方法:
方法 | 功能 |
---|---|
public void run() | run方法中封装应该是必须被线程执行的代码 |
public void start() | 开启线程 |
public final void setDaemon(Daemon(boolean on) | 把该线程标记为守护线程 |
public final void stop() | 停止线程的运行 |
public void interrupt() | 清除线程阻塞的状态 |
public final void join() | 等待该线程执行完毕了以后,其他线程才能再次执行 |
public final int getPriority() | 获取线程的优先级 |
public final void setPriority(int newPriority) | 设置线程的优先级 |
public static void sleep(long millis) | 线程休眠 |
public static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
(1)多线程程序实现方法1:
Thread
1.直接继承Thread类
2.重写run方法
3.创建Thread的子类对象,启动子线程
正确开启线程的方法是 调用start()方法,线程开启后,会去调用run()方法;
public class MyTest {
public static void main(String[] args) {
//获取的是当前的线程对象
Thread thread = Thread.currentThread();
String name = thread.getName();
System.out.println(name);
MyThread th = new MyThread();
//设置线程的名称
th.setName("线程一");
th.start();
}
}
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//this.getName() 获取当前线程的名称
System.out.println(this.getName() + ":" + i);
// System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
(2)多线程程序实现方法2:
Runnable
1.创建线程的另一种方法是声明实现 Runnable 接口的类。
2.该类然后实现 run 方法。然后可以分配该类的实例,
3.在创建 Thread 时作为一个参数来传递并启动
public class MyTest {
public static void main(String[] args) {
//Runnable任务
MyRunnable myRunnable = new MyRunnable();
Thread th = new Thread(myRunnable, "线程A");
th.start();
}
}
public class MyRunnable implements Runnable {
//让线程来执行的方法
@Override
public void run() {
int sum = 0;
for (int i = 0; i < 100; i++) {
// System.out.println(Thread.currentThread().getName() + ":" + i);
sum += i;
}
}
}
实现Runnable接口的好处:因为Java是单继承,实现这个接口后还可以去继承其他的类或接口,避免由于Java单继承带来的局限性。
(3)多线程程序实现方法3:
Callable
- 创建一个类实现Callable 接口
- 创建一个FutureTask类将Callable接口的子类对象作为参数传进去
- 创建Thread类, 将FutureTask对象作为参数传进去
- 开启线程
public class MyTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(myCallable);
Thread thread = new Thread(task);
thread.start();
Integer integer = task.get();
System.out.println(integer);
//Runnable run() 没有返回值 没有抛出异常
//Callable call() 有返回值的 可以抛出异常
}
}
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(200);
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}
Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
2.线程安全问题
出现线程安全问提要满足下面这些条件
1.是不是多线程。
2.是不是有共享数据
3.是不是有多条语句再操作这个共享变量 读 改 写
解决办法:我们可以加锁,也就是 使用同步代码块来解决
锁对象:任意一个Java对象就可以用来做一把锁 注意多个线程要用同一个锁对象
synchronized (锁对象){
需要同步代码,你认为有可能出现安全问题代码
}
我们来看以下的案例:
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
如果我们不加锁,看看下面的代码。
public class MyTest {
public static void main(String[] args) {
SellTiketRunnable sellTiketRunnable = new SellTiketRunnable();
Thread th1 = new Thread(sellTiketRunnable, "窗口1");
Thread th2 = new Thread(sellTiketRunnable, "窗口2");
Thread th3 = new Thread(sellTiketRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
public class SellTiketRunnable implements Runnable {
//共享数据
static int num = 100;
@Override
public void run() {
while (true) {
if (num > 0) {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售:" + (num--) + "张票");
}
}
}
}
结果:
我们看到都有负数票和0票的出现,有时候还会有相同票。
解决办法就是加锁,就上保证只有一个线程去操作这个共享变量。
加锁的方式有两种:
锁对象:任意一个Java对象就可以用来做一把锁 注意多个线程要用同一个锁对象
synchronized (锁对象){
需要同步代码,你认为有可能出现安全问题代码
}
我们来实现那个需求:
public class MyTest {
public static void main(String[] args) {
SellTiketRunnable sellTiketRunnable = new SellTiketRunnable();
Thread th1 = new Thread(sellTiketRunnable, "窗口1");
Thread th2 = new Thread(sellTiketRunnable, "窗口2");
Thread th3 = new Thread(sellTiketRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
public class SellTiketRunnable implements Runnable {
//共享数据
static int num = 100;
@Override
public void run() {
while (true) {
sell();
}
}
public synchronized void sell() {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售:" + (num--) + "张票");
}
}
Lock锁:虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
void lock() 加锁
void unlock() 释放锁
public class MyTest {
public static void main(String[] args) {
SellTiketRunnable sellTiketRunnable = new SellTiketRunnable();
Thread th1 = new Thread(sellTiketRunnable, "窗口1");
Thread th2 = new Thread(sellTiketRunnable, "窗口2");
Thread th3 = new Thread(sellTiketRunnable, "窗口3");
th1.start();
th2.start();
th3.start();
}
}
public class SellTiketRunnable implements Runnable {
//共享数据
static int num = 1000;
Lock lock = new ReentrantLock();
@Override
//th1 th2 th3
public void run() {
while (true) {
//加锁
lock.lock();
try {
// th1
if (num > 0) { //1
//模拟网络延迟
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(1 / 0);
System.out.println(Thread.currentThread().getName() + "正在出售:" + (num--) + "张票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//不管有没有出现异常都要释放锁
lock.unlock();
}
}
}
}
3.死锁
死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态
public interface LockUtils {
//定义两把锁对象
Object objA = new Object();
Object objB = new Object();
}
public class MyThread extends Thread {
private boolean flag;
public MyThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (LockUtils.objA) {
System.out.println("true线程进来了持有 objA锁");
synchronized (LockUtils.objB) {
System.out.println("true线程进来了持有 objB锁");
}
}
} else {
synchronized (LockUtils.objB) {
System.out.println("false线程进来了持有 objB锁");
synchronized (LockUtils.objA) {
System.out.println("false线程进来了持有 objA锁");
}
}
}
}
}
public class MyTest {
public static void main(String[] args) {
MyThread th1 = new MyThread(false);
MyThread th2 = new MyThread(true);
th1.start();
th2.start();
}
}