进程:
进程是指可执行程序并存放在计算机储存器得一个指令序列,它是一个动态执行得过程。
进程是系统分配资源(CPU,内存,硬盘等)的最小单位。
操作系统是多进程的。
线程:
线程是一个进程还要小的运行单位,一个进程包含多个线程
主线程是程序启动得时候就开启得线程。mian是主程序得入口
主线程中可以开启其他的线程就形成了多线程
多个线程共享该进程中的资源
线程其实是CPU运行得最小单位。给用户得感觉是多个线程同时在运行,但实际上不是的,是多个线程交替获取CPU的时间片,交替执行的,只是线程获取CPU的的时间比较短,是以毫秒计算的,所有人类感受不到。
多线程的好处
提升了CPU的执行效率
用户体验度更高
开发人员方便开发
实现线程的两种放方法:
- 一种是将类声明为Thread的子类
- 一种是实现Runnable接口
Thread类;
Java是通过Java.long.Thread类来代表线程的
按照面对对象的思想,Thread类应该是提供了实现多线程的方式。
构造方法 | 说明 |
Thread() | 创建一个线程对象 |
Thread(String name) | 创建一个具有指定名称的线程对象 |
Thread(Runnable targer) | 创建一个基于Runnable接口的实现类的线程的线程对象 |
Thread(Runnable target,String name) | 创建一个基于Runnable接口实现类党,并且具有指定名称的对象。 |
放法 | 说明 |
public void run() | 线程相关的代码写在该方法里面,一般需要子类重写 |
public void start() | 启动线程的方法 |
public static void sleep(long m) | 线程休眠m毫秒的方法 |
public void join() | 优先执行调用join()方法的线程 |
public static Thread currentThread() | 返回对象当前正在执行的线程对象的引用 |
public String name() | 返回该线程的名称 |
多线程的实现方案一:继承Thread类
1.定义一个子类MyThread继承类Thread,并重写run()方法
2.创建MyThread类的对象
3.调用线程对象的start()方法启动线程(启动线程后又JVM虚拟机自动调用run()方法)
优缺点:
优点:编码简单
缺点:线程已经继承Thread,无法再继承别的类。
讨论:
1.为什么不直接调用run()方法,而是调用start()方法?
- 直接调用run()方法会被当作普通方法执行,此时还是相当于单线程执行。
- 只有调用了start()方法才是启动一个线程执行。
代码演示:
public class Test {
public static void main(String[] args) {
MyThread m = new MyThread();
MyThread1 m1 = new MyThread1();
m.run();
m1.run();
}
}
class MyThread extends Thread{
public static int a;
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.print( " MyThread: " + i + " ");
a++;
if (this.a % 5==0) {
System.out.println("\n");
}
}
}
}
class MyThread1 extends Thread{
public static int a;
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.print(" MyThread1: " + i + " ");
a++;
if (this.a % 5==0) {
System.out.println("\n");
}
}
}
}
执行结果:
当调用start()方法时:
public class Test {
public static void main(String[] args) {
MyThread m = new MyThread();
MyThread1 m1 = new MyThread1();
m.start();
m1.start();
}
}
class MyThread extends Thread{
public static int a;
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.print( " MyThread: " + i + " ");
a++;
if (this.a % 5==0) {
System.out.println("\n");
}
}
}
}
class MyThread1 extends Thread{
public static int a;
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.print(" MyThread1: " + i + " ");
a++;
if (this.a % 5==0) {
System.out.println("\n");
}
}
}
}
执行结果:
2.把主线程任务放在子线程之前了。
- 这样主线程一直是先跑完,相当于是一个单线程的效果了。
代码演示:
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("main : " + i);
}
MyThread m = new MyThread();
m.start();
}
}
class MyThread extends Thread{
public static int a;
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.print(Thread.currentThread().getName()+ " : " + i + " ");
a++;
if (this.a == 5) {
System.out.println("\n");
}
}
}
}
执行结果:
3.如果一个线程报异常,会影响其他线程吗?
不会影响
Runnable接口:
- 只有一个方法run();
- Runnable时Java中用以实现线程的接口;
- 任何实现线程的类都必须继承实现该接口(Thread类也实现了Runnable接口);
多线程的实现方案二:实现Runnable接口(推荐)
1.定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法;
2.创建MyRunnable任务对象;
3.把MyRunnable任务对象交给Thread处理;
4.调用线程随想的start()方法启动线程
代码演示:
public class Test {
public static void main(String[] args) {
MyRunnable m = new MyRunnable();
MyRunnable1 m1 = new MyRunnable1();
Thread t = new Thread(m);
Thread t1 = new Thread(m1);
t.start();
t1.start();
}
}
class MyRunnable implements Runnable{
public static int a;
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.print( " MyRunnable: " + i + " ");
a++;
if (this.a % 5==0) {
System.out.println("\n");
}
}
}
}
class MyRunnable1 implements Runnable{
public static int a;
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.print(" MyRunnable1: " + i + " ");
a++;
if (this.a % 5==0) {
System.out.println("\n");
}
}
}
}
执行结果:
优缺点:
- 优点:线程任务只是实现接口,可以继续继承和实现接口,扩展性强。
- 缺点:编程多一层包装,如果线程有执行结果是不可以直接返回的。
线程安全问题:
多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。
买票模型演示:
需求:有五个窗口可以买票,一共有100张票,卖完为止。
如果五个窗口同时在卖票,会发生说明情况。
public class ticketSys {
public static void main(String[] args) {
ticket m1 = new ticket();
//创建五个线程,让他们一起工作。
Thread t1 = new Thread(m1, "窗口2");
Thread t2 = new Thread(m1, "窗口3");
Thread t3 = new Thread(m1, "窗口4");
Thread t4 = new Thread(m1, "窗口5");
Thread t5 = new Thread(m1, "窗口1");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class ticket implements Runnable{
private int piao = 100;
int count = 100;
@Override
public void run() {
while(true) {
if(piao > 0) {
try {
//假如卖一张票需要0.1秒
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//输出窗口还剩多少张票。
System.out.print(Thread.currentThread().getName() + " 还剩" + --piao+ ", ");
count--;
//为了输出美观,设置为没输出十次一换行。
if (count % 10 == 0 ) {
System.out.println();
}
}else {
//如果所剩的票小于0张就停止循环
break;
}
}
}
}
执行结果:
很显然上面的执行结果是不现实的,这是因为在while循环里的if判断语句中,假设线程t1抢占到了CPU的时间片进入了if判断语句,当判断语句还没有结束的时候,线程t2抢占到了CPU时间片也进入到了if判断语句,这是就会发生以上这些问题,这也叫线程安全问题。
线程安全问题出现的原因:
- 存在多线程并发
- 同时访问共享资源
- 存在修改共享资源
线程安全问题的处理方式:
将对共享数据操作的多条语句进行原子化封装即可 注意:封装的范围不要过大
原子化:可以理解为程序运行的最小单位
用synchronized代码进行封装
synchronize(锁对象){
需要被封装的对象
}
锁对象可以是任何一个类的对象,但是要保证全局的唯一性。也可以使用this。
建议使用共享资源作为锁的对象
对于实例方法建议使用this作为锁的对象
对于静态方法建议使用字节码(类名.class)对象作为锁对象
唯一性:只能保证只有一给锁。
this:这里指的是当前对象。当是同一个对象的时候使用。
修改上面买票模型代码:
public class ticketSys {
public static void main(String[] args) {
ticket m1 = new ticket();
Thread t1 = new Thread(m1, "窗口2");
Thread t2 = new Thread(m1, "窗口3");
Thread t3 = new Thread(m1, "窗口4");
Thread t4 = new Thread(m1, "窗口5");
Thread t5 = new Thread(m1, "窗口1");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class ticket implements Runnable{
private int piao = 100;
int count = 100;
private Object lo = new Object();
@Override
public void run() {
while(true) {
synchronized (lo) {
if(piao > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.print(Thread.currentThread().getName() + " 还剩" + --piao+ ", ");
count--;
if (count % 10 == 0 ) {
System.out.println();
}
}else {
break;
}
}
}
}
}
执行结果为:
注意:如果一个方法当中只有同步代码块,那么这个方法就可以定义为同步方法!此时同步方法的所就是this。