java学习笔记(6)-多线程(1)
标签(空格分隔): 笔记
一、基本概念###
1.1 程序、进程、线程
- 程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
- 进程(process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
- 如运行中的QQ,运行中的MP3播放器
- 程序是静态的,进程是动态的
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
- 线程(thread):,进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间--->它们从同一堆中分配对象,可以 访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患。
1.2 并行与并发
- 并行:许多cpu同时执行多个任务
- 并发:一个cpu同时执行多个任务
1.3 使用多线程的优点
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
1.4 何时需要多线程
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序时。
二、线程的创建和使用###
1.继承Thread类,然后重写run函数,不需要直接调用他,这个函数是JVM自动调用的。
public class MyThread1Test extends Thread{
@Override
public void run() {
for(int i=0;i<2000;i++) {
System.out.println("Threa666666666666");
}
}
}
2.实现Runnable接口,重写run函数,同理也不需要调用这个run函数,如果调用了系统就会把它当成普通函数。
public class MyRunnableTest implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int j=0;j<2000;j++) {
System.out.println("Runnable********执行");
}
}
}
3.调用上述两个方法(普通外部类),启动多线程
Thread t1=new MyThread1Test();
t1.start();//
Thread t2=new Thread(new MyRunnableTest());
t2.start();
4.通过匿名内部类实现
new Thread() {
public void run() {
for(int i=0;i<500;i++) {
System.out.println("Threa666666666666");
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for(int j=0;j<500;j++) {
Thread.yield();
System.out.println("Runnable********执行");
}
}
}).start();
5.通过成员内部类实现
public class th3 extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<2000;i++) {
System.out.println("Threa666666666666");
}
}
}//把这个类放在一个类的内部,然后调用,调用过程略,之后直接start就行。
6.注意点:
- 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
- run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU 调度决定。
- 想要启动多线程,必须调用start方法。
- 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上 的异常“IllegalThreadStateException”。
7.线程的调度
调度策略
- 时间片策略
- 抢占式:高优先级的线程抢占CPU ,可以设置优先级,默认是5,自己可以设置1-10,数字越大优先级越高,但是并不意味着优先级越高就一定会先执行,只是先执行的概率要大一点
Java的调度方法
- 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
- 对高优先级,使用优先调度的抢占式策略
8.线程的分类
- 用户线程
- 守护线程,(例如java的垃圾回收)是伴随着用户线程的存活而存活 当用用户线程死亡的时候 守护线程不论是否执行结束 都会死亡(不会立即死亡,而是运行一段时间再死亡),调用的时候在start前声明setDaemon(true)
- 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
三、线程的生命周期###
- 新建:当创建了一个线程之后 new Thread 则当前线程就处于新建状态
- 就绪:当调用了线程的start方法 则线程就进入到就绪状态 准备着与其他线程抢夺CPU的执行权
- 运行:当线程获得CPU的执行全 处于执行中的线程 就是运行状态
- 阻塞:线程在执行过程中,因为某些原因 失去了CPU的执行权 暂时被挂起 但是线程还没有结束 则线程就处于阻塞状态
- 死亡:线程任务执行完成 或者被 强制终止 则线程进入到死亡状态
线程的五种状态时可以相互转换的
四、线程的同步###
例如:使用多线程模拟火车站售票程序,开启三个窗口售票。
public class Tick implements Runnable{
private static int num = 100;
@Override
public void run() {
while(true) {
if(num > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"销售车票座位号为:" + num--);
}else {
break;
}
}
}
}
class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.setName("t1窗口");
t2.setName("t2窗口");
t3.setName("t3窗口");
t1.start();
t2.start();
t3.start();
}
}
备注:上述代码会出现很多问题,比如会出现卖的票数编号重复,甚至出现负数编号的车票,例如当票数只剩一张的时候,一个线程执行,判断票数大于零之后进入了休眠状态,此时二号线程也进入了,因为一号线程休眠中,并未对票数进行修改,所以二号线程判断票数也是大于0,依次类推,当这些线程依次休眠结束后,自然会产生0,-1,-2这些不符合常理的票数编号。
- 上述问题的原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有 执行完,另一个线程参与进来执行。导致共享数据的错误。
- 解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。这就叫线程同步
1.线程同步实现
1.1同步代码块
同步代码块用synchronized这个关键字去实现,注意实现的时候不要和static搭配。同步代码块可以放在静态方法中和非静态方法中。
放在非静态方法中,此时的锁对象可以是任意对象 只需要保证所有的线程使用的是同一个锁对象 this也可以充当锁对象,但是必须提前创建好这个对象,如下所示
//------------------------------------------------------------
public class PrintTest {
public static void main(String[] args) {
PrintTest p1=new PrintTest();//如果可以将此行注释掉,然后把下面的两行注释解除,在看看执行效果。
new Thread() {
@Override
public void run() {
// PrintTest p1=new PrintTest();//解除此行注释看看效果
p1.print1();
}
}.start();
//----------------------------
new Thread() {
@Override
public void run() {
// PrintTest p2=new PrintTest();////解除此行注释看看效果,但要把下面的p1换成p2.
p1.print2();
}
}.start();
}
//----------------------------------
public void print1() {
while(true) {
synchronized(this) {
System.out.print("中");
System.out.print("国");
System.out.print("科");
System.out.print("技");
System.out.print("大");
System.out.print("学");
System.out.println();
}
}
}
//------------------------------------
public void print2() {
while(true) {
synchronized(this) {
System.out.print("北");
System.out.print("大");
System.out.print("青");
System.out.print("鸟");
System.out.println();
}
}
}
}
静态代码块放在静态方法中 可以使用静态对象来作为锁对象,例如字符串,本类的class对象,保证所有的线程使用的是同一个锁对象
public class PrintTest {
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
print1();
}
}.start();
new Thread() {
@Override
public void run() {
print2();
}
}.start();
}
public static void print1() {
while(true) {
synchronized(PrintTest.class) {
System.out.print("中");
System.out.print("国");
System.out.print("科");
System.out.print("技");
System.out.print("大");
System.out.print("学");
System.out.println();
}
}
}
public static void print2() {
while(true) {
synchronized(PrintTest.class) {
System.out.print("北");
System.out.print("大");
System.out.print("青");
System.out.print("鸟");
System.out.println();
}
}
}
}
对于非静态同步方法 锁对象为this,可以通过下面的同步代码块和非静态同步方法证明
public class PrintTest {
public static void main(String[] args) {
PrintTest p1=new PrintTest();
new Thread() {
@Override
public void run() {
p1.print1();
}
}.start();
new Thread() {
@Override
public void run() {
while(true) {
p1.print2();
}
}
}.start();
}
//处于非静态方法中的同步代码块
public void print1() {
while(true) {
synchronized(this) {
System.out.print("中");
System.out.print("国");
System.out.print("科");
System.out.print("技");
System.out.print("大");
System.out.print("学");
System.out.println();
}
}
}
//非静态同步方法
public synchronized void print2() {
System.out.print("北");
System.out.print("大");
System.out.print("青");
System.out.print("鸟");
System.out.println();
}
}
对于静态同步方法 锁对象为字节码对象 ,本类的class对象,可以通过下面来验证:
public class PrintTest02 {
public static void main(String[] args) {
PrintTest02 p2=new PrintTest02();
Thread t1=new Thread() {
@Override
public void run() {
while(true) {
print1();
}
}
};
Thread t2=new Thread() {
public void run() {
while(true) {
p2.print2();
}
}
};
t2.start();
t1.start();
}
//静态同步方法
public static synchronized void print1() {
System.out.print("中");
System.out.print("国");
System.out.print("科");
System.out.print("技");
System.out.print("大");
System.out.print("学");
System.out.println();
}
//下面这个函数加不加staic结果都一样
public void print2() {
while(true) {
synchronized(PrintTest02.class) {
System.out.print("北");
System.out.print("大");
System.out.print("青");
System.out.print("鸟");
System.out.println();
}
}
}
}