1.
什么是进程?
通过查看任务管理器,发现正在运行的程序就是一个进程!多进程有什么意义呢?
现在的计算机是一个多进程计算机,在去做一件事情的同时可以做另一件事情;提高CPU的使用率
我们在打游戏同时在听音乐,这两个进程是同时发生的吗?
打游戏------->开启了游戏进程
听音乐------>开启了音乐进程
不是同时在进行,感觉他们在同时进行,这个因为CPU在两者之间进行着高效的切换(抢占CPU的执行权)
2
什么是线程?
线程是依赖于进程存在的:把线程可以看作是进程中的某一个任务,比如迅雷软件,扫雷(开始--->玩的同时--->计时)CPU的执行权:具有随机性! (每一个线程在抢占CPU的执行权具有随机性)
单线程:程序的执行路径只有一条路径
多线程:程序的执行路径有多条
3.
并行和并发:同时的意思
前者是逻辑上的同时,指的是在同一个时间内(时间段)同时发生后者是物理上的同时,指的是在同一个时间点(10:30:30进行开抢:购物商城...)上同时发生
面试题:
jvm:java虚拟机是多线程的吗?
程序能够被执行:是因为里面main()方法,main()被Jvm所识别,启动Jvm,实质启动了
主线程(main方法所在的就是主线程),通过刚才这个代码:两个输出语句之间做了多次创建对象,
程序依然能够执行打印出来,java语言会自动启动垃圾回收器,----->垃圾回收线程,所有能够保证有时候不会内存溢出
java虚拟机是一个多线程,至少开启了两个线程:
第一个:main():主线程 Thread main
第二个:垃圾回收线程,保证及时的回收不用的对象
4.
创建新执行线程有两种方法。
一种方法是 将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。第一种实现多线程程序的开发步骤:
1:自定义一个类,该类继承自Thread类,
2:重写该类中的run()方法
3:主线程中去创建该类的对象
4:启动线程.
//1)自定义该类继承自了一个Thread类
//2):重写run()方法
//为什么要重写run()方法呢?
//该方法里面需要执行一些耗时的操作:循环语句.../sleep()
public class MyThread extends Thread{
@Override
public void run() {
for(int x = 0; x<10; x++){
System.out.print(x);
}
}
}
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();//启动线程:start()方法,调用start()其实是让Jvm去调用线程的run 方法()来实现执行多线程的一种随机性
m2.start();
}
如何获取线程的名称呢?
public final String getName()返回该线程的名称。
public final void setName(String name)改变线程名称,使之与参数 name 相同
public static Thread currentThread()返回对当前正在执行的线程对象的引用。
System.out.println(Thread.currentThread().getName());//main :获取当前正在发生的线程的名称
5.
线程里面常用的方法
1
public final void join():等待该线程终止
t1.join();//等待t1线程结束后 t2 t3 线程才会开始
2
public static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
public final void stop():强制停止执行
public void interrupt()中断线程。 中断当前线程的这种状态(打破了一种状态)
public static void main(String[] args) {
ThreadStop t = new ThreadStop();
t.start();
//t.stop();//直接结束线程 什么都不执行 就停止在这里
try {
Thread.sleep(1000);//睡眠了一秒 然后被中断了
t.interrupt();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
4.
public static void yield()暂停当前正在执行的线程对象,并执行其他线程。
线程的执行具有随机性,并且不能保证该线程永远暂停,可能抢占到了CPU的执行权就会执行它...
第二种
API的描述:
创建线程的另一种方法是声明实现 Runnable 接口的类
该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动
实现方式2的开发步骤:
1)自定义一个类MyRunnable(同一个资源)实现Runnable接口
2)然后实现Rannable里面的run方法:耗时的操作
3)在主线程中创建MyRuhnable类的对象
4)创建Thread类对象,将第三步的对象作为参数进行传递来启动线程
public static void main(String[] args) {
MyRunnable m = new MyRunnable();//创建MyRunnable对象 作为同一个资源
//创建线程对象
Thread t1 = new Thread(m,"线程一");
Thread t2 = new Thread(m,"线程二");
//Thread(Runnable target, String name)
t1.start();
t2.start();
}
6.
线程安全的检测条件:
1)看我们当前的环境是否属于多线程程序2)当前的多线程程序中是否有共享数据
3)是否多条语句对共性数据进行操作
如何让线程安全?
要实现线程安全,那么就必须改变第三个条件(前两者不能改变),
将对共享数据的操作的多条语句包装起来,怎么包装:java提供了一个同步机制 :sychronized(是一个关键字)
同步机制的使用:
sychronized(对象){
多条语句对共享数据进行操作;
}
同步机制:每一个线程只能使用同一个对象(sychronized当作一个锁:使用的是同一把锁)
可以是任意的对象(重点)
public class SeilTiker implements Runnable {
int ticket = 100;
Object obj = new Object();
@Override
public void run() {
while (true) {// 同步代码块
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (ticket--));
}
}
}
}
}
如果一个方法进来之后是同步代码块,那么该方法就可以定义为同步方法,他的锁对象是this
静态的同步方法的锁对象是:
类名.class java中的反射机制
StringBuffer sb = new StringBuffer() ;
//Vecotr:List的子实现类
Vector<String> v = new Vector<String>() ;
//Hashtable
Hashtable<String, String> hs = new Hashtable<String,String>() ;
//Vector:线程安全:一般不使用它
//public static <T> List<T> synchronizedList(List<T> list)
//返回指定列表支持的同步(线程安全的)列表
List<String> list = new ArrayList<String>() ;
//也可以实现线程安全的操作!
List<String> list2 = Collections.synchronizedList(new ArrayList<String>()) ;
}
}
7.
分析:当前的资源情况:Student: 共同的资源
setThread:设置学生数据(生成者)
getThread:获取学生数据(消费者)
StudentDemo(测试类)
问题1:
按照生产消费模式:分别进行产生数据和消费数据,通过测试打印出来:
null---0
线程的通信:两个或者两个以上的线程只能针对同一资源进行操作,
优化:改进:通过在成员变量的位置:创建一个学生对象,吧作为有参的参数进行传递
改进:给每一个线程加入while循环,让在控制出现多个数据,
改进之后出现的问题:
1)同一数据打印了多次
CPU的一点点时间片,要执行很多次!
2)姓名和年龄不符
线程随机性导致的!
针对这个问题:如何解决这种问题!
检验线程安全问题的标准:
1)是否是多线程环境 是
2)是否有共享数据 是
3)是否有多条语句对共享数据进行操作 是
解决方案:
用同步代码块将多条语句对共享数据的操作包装起来
加入同步机制
注意:同步锁,不同的线程之间使用的是同一把锁对象!
这种方案虽然解决了线程安全的问题,但是在控制台出现一大片数据,用户体验不好!
如果想让控制一次的进行显示出来.
如何解决:
java提供了另一种机制: 等待唤醒机制
Object类中提供了一些方法:
wait():线程等待
public final void notify():唤醒正在等待的单个线程
public final void notifyAll()唤醒所有线程
面试题:
这几个方法都是线程有关的方法,为什么把这个方法不定一在Thread类里面?
刚才这个案例,使用的锁对象进行调用,锁对象可以是任意对象!
而Object本身就是代表所有的类根类:代表所有对象!
public class Student {
String name ;
int age;
boolean flag;
}
public class Set implements Runnable {
private Student s;
public Set(Student s) {
this.s = s;
}
private int x = 0;
@Override
public void run() {
while (true) {
synchronized (s) {
if (s.flag == true) {
try {// 当产生数据了 变为true Set线程在这等着 等Get把数据读完
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (x % 2 == 0) {
s.name = "zhang";
s.age = 19;
} else {
s.name = "gao";
s.age = 21;
}
x++;
s.flag = true;// 修改标记 告诉Get 有数据了 你过来取
s.notify();// 唤醒Get线程
}
}
}
}
public class Get implements Runnable {
private Student s;
public Get(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if (s.flag == false) {// 变为false时 等待Get把数据读完
try {
s.wait();// 等待读数据
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "--" + s.age);
s.flag = false;// 当接收到数据 修改标记为false 告诉Set线程 我接收完数据了
s.notify();// 唤醒Set线程
}
}
}
}
public class Test {
public static void main(String[] args) {
Student s = new Student();// 创建同一个资源
// 分别创建生产者和消费者线程
Thread t1 = new Thread(new Set(s), "y");
Thread t2 = new Thread(new Get(s), "t");
t1.start();
t2.start();
}
}
}