线程问题

线程的入门

在了解线程之前,首先明白什么是进程。

什么是进程:

进程是指运行中的应用程序,每个进程都会有自己独立的地址空间(内存空间)比如:浏览器,编译器,系统任务管理器等等。操作系统会给该进程分配独立的地址空间,当用户再次点击浏览器时,就又启动一个进程。用户每启动一个进程,操作系统就会给该进程分配一个独立的内存空间。

什么是线程:

线程可以理解成进程的寄生,一个进程可以由多个线程。线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但他可与同属一个进程的其他线程共享所拥有的全部资源,一个线程可以创建和撤销另一个线程,线程可以并发执行。

线程有三种基本状态:就绪,阻塞,运行。

线程的理解:

1、线程是轻量级进程。

2、线程没有独立的地址空间

3、线程由进程创建(寄生)

4、一个进程可以拥有多个线程------>多线程

5、线程的生命周期:

  新建状态,就绪状态,运行状态,阻塞状态,死亡状态。

线程的应用场景:

1、我们熟悉的Tomcat服务器内部就是使用多线程:

上百个客户访问同一个web应用,Tomcat接入后都是把后续的处理扔给一个新的线程来处理,这个新的线程就是servlet程序,比如doGetdoPost

如果不采用多线程,上百个用户同时访问一个web应用的时候,只能使用串行处理,那样就不实际。

2、异步处理:

异步处理也使用多线程,比如task a和task b要并行处理。但是CPU是多核的话,就可以让一个CPU执行有一个线程,如果只有一个CPU的话,底层是按照分时复用的原则,各个线程按照时间获取CPU资源。

3、javaweb应用中很少用到多线程,因为在开发过程中,多线程被框架实现了,需要自己实现的很少。

4、用的比较多的时网络应用程序。

线程的安全问题:

为什么又线程安全问题?

 

当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。

经典案例就是抢火车票的案例啦:

class ThreadTrain1 implements Runnable {

private int count = 100;

private static Object oj = new Object();

 

@Override

public void run() {

while (count > 0) {

try {

Thread.sleep(50);

} catch (Exception e) {

// TODO: handle exception

}

sale();

}

}

 

public void sale() {

// 前提 多线程进行使用、多个线程只能拿到一把锁。

// 保证只能让一个线程 在执行 缺点效率降低

// synchronized (oj) {

// if (count > 0) {

System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "");

count--;

// }

// }

}

}

 

public class ThreadDemo {

public static void main(String[] args) {

ThreadTrain1 threadTrain1 = new ThreadTrain1();

Thread t1 = new Thread(threadTrain1, "①号窗口");

Thread t2 = new Thread(threadTrain1, "②号窗口");

t1.start();

t2.start();

}

}

运行结果:

可以看到一号跟二号出现抢到同一张票,这显然是不可用的

线程安全的解决办法:

使用同步synchronized或者使用锁(lock)

为什么使用多线程同步或者锁就能解决线程安全问题?

答:因为多线程可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程执行,同步/锁内的代码执行完之后,才能让其他线程进来执行,一个一个执行,这样就可以解决线程不安全问题。多个线程共享一个资源,不会收到其他线程的干扰。

多线程同步代码块案例:

private static Object oj = new Object();   

public void sale() {

// 前提 多线程进行使用、多个线程只能拿到一把锁。

// 保证只能让一个线程 在执行 缺点效率降低

 synchronized (oj) {

if (count > 0) {

System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");

count--;

}

 }

}

也可以这样写:

public synchronized void sale() {

if (trainCount > 0) { 

try {

Thread.sleep(40);

} catch (Exception e) {

}

System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");

trainCount--;

}

}

 注意:使用同步代码块的时候一般都是使用当前类。静态方法时使用锁时当前类的字节码文件。

多线程死锁:

 简单理解:同步中嵌套同步,导致锁无法释放。

class ThreadTrain6 implements Runnable {

// 这是货票总票数,多个线程会同时共享资源

private int trainCount = 100;

public boolean flag = true;

private Object mutex = new Object();

 

@Override

public void run() {

if (flag) {

while (true) {

synchronized (mutex) {

// 锁(同步代码块)在什么时候释放? 代码执行完, 自动释放锁.

// 如果flag为true 先拿到 obj,在拿到this 锁、 才能执行。

// 如果flag为false先拿到this,在拿到obj锁,才能执行。

// 死锁解决办法:不要在同步中嵌套同步。

sale();

}

}

} else {

while (true) {

sale();

}

}

}

public synchronized void sale() {

synchronized (mutex) {

if (trainCount > 0) {

try {

Thread.sleep(40);

} catch (Exception e) {

 

}

System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");

trainCount--;

}

}

}

}

 

public class DeadlockThread {

 

public static void main(String[] args) throws InterruptedException {

 

ThreadTrain6 threadTrain = new ThreadTrain6(); // 定义 一个实例

Thread thread1 = new Thread(threadTrain, "一号窗口");

Thread thread2 = new Thread(threadTrain, "二号窗口");

thread1.start();

Thread.sleep(40);

threadTrain.flag = false;

thread2.start();

}

 

}

多线程的三大特性:

1、原子性

原子性是指不可分割,必须前后一致,比如去银行存取钱,A账户往B账户里转1000块钱,A账户里减去1000,B账户增加1000,前后不可以有分割。

2、可见性

多个线程访问同一个变量时,一个线程修改了该变量的值,其他线程能够立即看到修改的值

3、有序性

 程序执行的顺序按照代码的先后顺序执行。

Java内存模型:

共享内存模型指的就是Java内存模型(简称JMM)JMM决定一个线程对共享变量的写入时,对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。

 

posted @ 2019-01-05 11:08  空杯+空船  阅读(256)  评论(0编辑  收藏  举报