多线程的作用:一个时间点,同时做多个事情
(一个时间点,同时执行多行代码)
new 一个Thread类的子类
public class Main {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run () {
System.out.println("这是一个子线程");
}
};
}
}
static class A extends Thread {
@Override
public void run () {
System.out.println("这是一个子线程");
}
}
new 一个 Runnable 子类,传入Thread构造函数中执行
public class Main {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("这是一个子线程");
}
};
Thread thread = new Thread(runnable, "线程1");
}
}
Thread thread = new Thread(runnable, “线程1”);
这里的第二个参数,是给该子线程起一个名字
启动线程使用 start() 方法
public class Main {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("这是一个子线程");
}
};
Thread thread = new Thread(runnable, "线程1");
//启动线程
thread.start();
}
}
线程的执行顺序
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("这是一个子线程");
}
};
Thread thread = new Thread(runnable, "线程1");
thread.start();
System.out.println("main线程执行");
上面这段代码,执行顺序是:
main线程的打印语句执行的概率大于子线程打印语句执行的概率
因为创建子线程比较耗时
1.public static Thread currentThread()
返回当前正在执行的线程对象的引用
2.public static void yield()
线程让步
即让当前线程由运行状态转变为就绪状态
3.public static void sleep(long millis) throws InterruptedException
使当前正在执行的线程在指定的毫秒数内休眠(暂时停止执行)
public class Main {
//标志位
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
while (!flag) {
System.out.println("这是一个子线程");
Thread.sleep(5000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable, "线程1");
thread.start();
Thread.sleep(3000);
flag = true;
System.out.println("main线程执行");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
while (Thread.currentThread().isInterrupted() == false) {
System.out.println("这是一个子线程");
}
}
};
Thread thread = new Thread(runnable, "线程1");
thread.start();
Thread.sleep(1000);
thread.interrupt();
System.out.println("main线程执行");
}
}
Thread类中有一个中断标志位,通过调用 interrupt() 函数就会让中断标志位变成 true,此时调用 isInterrupted() 方法获取中断标志位的值,通过条件判断使线程中断
通过调用 join 方法
public class Main {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
}
};
Thread thread = new Thread(runnable, "线程1");
thread.start();
thread.join();
System.out.println("main线程执行");
}
}
执行结果
0
1
2
3
4
main线程执行
在等待子线程运行时,main线程,会卡在join()方法的调用处,直到子线程结束后,main线程才会向下运行
当然join也有参数
public final void join(long millis) throws InterruptedException
等待线程多少毫秒,到了时间后就不等待继续执行了,如果在时间之内子线程运行结束,那么就直接向下运行
public static enum State {
NEW, //创建
RUNNABLE, //可运行
BLOCKED, //阻塞
WAITING, //等待
TIMED_WAITING,//超时等待
TERMINATED; //终止
private State() {
}
}
上面是 Thread.State 枚举类
里面是线程的六个状态
由于多线程之间可以相互共享资源(变量),所以会出现一些问题,这些问题就是线程安全问题
不安全的原因:多个线程对同一个变量的操作
如:两个线程同时修改同一个变量的值,这就会导致变量的值不会出现我们预期的结果
public class Main {
private static int x = 0;
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
x++;
}
}
};
Thread thread1 = new Thread(runnable, "线程1");
Thread thread2 = new Thread(runnable, "线程2");
thread1.start();
thread2.start();
while (Thread.activeCount() > 1)
Thread.yield();
System.out.println(x);
}
}
上面代码的结果每次都不一样,有的时候是20000,有的时候不是20000,这就是线程不安全引起的原因
不安全的原因之一是:不具有原子性
原子性是指:多行代码执行时,是不可再分的
举个例子,就好比你打开你的笔记本电脑,先翻开笔记本,再按电源键,这两个事情必须连在一起执行,不能分开,原子性也类似,对变量进行操作,必须一次性完成,不可分
加锁共享变量
举个简单的例子:
共享变量就好比打印机,线程好比人,两个人同时使用一个打印机,肯定不行,会导致两个人要打印的内容打印到同一张纸上,为了防止这个现象的发生,那就要规定一个打印机在被一个人使用的时候,另一个人不能使用,这就相当于使用打印机的人给打印机加了锁,自己在使用时,其他人是不能使用的,对于线程和共享变量,也是这个道理
多个线程访问共享变量,最终表现为:
一个线程加锁,操作变量,释放锁,其他线程加锁失败,需要等待(等待可以是一直等着,也可以是执行其他代码,过一会再回来再尝试加锁)
这就好比一个人正在用打印机,你也想用,但是已经被占了,要么你就排队,排在他后面,等着他用完你用,此时你只能等着,做不了其他事;要么你先做其他事情,过一会来看一次,看一看打印机有人用没,如果有人用,就继续做其他事,没人用就使用打印机
锁,在java层面是一个对象,多个线程加锁,是对同一个对象加锁,解锁也是
使用方法:
public class Main {
private static int x = 0;
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
add();
}
}
};
Thread thread1 = new Thread(runnable, "线程1");
Thread thread2 = new Thread(runnable, "线程2");
thread1.start();
thread2.start();
while (Thread.activeCount() > 1)
Thread.yield();
System.out.println(x);
}
private synchronized static void add() {
x++;
}
}
add() 方法是静态方法
public class Main {
private static int x = 0;
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
increase();
}
}
private synchronized void increase() {
x++;
}
};
Thread thread1 = new Thread(runnable, "线程1");
Thread thread2 = new Thread(runnable, "线程2");
thread1.start();
thread2.start();
while (Thread.activeCount() > 1)
Thread.yield();
System.out.println(x);
}
}
increase() 方法是实例方法
public class Main {
private static int x = 0;
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
synchronized (o) {
x++;
}
}
}
Object o = new Object();
};
Thread thread1 = new Thread(runnable, "线程1");
Thread thread2 = new Thread(runnable, "线程2");
thread1.start();
thread2.start();
while (Thread.activeCount() > 1)
Thread.yield();
System.out.println(x);
}
}
具体用法:
synchronized (锁对象) {
共享变量操作;
}
当然也可以对一个锁反复加锁
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
synchronized (o) {
synchronized (o) {
synchronized (o) {
synchronized (o) {
x++;
}
}
}
}
}
}
Object o = new Object();
};
不需要加锁即可保证线程安全性
用于保证了原子性的变量
读取变量值的操作:原子性保证了
修改变量值的操作:变量值不依赖共享变量,才保证了原子性
如:用常量给共享变量赋值,算保证了原子性
保证了线程的安全情况下,要尽可能的提高效率,这就需要我们加锁的时候要注意了,只加锁访问共享变量的代码,不访问共享变量的代码不要加锁
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
synchronized (o) {
System.out.println("子线程操作变量");
x++;
}
}
}
Object o = new Object();
};
上面这段代码,实际上
System.out.println(“子线程操作变量”);
不需要加锁
举个例子,就好比打印机在一个房间内,房间内有饮水机等其他设备,你在使用打印机的时候,就不需要对整个房间加锁,你使用的是打印机,并不是房间内的其他设备,万一有人想喝水了,还得等你用完打印机,这就很影响其他人,线程之间也是
所以加锁一定要锁对代码