关于多线程的一些小知识
线程的生命周期
- 新建:new出一个实现Runnable接口或者继承Thread类的对象
- 就绪:调用对象的start方法,等待cpu分配内存,注意,这时候cpu还没有分给你内存
- 运行:cpu给你分配了内存,开始实现run方法
- 阻塞:在这个线程运行的时候有其他线程插入进来,或者使这个线程暂停
- 死亡:线程全部运行结束或者报错,这个线程完全停止
用图片来感受一下线程的生命周期
多线程的安全问题
例子:三个窗口一个出售100张门票
-
出现问题的原因:当a线程在操作此车票,出现了阻塞或还没有操作完成,b线程也来操作这张车票,就会出现线程的安全问题
-
如何解决?当a线程在操作这张车票时,其他线程不允许参与进来,必须要等a线程操作完之后下一个线程才可以参与进来,并且只允许参与一个线程
-
在Java中如何解决?通过同步机制,来解决线程安全问题
-
好处:解决了代码的安全问题
局限性:在同步代码块中相当于单线程,只有一个线程可以操作,其他线程等待,效率低
-
方法一:同步代码块
-
synchronized(同步监视器){
需要被同步的代码
}
-
操作共享数据的代码,就时需要被同步的代码
-
共享数据:多个线程操作同一个变量,在例子中,车票就是共享数据
-
同步监视器:俗称:锁,任何一个类的对象都可以充当同步监视器
要求:多个线程必须要共用同一把锁
说明:在实现Runnable接口中的锁,可以考虑有this来代替
在继承Thread类的锁,可以考虑同当前类的对象来代替
通过代码来看一下
class Windows implements Runnable{ private int ticket = 100; Object obj = new Object(); //随便定义一个类的对象作为同步监视器 @Override public void run() { while (true){ synchronized (obj) { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket); ticket--; } else break; } } } } public class WindowsDemo01 { public static void main(String[] args) { Windows w = new Windows(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("线程1:"); t2.setName("线程2:"); t3.setName("线程3:"); t1.start(); t2.start(); t3.start(); } }
-
-
-
方法二:同步方法
-
修饰符 synchronized void 方法名(){ //同步监视器默认时this
需要被同步的代码
}
- 注意点:同步方法也需要同步监视器和共享数据,只是同步方法里不显示的显示同步监视器
- 在非静态的同步方法中,同步监视器默认为this
- 在静态的同步方法中,同步监视器默认为当前类本身
通过代码来看一下
实现Runnable接口
//同步方法默认同步监视器为this,所以使用实现Runnable的方式可以直接使用 class Windows3 implements Runnable{ private int ticket = 100; @Override public void run() { while (ticket > 0) { show(); } } private synchronized void show(){ if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket); ticket--; } } } public class WindowsDemo03 { public static void main(String[] args) { Windows3 w = new Windows3(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("线程1:"); t2.setName("线程2:"); t3.setName("线程3:"); t1.start(); t2.start(); t3.start(); } }
继承Thread类
/** * @Author YellowStar * @Date 2021/7/1 8:42 */ class Window4 extends Thread{ private static int ticket = 100; static Object obj = new Object(); @Override public void run() { while (ticket > 0){ show(); } } private static synchronized void show(){ //静态的同步方法,同步监视器默认为当前类 if (ticket > 0) { try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket); ticket--; } } } public class WindowsDemo04 { public static void main(String[] args) { Window4 w1 = new Window4(); Window4 w2 = new Window4(); Window4 w3 = new Window4(); w1.setName("线程1:"); w2.setName("线程2:"); w3.setName("线程3:"); w1.start(); w2.start(); w3.start(); } }
-
-
关于单例模式中的懒汉式的线程安全问题怎么解决?
class Singleton1{ //1.将构造方法私有化,防止外部修改 private Singleton1(){} //2.先创建一个对象,但不将他实例化 private static Singleton1 singleton1 = null; //3.等到外部需要调用的时候再将他实例化 //需要注意的是懒汉式这样子写,遇到多线程的时候,线程是不安全的,需要加上同步代码块 public static Singleton1 getSingleton1(){ synchronized (Singleton1.class) { if (singleton1 == null) { singleton1 = new Singleton1(); } return singleton1; } } } public class test { public static void main(String[] args) { Singleton1 s3 = Singleton1.getSingleton1(); Singleton1 s4= Singleton1.getSingleton1(); System.out.println(s3 == s4); } }
-
方法三:Lock锁
- 实例化一个ReentrantLock的对象,默认传值为false
- 调用加锁方法lock()
- 调用解锁方法unlock()
Lock与synchronized同步机制的异同
相同点:都可以处理多线程的安全问题
不同点:synchronized是非公平锁,lock(true)就可以实现公平锁
synchronized在执行完同步代码以后,自动释放同步监视器
lock需要手动上锁,手动解锁
通过代码来看一下
package com.yellowstar.demo01;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author YellowStar
* @Date 2021/7/1 11:11
*/
class Window05 implements Runnable{
private static int tickte = 100;
//1.实例化ReentrantLock对象,默认传值为false
//如果传参为true的话,会根据线程先到的时间进行排序,即不会出现线程1抢到票下一张票还是线程1抢到的
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//2.调用加锁方法lock(),执行lock方法后立即执行try-finally
lock.lock();
try {
if (tickte > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + tickte);
tickte--;
} else break;
}finally {
//3.调用解锁方法unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window05 w = new Window05();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
多线程的死锁问题
-
死锁:不同的线程分别占用对方所需要的同步资源不放弃,都在等待对方放弃自己的同步资源,就会形成死锁
-
说明:1.死锁状况出现时,系统不会报错不会出异常,不会有任何反应,只是线程处于阻塞状态,无法继续
2.在使用同步机制时,尽量避免死锁的出现
-
如何处理?
- 专门的算法、原则
- 尽量减少同步资源的定义
- 避免嵌套同步机制
用代码来演示一下出现死锁的现象
package com.yellowstar.demo01;
/**
* 多线程中出现死锁的问题
*
* @Author YellowStar
* @Date 2021/7/1 10:39
*/
public class test01 {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
多线程的通信
- 涉及到的方法:wait() notify() notifyAll()
- wait():让当前线程进入阻塞状态,并且执行解锁操作,释放同步监视器
- notify():释放一个阻塞的线程,如果有多个线程处于阻塞状态,则释放优先级最高的那个
- notifyAll():释放所有阻塞的线程
- 说明
- 以上三个方法只能存在于synchronized同步机制中,lock不适用
- 以上三个方法的调用者为synchronized同步机制中的同步监视器
- 以上三个方法均定义在Java.lang.Object类中
- 写个小例子:打印1-100的数字,要求线程a和线程b交替输出
package com.yellowstar.demo02;
import java.util.concurrent.locks.ReentrantLock;
/**
* 打印1-100的数字,要求线程1打印一次,线程2打印一次,交替输出
*
* @Author YellowStar
* @Date 2021/7/1 14:31
*/
class Print implements Runnable{
private static int sum = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
//解除优先级最高的一个线程的阻塞状态
notify();
if (sum <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":输出:" + sum);
sum++;
try {
//让当前线程进入阻塞状态,并释放同步监视器
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else break;
}
}
}
}
public class PrintTest {
public static void main(String[] args) {
Print p = new Print();
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
sleep()和wait()的区别
- 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
- 调用的要求不同,sleep()在任何需要的地方都可以调用,wait()必须在synchronized同步机制中
- sleep()不会释放同步监视器,wait()则会释放同步监视器
- sleep()会自动关闭阻塞状态,例如时间到了,而wait()需要手动关闭阻塞状态