一、多线程
1、什么是多线程?
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
什么是线程什么是进程?
1)进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。
2)线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
进程可以理解为是一个身体;
线程就是身体的一些部分(手、脚);
1。单进程单线程:一个人在一个桌子上吃菜。
2。单进程多线程:多个人在同一个桌子上一起吃菜。
3。多进程单线程:多个人每个人在自己的桌子上吃菜。
什么是多线程什么是单线程
你一只手用筷子夹菜吃饭,就是单线程,如果你用两只手拿筷子夹菜吃饭就是多线程;
多线程能做什么?
1、多线程能够快速的处理大量数据(一个手写字和俩手写字);
2、多线程能模拟一些场景练习(并发场景)(秒杀、抢票);
3、多线程能把复杂的问题变成简单的问题(新手很容易出错);
2、创建多线程的三种方式
1、继承Thread
public class Test {
public static void main(String[] args) {
Th th = new Th();
th.start(); // 启动多线程
}
}
class Th extends Thread{
@Override
public void run() {
System.out.println("多线程~");
}
}
2、实现Runnble接口
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(new Ru());
thread.start();
}
}
class Ru implements Runnable{
@Override
public void run() {
System.out.println("多线程~");
}
}
3、创建Thread类
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程~");
}
}).start();
}
}
3、java多线程的5种状态
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
二、线程安全问题
同时满足一下两种条件时:
1、多个线程在操控共享数据。
2、操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
1、代码演示
例1:100根冰棍2个线程操控
public class Test {
public static void main(String[] args) {
Th th = new Th();
new Thread(th, "线程1").start();
new Thread(th, "线程2").start();
}
}
class Th implements Runnable{
// 冰棍数量
private int k = 100;
@Override
public void run() {
while (k > 0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
sell();
}
}
public void sell(){
if (k > 0){
System.out.println(Thread.currentThread().getName()+": 出售第"+(100-k+1)+"根冰棍");
k--;
}
}
}
2、分析结果
打印:
这里我们看到的并不是出售第1,2,3,4…,根冰棍;
线程1已经出售了第一根了,但是线程2也出售了第一根;
分析:
1、线程1还没有执行k-的时候;
2、线程2就已经执行了出售(打印)了;
3、导致出现了两次出售第1根冰棍
3、解决思路
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。
当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象)
{
需要被同步的代码 ;
}
这个对象一般称为同步锁。
同步的前提:同步中必须有多 个线程并使用同一个锁。
同步的好处:解决了线程的安全问题。
同步的弊端:相对降低了效率,因为同步外的线程的都会判断同步锁。
解决例1的线程安全问题代码:
public class Test {
public static void main(String[] args) {
Th th = new Th();
new Thread(th, "线程1").start();
new Thread(th, "线程2").start();
}
}
class Th implements Runnable{
// 冰棍数量
private int k = 100;
@Override
public void run() {
while (k > 0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
sell();
}
}
public void sell(){
synchronized(this){
if (k > 0){
System.out.println(Thread.currentThread().getName()+": 出售第"+(100-k+1)+"根冰棍");
k--;
}
}
}
}
结果:
===================================================
4、同步锁是什么?
同步函数使用的是this锁。
静态同步函数使用的是当前类的字节码文件。
同步函数和同步代码块的区别:
同步函数的锁是固定的this。
同步代码块的锁是任意的对象。
建议使用同步代码块。
同步静态函数定义方式:
private static synchronized void sell(){
if (k > 0){
System.out.println(Thread.currentThread().getName()+"出售第"+(100 - k + 1)+"票");
k--;
}
}
5、死锁常见问题
同步嵌套时,两个线程你拿了我的锁,我拿了你的锁,都不释放,造成死锁。
死锁代码:
public class Test {
public static void main(String[] args) throws InterruptedException {
Th th = new Th();
Thread thread = new Thread(th, "线程1");
Thread thread2 = new Thread(th, "线程2");
thread.start();
Thread.sleep(50);
th.bo = false;
thread2.start();
}
}
class Th implements Runnable{
// 冰棍数量
private int k = 100;
private Object object = new Object(); // object锁
public boolean bo = true;
@Override
public void run() {
if (bo){
while (k > 0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object){
sell();
}
}
}else{
while (k > 0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
sell2();
}
}
}
public void sell(){
synchronized (this){
if (k > 0){
System.out.println(Thread.currentThread().getName()+": 出售第"+(100-k+1)+"根冰棍");
k--;
}
}
}
public synchronized void sell2(){
synchronized (object){
if (k > 0){
System.out.println(Thread.currentThread().getName()+": 出售第"+(100-k+1)+"根冰棍");
k--;
}
}
}
}
打印结果:
出售到第5根冰棍时卡住了,造成了死锁;
分析:
1、线程1执行sell(),先使用object锁,后使用this锁;
2、线程2执行sell2(),先使用this锁,后时间object锁;
3、线程1使用了object锁,这时需要this锁;
4、线程2使用了this锁,这时需要object锁;
5、线程1需要this锁,线程2需要object锁,但是this锁被线程2占用没有释放,线程1占用object锁没有释放,两者需要的锁都被占用,造成死锁;