多线程技术
- 线程的休眠
- 线程堵塞
- 线程中断
- jvm里线程的状态
- 守护线程与用户线程
- 线程安全问题
- 解决线程不安全的三种加锁机制
- 隐式锁
- 同步代码块
- 同步方法
- 显式锁
- 公平锁与非公平锁
- 线程死锁
线程的休眠
public class Demo {
public static void main(String[] args) throws InterruptedException {
// 线程休眠 sleep
for (int i = 0; i < 10; i++) {
System.out.println(i);
// 设置休眠时间(毫秒数)
Thread.sleep(1000);
}
}
}
线程堵塞
需要耗时间的操作都会导致线程堵塞(例如:文件读取、等待用户输入)
线程中断
一个线程是一个独立的执行路径,它是否中断应该由其自身决定,如果通过外部干预(即被非该线程的代码)中断,会导致线程的资源没有释放,占用内存
应该通过对线程打标记(调用interrupt()方法),进行线程中断操作,通过对线程打标记,线程会在有sleep和wait操作中时刻检查是否有interrupt终断标记,如果有会抛出interruptedException,然后在catch中输入各种释放资源的操作和return,资源释放,线程会自动关闭
public class Demo {
public static void main(String[] args) {
// 线程的中断
// 一个线程是一个独立的执行路径,它是否应该结束应该有其自身决定
Thread t1 = new Thread(new MyRunnable());
t1.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 给子线程添加中断标记,子线程会时刻关注这个标记
t1.interrupt();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//e.printStackTrace();
System.out.println("发现中断标记,就是不死亡");
// 如果我们要中途结束该线程使用return
// 这种中断死亡需要在return语句上方进行自我释放资源的操作
return;
}
}
}
}
}
jvm里线程的状态
线程状态有六种
(1) 新创建状态new
(2) 运行状态tunnable
(运行状态也不是一直能运行的,也可能会出现线程抢不到cpu的时间片,从而进入了堵塞状态)
(3) 堵塞状态blocked(在线程安全模式下需要排队)
(4) 休眠状态waiting(需要唤醒才能重新进人runnable状态)
(5) 指定休眠时间状态timed_waiting(如果没有主动去唤醒,经过指定时间也会自己醒来进入runnable)
(6) 结束状态(死亡状态,没有用户线程执行)Terminated
守护线程与用户线程
守护线程(需要自己设置线程为守护线程):
当所有用户线程死亡,守护线程就会自动死亡
用户线程:所有创建的线程都是用户线程
线程安全问题
不设置锁的线程都是会产生数据不安全问题的
这里我用卖票问题来模拟线程不安全问题
public class Demo {
public static void main(String[] args) {
// 创建任务对象
Runnable run = new Ticket();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
t1.start();
t2.start();
t3.start();
}
static class Ticket implements Runnable{
//票数
private int count =5;
private Object o = new Object();
@Override
public void run() {
while(count > 0){
// 卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票"+count);
}
}
}
}
这里我们能看到出现了不合理的票数为负数的数据
解决线程不安全的三种加锁机制
线程同步(排队机制)
加锁机制都是牺牲了处理数据的效率来保证数据安全
隐式锁
同步代码块和同步方法为隐式锁
同步代码块
**同步代码块:**使用Synchronized方法
Synchronized(锁对象){
放入需要排队执行的代码(需要线程安全的代码)
}
该方法需要传入一个锁对象,锁对象可以是任何对象(内部底层原理),每个线程在进入Synchronized方法的代码块时都要时刻关注锁对象是否上了锁,有线程在执行代码块时,其他线程必须要排队,等上一个线程执行完
需要注意全部线程都要关注同一把锁,而不是各自一把锁
(例如三个人需要进入衣帽间换衣服,只有一个衣帽间,如果三人都有各自的锁对象,意味着三个人都有锁钥匙,这就不能达到一个人换衣服其他人排队的情况)
public class Demo {
public static void main(String[] args) {
// 创建任务对象
Runnable run = new Ticket();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
t1.start();
t2.start();
t3.start();
}
static class Ticket implements Runnable{
//票数
private int count =10;
private Object o = new Object();
@Override
public void run() {
while(true){
synchronized (o){
if (count > 0) {
// 卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票"+count);
}else {
return;
}
}
}
}
}
}
没有出现票数为负数的情况,数据安全
同步方法
同步方法: Synchronized修饰方法
锁的是方法而不是代码块了,而这个锁是调用这个方法的对象(没有办法改变),如果同步方法是static静态修饰的,锁就是这个方法的类名.class(字节码文件)
多个线程要执行同一个任务才会排队,执行各自的任务是不需要排队的
当一个类中有使用同一个锁对象(this)的同步代码块和同步方法,意味着同一把锁,锁了两个门,所以当执行同步代码块的时候,其他线程也执行不了同步方法因为两个都是同时被同一个锁锁死的
public class Demo {
public static void main(String[] args) {
// 创建任务对象
Runnable run = new Ticket();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
t1.start();
t2.start();
t3.start();
}
static class Ticket implements Runnable {
//票数
private int count = 5;
@Override
public void run() {
while (true) {
boolean flag = sale();
// !一元运算符,当flag为true时,!flag为false;当flag为false时,!flag为true
if (!flag) {
break;
}
}
}
public synchronized boolean sale() {
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
return true;
}
return false;
}
}
}
显式锁
使用Lock类
public class Demo {
public static void main(String[] args) {
// 创建任务对象
Runnable run = new Ticket();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
t1.start();
t2.start();
t3.start();
}
static class Ticket implements Runnable {
//票数
private int count = 5;
private Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
// 进来方法关锁
l.lock();
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
// 退出开锁
l.unlock();
} else {
// 退出开锁
l.unlock();
break;
}
}
}
}
}
显示锁与隐式锁的区别
一、层面不同
synchronized:Java中的关键字,是由JVM来维护的,是JVM层面的锁。
synchronized底层是通过monitorenter进行加锁
底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。并且只有在同步块或同步方法中,JVM才会调用monitory对象的,才可以调用wait/notify等方)
通过monitorexit来退出锁
Lock:是JDK5以后才出现的具体的类。使用lock是调用对应的API,是API层面的锁。
lock是通过调用对应的API方法来获取锁和释放锁的。
二、使用方式不同
synchronized
程序能够自动获取锁和释放锁。Sync是由系统维护的,如果非逻辑问题的话话,不会出现死锁。
Lock
需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。
手动获取锁方法:lock.lock()。释放锁:unlock方法。并且需要配合tyr/finaly语句块来完成。
三、等待是否可中断
synchronized
不可中断,除非抛出异常或者正常运行完成。
Lock
可以中断的。
中断方式:
调用设置超时方法tryLock(long timeout ,timeUnit unit)
调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
四、加锁的时候是否可以设置成公平锁
synchronized
只能为非公平锁。
lock:两者都可以的。默认是非公平锁。
在其构造方法的时候可以传入Boolean值。true:公平锁、false:非公平锁
五、锁绑定多个条件来condition
synchronized
不能精确唤醒线程。要么随机唤醒一个线程;要么是唤醒所有等待的线程。
Lock
用来实现分组唤醒需要唤醒的线程,可以精确的唤醒。
六、性能区别
synchronized
托管给JVM执行,Java1.5中,由于需要调用操作接口,可能导致加锁消耗时间过长,与Lock性比性能低。1.6以后,语义定义更加清晰,有适应自旋、锁粗化、锁消除、轻量级锁、偏向锁等,可进行许多优化,性能提高了,与Lock差不多。
Lock
java写的控制锁的代码,性能高。
公平锁与非公平锁
隐式锁和显示锁都是不公平锁,锁一旦解开哪个线程抢到就那个线程执行
公平锁:全部线程都排队,轮到哪个线程就哪个线程执行
通过对显示锁传入fair参数,参数为true则是公平锁
线程死锁
在任何有可能导致锁产生的方法里不要再调用另一个方法导致另一个锁产生
public class Demo {
public static void main(String[] args) {
// 线程死锁
Culprit c = new Culprit();
Police p = new Police();
new MyThread(c,p).start();
c.say(p);
}
static class MyThread extends Thread{
private Culprit c;
private Police p;
public MyThread(Culprit c, Police p){
this.c = c;
this.p = p;
}
@Override
public void run(){
p.say(c);
}
}
static class Culprit{
public synchronized void say(Police p){
System.out.println("罪犯:你放了我,我放人质");
p.fun();
}
public synchronized void fun(){
System.out.println("罪犯被放走,罪犯放了人质");
}
}
// 警察
static class Police{
public synchronized void say(Culprit c){
System.out.println("警察:你放了人质,我放你");
c.fun();
}
public synchronized void fun(){
System.out.println("人质被解救了,罪犯逃跑了");
}
}
}
警匪双方在对质的时候双方都不肯让步,匪不放人质,警察不相信匪放人质,导致局面僵在那里了
两个线程都被锁死了,都等不到对方做完双方的任务
剩下的大佬补充(乐观锁和悲观锁)
https://blog.csdn.net/predawnlove/article/details/107124571