JAVA多线程
一、进程和线程的区别和关系。
多线程:就是指应用程序有多条执行路径。
* 进程:正在运行的应用程序。
* 线程:进程的执行单元,一条执行路径。
*
* 举例:
* 迅雷下载,360管理界面。
*
* 我们如何实现多线程程序呢?
* 由于线程是依赖于进程存在,而进程是由操作系统创建的,并且java语言是不能直接调用操作系统的功能。
* 所以,为了方便对多线程程序的时候,java就提供了线程的API对应的类。
*
* 线程类:Thread
*
* 通过查看API,我们到创建线程的方式有2种。
* 方式1:继承Thread类。
* A:定义一个类继承Thread类。
* B:子类要重写Thread类的run()方法。
* C:让线程启动并执行。
* 注意:启动线程并执行,是不能使用run()方法的。这个时候,必须使用另外的一个方法。
* 这个方法名是start()。这个方法其实做了两件事情,第一,让线程启动。第二,自动调用run()方法。
现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。
比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。
线程总是属于某个进程,进程中的多个线程共享进程的内存。
“同时”执行是人的感觉,在线程之间实际上轮换执行。
二、JAVA中的多线程
在Java中,“线程”指两件不同的事情:
1、java.lang.Thread类的一个实例;
2、线程的执行。
使用java.lang.Thread类或者java.lang Runnable接口编写代码来定义、实例化和启动新线程。
一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。
Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。
一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。
一旦创建一个新的线程,就产生一个新的调用栈。
线程总体分两类:用户线程和守候线程。
三、Thread多线程的方法:
public final void setName(String name)
public final void getName(String name)
public void start()
public static void sleep(long millis)
public static void yield()
以下是以售卖电影票为例子,举例代码体现。
package cn.itcast_03;
public class RunnableTest {
public static void main(String[] args) {
TicketRunnable tr = new TicketRunnable();
Thread t1 = new Thread(tr);
Thread t2 = new Thread(tr);
Thread t3 = new Thread(tr);
Thread t4 = new Thread(tr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t4.setName("窗口4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
package cn.itcast_03;
public class TicketRunnable implements Runnable {
private int tickets = 200;
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "张票");
}
}
}
}
四、线程中的锁
以上代码虽然实现了模拟售票的内容,但是存在着一定的问题,就是会出现卖出负数票的情况。
那么,产生的原因是什么呢?
* 线程的随机性和延迟性,导致了线程访问共享数据出现了问题。
* 怎么解决呢?
* 在多线程程序中,一般来说,不会是所有的代码都有问题,所以,我们只需要找到那些可能出问题的代码,
* 我把可能出问题的代码, 跟包起来,看做是一个整体,只有这个整体完毕,别人才能继续访问。
* 为了安全,把这个整体包起来的代码加个锁。给锁起来。
*
* 怎么找?(多线程出问题的判断条件)
* A:看有没有共享数据
* B:看对共享数据的操作是不是多条语句
* C:看是不是在多线程程序中
*
* 找到后,就把同时满足这三个条件的代码给锁起来。
*
* 怎么锁?
* java提供了一种锁机制方式:
* synchronized(锁对象)
* {
* 需要被锁的代码;
* }
* 锁对象:怎么做呢?反正不知道,所以,我们就用Object类的实例。
*
* 注意:多个线程必须使用同一把锁。
代码如下:
package cn.itcast_05;
public class RunnableTest {
public static void main(String[] args) {
TicketRunnable tr = new TicketRunnable();
Thread t1 = new Thread(tr);
Thread t2 = new Thread(tr);
Thread t3 = new Thread(tr);
Thread t4 = new Thread(tr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t4.setName("窗口4");
t2.start();
t3.start();
t4.start();
t1.start();
}
}
public class TicketRunnable implements Runnable {
private int tickets = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
//tickets=1
//t1,t2,t3,t4都来了
//假设t1抢到,看到是开的状态
synchronized (obj) {
//锁对象的状态:开,关
//t1进来了,给外界了一个关的状态。
if (tickets > 0) {
try {
//t1睡了
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ (tickets--) + "张票");
}
} //t1把状态修改为开
}
}
}
五、多线程中的错误输出解决
我们先分析问题什么有这个问题,然后再解决问题。
* 由于线程的随机性产生的问题。
*
* 然后我们在回到上午给大家的那个总结:
* A:是否有共享数据
* B:是否有多条语句操作共享数据
* C:是否在多线程环境中
*
* 出问题的原因我们知道了,那么怎么解决呢?
* 用同步解决。
* 我们把setStudent给加同步了,但是,还是有问题。原因是需要对多个线程都要加同步。
* 我给两个操作都加同步了,还是出问题,这一次的原因是:两种操作的锁对象不一致。
* 当我们把所有的操作都加同步,并且锁用同一个以后,我们的数据就没有问题了。
*
* 这个时候,我们看到了一个不好的现象,就是同一个数据一大片一大片的输出,原因就是每一次获取到CPU的执行权,就足够输出很多次数据。
* 我想,能不能输出一个林青霞,然后输出一个刘意,然后再输出一个林青霞,再输出一个刘意...
*
* 如何能够做到这个效果呢?
*
* 做这个效果前,我们先分析一个问题,问题是:我们的程序其实是有一点点逻辑问题的。
* 如果没有设置数据,就直接获取数据了。这应该不符合逻辑。
* 正常逻辑是:
* 针对输出:
* 判断是否有数据,如果有就输出。否则,就等待设置数据。
* 针对设置:
* 判断是否有数据,如果有就等待输出数据,否则,就“输出”(写错了,应该是否则就设置数据)。
* 等待唤醒机制:
* 抓人小游戏。
*
* Object类中:
* wait() 让线程处于等待状态
* notify() 唤醒等待的线程
public class StudentTest {
public static void main(String[] args) {
Student s = new Student();// 资源类
SetStudent ss = new SetStudent(s);
GetStudent gs = new GetStudent(s);
Thread t1 = new Thread(ss);
Thread t2 = new Thread(gs);
t1.start();
t2.start();
}
}
package cn.itcast_10;
public class Student {
String name;
int age;
// flag作为数据标识存在,true表示有数据,false表示没有数据。
boolean flag = false;
}
package cn.itcast_10;
public class SetStudent implements Runnable {
private Student s;
public SetStudent(Student s) {
this.s = s;
}
@Override
public void run() {
// t1过来了
int x = 0;
while (true) {
synchronized (s) {
//如果有数据,就等待。
if(s.flag){
try {
s.wait();//t1等待了,
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (x % 2 == 0) {
s.name = "林青霞"; // t2抢到;
s.age = 26;
} else {
s.name = "刘意";// t2抢到;
s.age = 29;
}
//修改标记
s.flag = true;
s.notify();//唤醒了t2,这个时候,可能是t1继续,或者t2抢到。
}
x++;// x=1,t2抢到;x=2,t2抢到;
}
}
}
package cn.itcast_10;
public class GetStudent implements Runnable {
private Student s;
public GetStudent(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if(!s.flag){
try {
s.wait();//t2等待了。wait()的线程,被唤醒后,继续执行。wait()方法出现后,对应的线程就释放了锁对象
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "***" + s.age);
//修改标记
s.flag = false;
s.notify(); //t1被唤醒,但是不一定会立马执行。
}
}
}
}