Java多线程入门
1. 并发与并行
- 并发 : 指两个或者多个事件在同一时间段运行
- 并行 : 指两个或者多个事件在同一时刻发生(同时发生)
并发指的是多个事件交替执行 --> 一个CPU在执行
并行两个或者多个事件同时运行 --> 多个CPU在执行
因此并行的速度比并发快
2. 线程与进程
-
进程: 是指一个内存中运行的应用程序,每个进程都有一个独立的内存运行空间
-
线程: 线程是进程中的一个执行单元, 负责运行程序的执行, 一个进程中至少有一个线程, 也可以有多个线程
3. 线程调度
-
分时调度: 所有线程轮流使用CPU的使用权,平均分配每个线程的占用CPU使用时间
-
抢占式调度: 优先让优先级高的线程使用CPU, 如果优先级相同, 则同一优先级的线程随机分配
4. 创建线程类
4.1 主线程
程序在main方法中运行的线程称之为主线程 ==> main Thread
4.2 创建线程的方法
- 将声明为 Thread的子类, 该子类方法重写Thread父类的run方法
// 1.创建子线程 --> childThread
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run: ==>"+ i);
}
}
}
// 在主线程中调用子线程 ==> mainThread
public class Demo01Thread {
public static void main(String[] args) {
// 定义线程只有开启多线程
MyThread mt = new MyThread();
mt.start();
for (int i = 0; i < 20; i++) {
System.out.println("main: ==> "+ i);
} // 此处的应该是随机打印, 于此可以证明线程之间互不影响
System.out.println(Thread.currentThread().getName()); // 输出 main
}
}
- 声明实现Runable接口的类, 然后该类实现run 方法
// 1. Runnable接口的实现类 RunnableImpl
public class Demo01Runnable {
public static void main(String[] args) {
// 实例化 Runnable的实现类
RunnableImpl run = new RunnableImpl();
// 将实例置入 Thread类的构造函数中
Thread t = new Thread(run);
// 调用Thread实例的start方法;
t.start();
System.out.println(t.getName()); // Thread-0
}
}
// 2. 调用 Runnable 接口的实现类 MyRunnable
public class Demo01Runnable {
public static void main(String[] args) {
// 实例化 Runnable的实现类
RunnableImpl run = new RunnableImpl();
// 将实例置入 Thread类的构造函数中
Thread t = new Thread(run);
// 调用Thread实例的start方法;
t.start();
System.out.println(t.getName()); // Thread-0
}
}
总结:
- 避免了单继承的局限性
即一个类只能继承一个类
实现Runnable接口还可以实现其他的类, 实现其他的接口 - 增强了程序的可扩展性(即: 解耦)
4. 匿名内部类创建子线程
匿名内部的作用: 把子类创建父类, 重写父类方法, 创建子类对象的过程合成一步实现
public class Demo01ClassThread {
public static void main(String[] args) {
// 父类的线程是 Thread
// new Thread().start()
// 使用 Thread创建
new Thread() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("ClassThread"+ i);
System.out.println(Thread.currentThread().getName());
}
}
}.start();
// 2. Runnable 实现
Runnable n1 = new Runnable(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName());
System.out.println("Runnable"+ i);
}
}
};
new Thread(n1).start();
// 上述代码可以简化为
new Thread(
new Runnable(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName());
System.out.println("Runnable"+ i);
}
}
}
).start();
}
}
5. 线程安全
多线程访问了共享的数据, 就会产生安全的问题
举个例子
// 1. 在Runnable接口实现类中
public class ThreadSafeRunnable 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);
ticket--;
}
}
}
}
// 2. 在 Runnable实现类的调用中
public class ThreadSafe {
public static void main(String[] args) {
ThreadSafeRunnable n1 = new ThreadSafeRunnable();
Thread t1 = new Thread(n1);
Thread t2 = new Thread(n1);
Thread t3 = new Thread(n1);
t1.start();
t2.start();
t3.start();
// 此时会出现 相同票数的情况
}
}
6. 线程安全的解决方案
6.1 同步代码块
// 1. 在Runnable的实现类中
public class ThreadSafeRunnable implements Runnable {
// 为了线程安全, 创建一个锁对象, 保重共享数据同步进行
// 创建一个锁对象 ==> 也叫同步锁, 对象锁
final Object obj = new Object();
private static int ticket = 100;
@Override
public void run() {
while (true) {
// 同步代码块
/*
当多线程代码 执行到同步代码块synchronized时, 会检查有没有锁对象
发现有, 就会获取锁对象, 进入同步执行中
当下一个线程遇到同步代码块时, 因为锁对象已经被上一个线程拿走, 此时该线程就会进入阻塞状态, 停止运行,
并等待锁对象从上一个代码块中释放出来
*/
synchronized (obj) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()+"当前剩余票数: "+ticket);
ticket--;
}
}
}
}
}
6.2 使用同步方法
使用 synchronized
修饰同步方法
public class SynchronizeMethod implements Runnable {
private static int ticket = 100;
// 把访问共享数据的代码抽取出来放到一个方法中, 并加上 synchronized 修饰符
public synchronized void payTicket() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()+ "2当前剩余票数: "+ticket);
ticket--;
}
}
@Override
public void run() {
while (true) {
payTicket();
}
}
}
// 2.
public class Demo01SynchronizeMethod {
public static void main(String[] args) {
Runnable n1 = new SynchronizeMethod();
Thread t1 = new Thread(n1);
Thread t2 = new Thread(n1);
Thread t3 = new Thread(n1);
t1.start();
t2.start();
t3.start();
}
}
6.3 静态方法
即用 synchronized 和 static 修饰的方法
值得注意的是:
因为静态方法只与类相关而不与对象相关, 所以this是指向类而不是实例化对象
public class SynchronizeMethod implements Runnable {
private static int ticket = 100;
// 静态方法 ==> synchronized 不能使用 this 对象锁
public static synchronized void payTicket() {
synchronized (Runnable.class) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()+ "2当前剩余票数: "+ticket);
ticket--;
}
}
}
@Override
public void run() {
while (true) {
payTicket();
}
}
}
6.4 Lock锁
即: 创建一个 ReentrantLock对象 实现了Lock接口ReentrantLock对象(在 Java.utils中实现, 可以调用实例)
在线程安全开始时 调用 lock方法, 结束时调用 unlock方法
// 需要导入java.util包
public class ThreadLock implements Runnable {
Lock l1 = new ReentrantLock();
private static int ticket = 100;
private static void payTicket() {
l1.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "当前剩余票数: ==> "+ ticket);
ticket--;
}
l1.unlock();
// 这么写有个好处, 运行到这一步无论过程是否正常处理, 线程锁都能被正常的释放掉, 不会阻塞后续线程的进行
}
@Override
public void run() {
while (true) {
payTicket();
}
}
}
网友评论